4、传感器驱动开发(上):DHT22温湿度传感器驱动编写、时序分析、数据校验
各位同学,咱们今天开始啃传感器驱动这块硬骨头。DHT22这个传感器,说实话,在温室项目里用得特别多。便宜、精度还行、单总线通信,省IO口。但坑也不少,我当年第一个温室项目就差点被它搞崩溃。
嗯,咱们先理清楚一件事:DHT22和DHT11啥区别?说白了,DHT22精度更高,测量范围更宽。温度精度±0.5°C,湿度精度±2%RH。DHT11呢?温度±2°C,湿度±5%RH。温室里控温控湿,你想想看,差2度可能苗就蔫了。所以我个人习惯,但凡正经项目,直接上DHT22。
4.1 硬件连接与原理
DHT22只有4个引脚,但实际只用3个:VCC、DATA、GND。第4脚是NC,悬空就行。
| 引脚 | 功能 | 连接 |
|---|---|---|
| 1 | VCC | 3.3V ~ 5V |
| 2 | DATA | GPIO + 上拉电阻 |
| 3 | NC | 悬空 |
| 4 | GND | 地 |
通信协议是单总线,说白了就是一根线既发又收。主机(MCU)和从机(DHT22)分时复用这根线。时序要求挺严格,微秒级的延迟都得算准。
4.2 通信时序详解
DHT22的通信流程分三步:主机发起、从机响应、数据传输。咱们一步步拆。
4.2.1 主机发起信号
MCU先把DATA线拉低,至少持续18ms。为什么是18ms?因为DHT22要检测到这个低电平信号,才能从休眠中唤醒。我刚开始写的时候只拉了1ms,结果传感器根本不鸟我。
拉低18ms后,MCU释放总线(拉高),然后等待20~40μs。这个等待时间很关键,短了传感器来不及响应,长了就超时了。
// 主机发起信号
void dht22_start(void) {
// 拉低至少18ms
gpio_set_mode(DHT22_PIN, GPIO_MODE_OUTPUT);
gpio_write(DHT22_PIN, 0);
delay_ms(20); // 我习惯给20ms,留点余量
// 释放总线,拉高20~40μs
gpio_write(DHT22_PIN, 1);
delay_us(30);
// 切换为输入模式,准备接收
gpio_set_mode(DHT22_PIN, GPIO_MODE_INPUT);
}
4.2.2 从机响应信号
主机释放总线后,DHT22会做两件事:先把总线拉低80μs,再拉高80μs。这就是响应信号。如果没看到这个响应,说明传感器没工作,或者线没接好。
4.2.3 数据位时序
响应信号之后,就是40位的数据了。每一位的传输方式都一样:
- 每位开始,DHT22先把总线拉低50μs
- 然后拉高,拉高的时间长短决定是0还是1
- 拉高26~28μs表示0
- 拉高70μs表示1
你想想看,这不就是脉冲宽度调制吗?说白了,就是通过高电平的持续时间来编码。
// 读取一个位
uint8_t dht22_read_bit(void) {
// 等待低电平结束(50μs)
while(gpio_read(DHT22_PIN) == 0);
// 延时30μs,在中间采样
delay_us(30);
// 读取电平状态
if(gpio_read(DHT22_PIN) == 1) {
// 高电平还在,说明是1
while(gpio_read(DHT22_PIN) == 1); // 等待位结束
return 1;
} else {
// 高电平已结束,说明是0
return 0;
}
}
4.3 数据格式与校验
40位数据怎么分配?看这张表:
| 字节 | 位范围 | 含义 |
|---|---|---|
| Byte 0 | Bit 39 ~ Bit 32 | 湿度整数部分 |
| Byte 1 | Bit 31 ~ Bit 24 | 湿度小数部分 |
| Byte 2 | Bit 23 ~ Bit 16 | 温度整数部分 |
| Byte 3 | Bit 15 ~ Bit 8 | 温度小数部分 |
| Byte 4 | Bit 7 ~ Bit 0 | 校验和 |
实际湿度值 = 湿度整数 + 湿度小数/10。温度也一样。比如湿度整数是25,小数是6,那实际湿度就是25.6%RH。
校验和怎么算?简单:Byte0 + Byte1 + Byte2 + Byte3,取低8位。如果等于Byte4,数据有效。否则扔掉重读。
// 数据校验
uint8_t dht22_check_sum(uint8_t data[5]) {
uint8_t sum = data[0] + data[1] + data[2] + data[3];
return (sum == data[4]);
}
4.4 完整驱动代码
把上面这些拼起来,就是一个完整的DHT22驱动。我习惯把读取函数封装成一次完整的事务:发起、等待响应、读40位、校验、返回数据。
// DHT22完整读取函数
int dht22_read(float *humidity, float *temperature) {
uint8_t data[5] = {0};
// 1. 发起信号
dht22_start();
// 2. 等待响应(超时200μs)
uint32_t timeout = 200;
while(gpio_read(DHT22_PIN) == 1) {
if(--timeout == 0) return -1; // 无响应
}
while(gpio_read(DHT22_PIN) == 0); // 等待低电平结束
while(gpio_read(DHT22_PIN) == 1); // 等待高电平结束
// 3. 读取40位数据
for(int i = 0; i < 40; i++) {
uint8_t bit = dht22_read_bit();
data[i / 8] = (data[i / 8] << 1) | bit;
}
// 4. 校验
if(!dht22_check_sum(data)) return -2; // 校验失败
// 5. 计算实际值
*humidity = data[0] + data[1] / 10.0;
*temperature = data[2] + data[3] / 10.0;
return 0; // 成功
}
4.5 常见问题与调试
写驱动时最容易踩的坑,我列几个:
- 读出来全是0或0xFF:大概率是上拉电阻没接,或者GPIO模式没切对
- 校验和总错:时序不对,尤其是采样点位置偏了
- 偶尔读成功偶尔失败:中断干扰了时序。读DHT22时最好关中断,或者用临界区保护
- 温度湿度值不变:读取间隔太短,传感器没更新数据
嗯,DHT22的驱动开发就讲到这里。下节课咱们继续讲传感器驱动开发的下半部分,重点讲光照传感器和土壤湿度传感器的驱动。到时候会涉及ADC采样和I2C通信,比这个稍微复杂点,但套路是一样的。
记住一句话:传感器驱动,时序是灵魂,校验是底线。时序对了,数据就对了八成。