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用的是单总线协议,说白了就是一根数据线既发又收。很多新手在这里翻车,因为时序要求很严格。

整个通信过程分三步:

  1. 主机发起信号:你把数据线拉低至少18ms,再拉高20~40μs
  2. 传感器响应:DHT11会拉低80μs,再拉高80μs,告诉你「我准备好了」
  3. 数据传输:连续发送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读取的完整流程:

DHT11读取流程 步骤1:主机发起 拉低18ms → 拉高40μs 步骤2:传感器响应 拉低80μs → 拉高80μs 步骤3:数据传输 40位数据(5字节) 解析 校验 40位数据格式 湿度整数(8位) 湿度小数(8位) 温度整数(8位) 温度小数(8位) 校验(8位) 校验规则:前4字节相加,取低8位 = 校验字节

4.7 写在最后

DHT11的读取其实不难,难的是把细节处理好。时序、校验、异常处理,这三样缺一不可。我刚开始做物联网项目时,就因为在异常处理上偷懒,结果半夜被电话叫醒——系统挂了。

嗯,记住一句话:传感器读取代码,宁可多写10行异常处理,也别少写1行。你省下的时间,迟早会加倍还回来。

实用技巧:调试时可以用示波器或逻辑分析仪看波形。没有仪器的话,在代码里加print打印每个步骤的时间,也能帮你定位问题。

可可精品资料库