4、读取第一个传感器(DHT11/DHT22):DHT11时序解析、Python代码读取温湿度、数据校验与异常处理
4.1 先聊聊DHT11和DHT22的区别
很多初学者会问:DHT11和DHT22到底选哪个?
我个人习惯把DHT11叫「入门款」,DHT22叫「进阶款」。说白了,DHT11精度低一些,温度只到±2°C,湿度±5%RH。而DHT22能做到±0.5°C和±2%RH。价格上DHT11便宜几块钱,但DHT22的稳定性好不少。
我在项目中遇到过用DHT11做环境监测,结果数据跳得厉害,后来换成DHT22就稳了。如果你只是做个演示,DHT11够用。但真要采集数据做分析,我建议直接上DHT22。
| 参数 | DHT11 | DHT22 |
|---|---|---|
| 温度范围 | 0~50°C | -40~80°C |
| 温度精度 | ±2°C | ±0.5°C |
| 湿度范围 | 20~90%RH | 0~100%RH |
| 湿度精度 | ±5%RH | ±2%RH |
| 采样周期 | 1秒 | 2秒 |
4.2 DHT11的时序到底怎么回事?
嗯,这里要注意。DHT11用的是单总线协议,说白了就是一根数据线既发又收。很多新手在这里翻车,因为时序要求很严格。
整个通信过程分三步:
- 主机发起信号:你把数据线拉低至少18ms,再拉高20~40μs
- 传感器响应:DHT11会拉低80μs,再拉高80μs,告诉你「我准备好了」
- 数据传输:连续发送40位数据,每位数据用高低电平的时长来区分0和1
为什么会这样设计?因为单总线没有时钟线,全靠时间长短来编码。0和1的区别其实很简单:
- 数据0:低电平50μs,高电平26~28μs
- 数据1:低电平50μs,高电平70μs
你想想看,如果高电平时间短就是0,长就是1。我在调试时经常用逻辑分析仪抓波形,一眼就能看出数据对不对。
核心要点:DHT11的时序本质就是「用高电平宽度编码数据」。只要你能精确控制微秒级的延时,就能搞定。
4.3 用Python读取DHT11——手写驱动
虽然市面上有现成的库,但我建议你至少手写一次。为什么?因为只有自己写过,才能真正理解时序。
下面是我常用的代码,基于树莓派的GPIO:
import RPi.GPIO as GPIO
import time
# 定义DHT11数据引脚
DHT_PIN = 4
def read_dht11():
# 发送起始信号
GPIO.setup(DHT_PIN, GPIO.OUT)
GPIO.output(DHT_PIN, GPIO.LOW)
time.sleep(0.02) # 拉低至少18ms
GPIO.output(DHT_PIN, GPIO.HIGH)
time.sleep(0.00004) # 拉高40μs
# 切换为输入模式,等待响应
GPIO.setup(DHT_PIN, GPIO.IN)
# 等待传感器拉低(响应信号)
while GPIO.input(DHT_PIN) == GPIO.HIGH:
pass
# 等待传感器拉高
while GPIO.input(DHT_PIN) == GPIO.LOW:
pass
# 等待数据开始
while GPIO.input(DHT_PIN) == GPIO.HIGH:
pass
# 读取40位数据
data = []
for i in range(40):
# 等待低电平结束
while GPIO.input(DHT_PIN) == GPIO.LOW:
pass
# 计时高电平宽度
start = time.time()
while GPIO.input(DHT_PIN) == GPIO.HIGH:
pass
end = time.time()
duration = end - start
# 判断0还是1
if duration > 0.00005:
data.append(1)
else:
data.append(0)
# 解析数据
humidity = data[0:8]
humidity_dec = data[8:16]
temperature = data[16:24]
temperature_dec = data[24:32]
checksum = data[32:40]
# 二进制转十进制
h = int(''.join(str(bit) for bit in humidity), 2)
t = int(''.join(str(bit) for bit in temperature), 2)
return t, h
个人经验:这段代码里最关键的其实是高电平计时那块。我曾经用time.sleep()来延时判断,结果发现精度不够,数据经常读错。后来改用time.time()计算时间差,才稳定下来。
4.4 数据校验——别被假数据骗了
DHT11的40位数据里,最后8位是校验和。校验规则很简单:前4个字节(湿度整数+湿度小数+温度整数+温度小数)相加,取低8位,应该等于校验字节。
举个例子:
- 湿度整数:0010 1101 (45)
- 湿度小数:0000 0000 (0)
- 温度整数:0001 1100 (28)
- 温度小数:0000 0000 (0)
- 校验和:0100 0101 (69)
计算:45 + 0 + 28 + 0 = 73,低8位是0100 1001 (73)。嗯?不等于69?那这组数据就是错的,直接丢弃。
我在项目中遇到过最坑的一次:校验和明明对了,但温度显示-10°C,大夏天怎么可能?后来发现是传感器老化,数据位偶尔会跳变。从那以后我加了「合理性检查」——温度在-40~80°C之外直接扔掉。
避坑指南:我曾经只做校验和检查,结果被坑惨了。建议你至少加两层校验:
- 第一层:校验和匹配
- 第二层:数值范围检查(温度-40~80°C,湿度0~100%RH)
4.5 异常处理——让代码更健壮
实际项目中,DHT11经常读不到数据。原因很多:接线松动、电磁干扰、传感器损坏。你不能让程序卡死在那里。
我常用的策略是「重试机制」:
def read_dht11_safe(retries=5):
for attempt in range(retries):
try:
t, h = read_dht11()
# 合理性检查
if -40 <= t <= 80 and 0 <= h <= 100:
return t, h
else:
print(f"第{attempt+1}次:数据不合理,重试")
except Exception as e:
print(f"第{attempt+1}次:读取失败 - {e}")
time.sleep(1)
# 所有重试都失败,返回None
return None, None
另外,每次读取之间至少要间隔1秒。DHT11的采样周期就是1秒,你读得太快它根本来不及更新数据。
核心原则:传感器读取代码一定要有超时和重试。我见过太多人因为没加异常处理,程序跑几个小时就崩了。
4.6 本章知识体系
下面这张图帮你理清DHT11读取的完整流程:
4.7 写在最后
DHT11的读取其实不难,难的是把细节处理好。时序、校验、异常处理,这三样缺一不可。我刚开始做物联网项目时,就因为在异常处理上偷懒,结果半夜被电话叫醒——系统挂了。
嗯,记住一句话:传感器读取代码,宁可多写10行异常处理,也别少写1行。你省下的时间,迟早会加倍还回来。
实用技巧:调试时可以用示波器或逻辑分析仪看波形。没有仪器的话,在代码里加print打印每个步骤的时间,也能帮你定位问题。
可可精品资料库