2. DHT22温湿度传感器驱动:从时序到实战
DHT22这颗传感器,我用了不下五年。说实话,第一次调它的时候差点被时序搞疯——单总线协议看着简单,但坑是真不少。今天咱们就把它的底裤扒干净,从通信协议到代码实现,一步到位。
2.1 DHT22通信协议解析
DHT22用的是单总线协议,说白了就是一根数据线既当输入又当输出。主机和传感器之间靠拉高拉低电平来传递信息。我刚开始做项目时总觉得单总线比I2C简单,后来发现恰恰相反——时序要求太严格了。
先看数据格式。DHT22一次传输40位数据,结构如下:
| 数据位 | 内容 | 说明 |
|---|---|---|
| bit 31-24 | 湿度整数部分 | 0-100% RH |
| bit 23-16 | 湿度小数部分 | 精确到0.1% |
| bit 15-8 | 温度整数部分 | -40~80°C |
| bit 7-0 | 温度小数部分 | 精确到0.1°C |
| bit 39-32 | 校验和 | 前4字节之和的低8位 |
举个例子:如果收到 0000 0010 1001 0010 0000 0001 1011 1100 1010 0010,拆开看就是:
- 湿度 = 0x02 0x92 = 658 → 65.8% RH
- 温度 = 0x01 0xBC = 444 → 44.4°C
- 校验和 = 0xA2
- 验证:0x02 + 0x92 + 0x01 + 0xBC = 0x151,取低8位 = 0x51 ≠ 0xA2 → 数据无效
2.2 时序图解读
时序图这东西,很多人一看就头大。其实你把它拆成几个阶段就好理解了。DHT22的通信过程分四步:
- 主机发起信号:主机拉低总线至少18ms,然后拉高20-40μs
- 传感器响应:传感器拉低80μs,再拉高80μs
- 数据传输:40位数据,每位由低电平50μs+高电平决定
- 结束:传感器拉低50μs后释放总线
关键在第三步。每个数据位都是先拉低50μs,然后拉高。高电平持续26-28μs代表"0",持续70μs代表"1"。说白了就是看高电平的时间长短。
我的经验:实际测量时,26μs和70μs的边界没那么清晰。我一般取中间值50μs作为判断阈值。高电平超过50μs算"1",否则算"0"。这个阈值在大多数MCU上都能稳定工作。
下面这张图把整个时序流程画清楚了:
2.3 使用Adafruit_DHT库读取数据
自己写时序代码太折腾了,尤其是不同平台移植起来很麻烦。我推荐直接用Adafruit_DHT库,它把底层时序封装好了,支持树莓派、Jetson Nano等主流平台。
安装很简单:
pip install Adafruit_DHT
然后看个完整示例:
import Adafruit_DHT
import time
# 定义传感器类型和引脚
SENSOR = Adafruit_DHT.DHT22
PIN = 4 # GPIO4,对应物理引脚7
def read_dht22():
"""
读取DHT22温湿度数据
返回:(humidity, temperature) 或 (None, None)
"""
humidity, temperature = Adafruit_DHT.read_retry(SENSOR, PIN)
return humidity, temperature
def main():
print("DHT22温湿度读取程序启动...")
print("按 Ctrl+C 退出\n")
while True:
humidity, temperature = read_dht22()
if humidity is not None and temperature is not None:
# 温度单位是摄氏度,湿度是百分比
print(f"温度: {temperature:.1f}°C, 湿度: {humidity:.1f}%")
else:
print("读取失败,请检查接线")
# DHT22建议读取间隔2秒以上
time.sleep(2)
if __name__ == "__main__":
main()
我的习惯:实际项目中我不会直接用read_retry(),因为它会无限重试。我一般自己写个带超时的循环,比如重试3次就放弃。这样系统不会因为传感器故障而卡死。
2.4 数据校验与异常处理
数据校验这块,很多人觉得DHT22自带校验和就万事大吉了。其实不然。我在项目中遇到过好几次校验和通过但数据明显异常的情况——比如湿度突然跳到120%,温度飙到99°C。
为什么会这样?因为校验和只能检测传输错误,不能检测传感器本身故障。所以我们需要加几层防护:
2.4.1 校验和验证
Adafruit_DHT库内部已经做了校验和验证,但如果你自己写驱动,一定要手动校验:
def verify_checksum(data):
"""
data: 长度为5的字节数组 [hum_H, hum_L, temp_H, temp_L, checksum]
返回 True/False
"""
if len(data) != 5:
return False
total = (data[0] + data[1] + data[2] + data[3]) & 0xFF
return total == data[4]
2.4.2 范围检查
DHT22的官方参数范围:
| 参数 | 最小值 | 最大值 |
|---|---|---|
| 温度 | -40°C | 80°C |
| 湿度 | 0% RH | 100% RH |
超出这个范围的数据,直接扔掉。我一般还会加个变化率限制——比如相邻两次读取的温度变化超过5°C,大概率是传感器出问题了。
2.4.3 超时处理
DHT22的响应时间一般在20-200ms之间。如果超过200ms还没收到响应,直接超时退出。别死等。
import time
def read_with_timeout(sensor, pin, timeout=0.5):
"""
带超时的DHT22读取
"""
start = time.time()
while time.time() - start < timeout:
humidity, temperature = Adafruit_DHT.read(sensor, pin)
if humidity is not None:
return humidity, temperature
time.sleep(0.05) # 50ms轮询一次
return None, None # 超时返回None
2.5 完整驱动封装
把上面这些整合起来,写一个健壮的驱动类:
import Adafruit_DHT
import time
class DHT22Driver:
def __init__(self, pin=4, retry=3, timeout=0.5, interval=2):
self.pin = pin
self.sensor = Adafruit_DHT.DHT22
self.retry = retry
self.timeout = timeout
self.interval = interval
self.last_read = 0
def read(self):
"""带异常处理的读取方法"""
# 检查读取间隔
if time.time() - self.last_read < self.interval:
return None, None
for attempt in range(self.retry):
try:
humidity, temperature = Adafruit_DHT.read(
self.sensor, self.pin
)
if humidity is not None and temperature is not None:
# 范围检查
if -40 <= temperature <= 80 and 0 <= humidity <= 100:
self.last_read = time.time()
return round(temperature, 1), round(humidity, 1)
else:
print(f"数据超范围: T={temperature}, H={humidity}")
else:
print(f"第{attempt+1}次读取失败")
except Exception as e:
print(f"读取异常: {e}")
time.sleep(0.1)
return None, None
# 使用示例
dht = DHT22Driver(pin=4)
temp, hum = dht.read()
if temp:
print(f"温度: {temp}°C, 湿度: {hum}%")
总结一下:DHT22驱动看似简单,但要做好异常处理并不容易。我的经验是——校验和检查是底线,范围检查是常规操作,超时和重试机制是保命符。这三层防护做好了,传感器基本不会拖垮整个系统。
嗯,到这里DHT22的驱动就讲完了。代码可以直接拿去用,但建议你根据实际硬件平台微调一下时序参数。不同MCU的主频不同,延时函数精度也不一样,跑一遍测试就知道该调多少了。