2、GPIO基础与数字传感器:GPIO模式配置、数字输入输出、按键消抖、读取DHT11温湿度传感器
各位同学,欢迎来到第二章。上一章我们把ESP32的开发环境搭好了,也点亮了那颗经典的LED。从这一章开始,我们要真正跟硬件打交道了。
GPIO,全称叫通用输入输出口。说白了,它就是芯片伸出来的那些引脚,你可以让它们输出高电平或低电平,也可以读取外部是高还是低。我刚开始学单片机时,觉得GPIO不就是0和1嘛,有啥好学的?后来踩的坑多了才发现,这里面的门道真不少。
本章核心目标:掌握ESP32的GPIO模式配置,学会数字输入输出,搞定按键消抖,最后读取DHT11温湿度传感器。一步一个脚印,都是实战干货。
2.1 GPIO模式配置:别让引脚"裸奔"
ESP32的GPIO模式,我习惯把它分成三大类:输出模式、输入模式和特殊功能模式。输出模式里又分推挽输出和开漏输出;输入模式里分浮空输入、上拉输入和下拉输入。
你可能会问:为啥要分这么细?直接输出高低电平不就行了?
嗯,这里有个坑。我曾经在一个项目里,直接用推挽输出驱动一个I2C总线,结果总线拉不下来,通信一直失败。后来才发现,I2C协议要求开漏输出,因为多个设备要共享一根线,谁都不能强行拉高,只能拉低。
| 模式 | 配置函数 | 典型用途 | 注意事项 |
|---|---|---|---|
| 推挽输出 | pinMode(pin, OUTPUT) |
LED、蜂鸣器、继电器 | 高电平驱动能力强,但多个输出不能并联 |
| 开漏输出 | pinMode(pin, OUTPUT_OPEN_DRAIN) |
I2C总线、电平转换 | 必须外接上拉电阻 |
| 浮空输入 | pinMode(pin, INPUT) |
读取外部明确电平 | 引脚悬空时电平不确定,容易误读 |
| 上拉输入 | pinMode(pin, INPUT_PULLUP) |
按键读取(按下为低) | 内部上拉电阻约40kΩ,噪声敏感时需外接更小电阻 |
| 下拉输入 | pinMode(pin, INPUT_PULLDOWN) |
按键读取(按下为高) | ESP32部分引脚不支持内部下拉,需查手册 |
我的习惯:读取按键时,我几乎只用 INPUT_PULLUP。为啥?因为高电平默认状态稳定,按下拉低,逻辑清晰。而且内部上拉省一个电阻,板子能小一点。
2.2 数字输入输出:从LED到按键
数字输出,就是 digitalWrite(pin, HIGH/LOW)。数字输入,就是 digitalRead(pin)。听起来简单,但实际用起来有几个细节要注意。
先看一个完整的例子:用按键控制LED。
const int ledPin = 2; // 板载LED
const int buttonPin = 0; // BOOT按键
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP); // 内部上拉
Serial.begin(115200);
}
void loop() {
int buttonState = digitalRead(buttonPin);
if (buttonState == LOW) { // 按键按下
digitalWrite(ledPin, HIGH);
Serial.println("Button pressed!");
} else {
digitalWrite(ledPin, LOW);
}
delay(50); // 简单延时,后面会讲消抖
}
这段代码能跑,但有个问题。你按下按键时,串口可能会打印好几条"Button pressed!"。这就是按键抖动。
2.3 按键消抖:别让抖动坑了你
按键抖动的本质是什么?机械触点在闭合和断开的瞬间,会弹跳几次,每次持续几毫秒到十几毫秒。如果不处理,一次按下会被误读成多次。
我遇到过最离谱的一次,是一个工业控制面板,操作员按一次启动键,机器启动了三次。查了两天才发现是消抖没做好,生产线差点停摆。
消抖有两种主流方法:
- 硬件消抖:在按键两端并联一个0.1μF电容,或者加一个RC低通滤波器。简单粗暴,适合量产。
- 软件消抖:检测到电平变化后,延时10-20ms再读一次。如果状态一致,才确认有效。
我个人更推荐软件消抖,因为不改硬件,灵活调整。下面是我常用的消抖函数:
bool debounce(int pin) {
if (digitalRead(pin) == LOW) { // 第一次检测到按下
delay(20); // 等待20ms,避开抖动期
if (digitalRead(pin) == LOW) { // 再次确认
while (digitalRead(pin) == LOW); // 等待松开
return true; // 返回有效按下
}
}
return false;
}
注意:上面的 while 等待松开会阻塞程序。在复杂项目中,建议用状态机或定时器实现非阻塞消抖。我们后面章节会讲。
2.4 读取DHT11温湿度传感器:实战开始
DHT11,可能是物联网入门最常用的数字温湿度传感器。它用单总线协议通信,说白了就是一根数据线既当输入又当输出,靠时序来区分数据位。
DHT11的数据格式是这样的:
| 字节 | 内容 | 范围 |
|---|---|---|
| Byte 0 | 湿度整数部分 | 0-100%RH |
| Byte 1 | 湿度小数部分 | 0-9 |
| Byte 2 | 温度整数部分 | -20~60°C |
| Byte 3 | 温度小数部分 | 0-9 |
| Byte 4 | 校验和 | 前4字节之和的低8位 |
读取DHT11的时序,说白了就是三步:
- 主机发起信号:拉低数据线至少18ms,然后拉高20-40μs。
- DHT11响应:拉低80μs,再拉高80μs,表示准备好了。
- 读取40位数据:每一位开始都是50μs的低电平,然后高电平持续26-28μs表示0,持续70μs表示1。
听起来复杂?其实用库就简单了。但我建议你至少自己手写一次,才能真正理解单总线协议。
下面是用DHT11库的代码:
#include <DHT.h>
#define DHTPIN 4 // 数据线接GPIO4
#define DHTTYPE DHT11 // DHT11型号
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
dht.begin();
Serial.println("DHT11 Ready!");
}
void loop() {
delay(2000); // DHT11采样间隔至少1秒,建议2秒
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
Serial.print("Humidity: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(t);
Serial.println(" *C");
}
关键提醒:DHT11的采样周期是1秒,也就是说你最快1秒读一次。读太快会返回NAN。我习惯在 loop() 里加 delay(2000),稳定可靠。
2.5 避坑指南:我踩过的那些坑
- 引脚选择:ESP32的GPIO6-11默认接Flash,别乱用。GPIO34-39只能输入,不能输出。我一开始没看手册,用GPIO34驱动LED,死活不亮。
- 上拉电阻:DHT11的数据线必须外接4.7k-10k上拉电阻到3.3V。忘了接?数据读出来全是0。别问我怎么知道的。
- 电源稳定:DHT11对电源噪声敏感,最好在VCC和GND之间加一个100μF电解电容。特别是用面包板时,接触不良会导致读数跳变。
- 线长限制:DHT11的数据线不要超过20米。超过这个长度,信号会畸变。我试过用50米网线接DHT11,结果读出来湿度120%,温度-40°C。
我的调试习惯:第一次读取DHT11时,先用逻辑分析仪抓一下波形。看看起始信号对不对,响应信号有没有,数据位的宽度是否在范围内。波形对了,代码基本就对了。
好了,这一章的内容就到这里。GPIO配置、数字输入输出、按键消抖、DHT11读取,这些都是嵌入式开发的"基本功"。练好了,后面的项目才能走得稳。