温湿度传感器 DHT11时序详解
- 嵌入式开发
- 11小时前
- 22热度
- 0评论
温湿度传感器 DHT11 的时序是理解这个模块的关键。只要搞懂单总线空闲高电平、主机起始信号、传感器响应信号,以及 40 位数据格式,就能更轻松地看懂逻辑分析仪波形,也能理解 ESP32 / MicroPython 代码为什么这样写。

一、温湿度传感器 DHT11 是什么
DHT11 是常见的数字温湿度传感器,通常采用 3 针或 4 针单排引脚封装。它内部集成了电阻式湿度传感器、NTC 测温元件以及简单的数据处理电路,因此可以直接输出数字量,而不是模拟电压。
DHT11 关键参数
- 湿度测量范围:20~90%RH
- 湿度测量精度:±5%RH
- 温度测量范围:0~50℃
- 温度测量精度:±2℃
- 工作电压:DC 3.3V / 5V
DHT11 使用 单总线通信,也就是只用一根 DATA 线完成数据收发。一次完整通信大约持续 4ms,发送的数据总共 40 位,格式如下:
- 8bit 湿度整数部分
- 8bit 湿度小数部分
- 8bit 温度整数部分
- 8bit 温度小数部分
- 8bit 校验位
二、DHT11时序中的单总线规则
理解 DHT11时序时,第一件事就是记住总线空闲状态。
DHT11 的 DATA 线在空闲时应保持为高电平,这个高电平通常由上拉电阻提供。主机或传感器需要发送信号时,都会先主动把总线拉低。拉低结束后,线路会在上拉作用下重新回到高电平。
这也是为什么在代码里经常会看到 Pin.PULL_UP。它不是可有可无的配置,而是为了让单总线在释放后回到正确的空闲状态。
DHT11 单总线的默认状态 = 高电平空闲
谁要开始说话,谁就先把线 拉低
DHT11时序与 40 位数据格式

| 名称 | 单总线格式定义 |
|---|---|
| 起始信号 | 主机把数据总线(DATA)拉低至少 18ms,最大不超过 30ms,通知传感器准备数据。 |
| 响应信号 | 传感器先把数据总线(DATA)拉低约 80μs,再拉高约 80μs,以响应主机起始信号。 |
| 数据格式 | 传感器响应完成后,一次性从数据总线输出 40 位数据,高位先出。 |
| 湿度 | 高 8 位是湿度整数部分,低 8 位是湿度小数部分。 |
| 温度 | 高 8 位是温度整数部分,低 8 位是温度小数部分。 |
| 校验位 | 校验位 = 湿度高位 + 湿度低位 + 温度高位 + 温度低位。 |
- Byte1:湿度整数
- Byte2:湿度小数
- Byte3:温度整数
- Byte4:温度小数
- Byte5:校验位
DHT11时序的起始信号与响应信号
主机先把总线拉低至少 18ms,然后释放总线,并等待 20~40μs。这个过程就是起始信号,也可以理解成主机发出的复位信号。

DHT11 检测到起始信号后,会先把总线拉低约 80μs,表示已经收到请求;然后再拉高约 80μs,表示即将开始发送数据。

DHT11时序里的数据位怎么判断 0 和 1
DHT11 在真正发数据时,每 1 位都从一段约 50μs 的低电平开始。低电平结束后进入高电平,高电平的持续时间决定当前位到底是 0 还是 1。
- 高电平持续约 26~28μs,表示数据位
0 - 高电平持续约 70μs,表示数据位
1

当最后 1 位传输完毕后,DHT11 会再拉低总线约 50μs,随后释放总线,线路重新回到高电平空闲状态。

用 MicroPython 读取 DHT11时序
下面这段代码采用“尽量贴近时序图”的写法。先发主机起始信号,再等待传感器响应,最后按 40 位数据的节奏逐位读取高电平宽度。
from machine import Pin, time_pulse_us
import dht
import time
def read_dht11_manual(pin_no=5, bit_threshold_us=50):
"""手动读取 DHT11 原始时序数据(按时序图步骤写法)。"""
pin = Pin(pin_no, Pin.OUT, value=1)
time.sleep_ms(1200) # DHT11 两次采样间隔建议 >= 1s
# 1) 主机起始信号:拉低 >=18ms,再拉高 20~40us
pin.value(0)
time.sleep_ms(20)
pin.value(1)
pin.init(Pin.IN, Pin.PULL_UP) # 释放总线
time.sleep_us(20)
# 2) 传感器响应:先低约80us,再高约80us
# 先等待响应低电平开始
t0 = time.ticks_us()
while pin.value() == 1:
if time.ticks_diff(time.ticks_us(), t0) > 120:
raise OSError("DHT11 no response")
resp_low = time_pulse_us(pin, 0, 300)
resp_high = time_pulse_us(pin, 1, 300)
highs = []
bits = []
# 3) 读取 40 位数据
# 每位:先 50us 低电平,再进入高电平(26~28us=0,约70us=1)
for _ in range(40):
time_pulse_us(pin, 0, 120)
high_us = time_pulse_us(pin, 1, 150)
highs.append(high_us)
if high_us < 0:
bits.append("?")
elif high_us > bit_threshold_us:
bits.append("1")
else:
bits.append("0")
return resp_low, resp_high, highs, bits
def print_pulse_data(resp_low, resp_high, highs, bits):
"""打印响应脉冲与 40 位数据(按字节分组)。"""
print("response low =", resp_low, "us")
print("response high =", resp_high, "us")
print("-" * 40)
for i in range(0, 40, 8):
print("high%02d-%02d:" % (i, i + 7), highs[i:i + 8])
for i in range(0, 40, 8):
print("bit %02d-%02d:" % (i, i + 7), " ".join(bits[i:i + 8]))
def read_dht11_lib(pin_no=5):
"""使用 dht 库读取传感器数据。"""
sensor = dht.DHT11(Pin(pin_no))
sensor.measure()
return sensor.temperature(), sensor.humidity()
# 主程序入口
if __name__ == "__main__":
# 手动读取原始数据
try:
resp_low, resp_high, highs, bits = read_dht11_manual(5)
print_pulse_data(resp_low, resp_high, highs, bits)
except OSError as e:
print("manual read error:", e)
# 与库函数对比
time.sleep_ms(1500)
temp, humi = read_dht11_lib(5)
print("-" * 40)
print("dht lib temp =", temp, "C")
print("dht lib humi =", humi, "%")
print("expected bytes=", [humi, 0, temp, 0, (humi + temp) & 0xFF])
调试 DHT11时序时重点看什么
如果你在逻辑分析仪或串口输出里看到读数不稳定,可以优先检查下面几个点:
- 总线空闲时是否为高电平
- 主机起始信号是否真的拉低了至少 18ms
- 传感器响应是否出现“低约 80μs + 高约 80μs”
- 数据位高电平是否明显分成两档:一档接近 26~28μs,一档接近 70μs
- 上拉电阻是否合适,GPIO 是否已经正确切换到输入上拉模式
如果波形正常但代码总丢位,优先检查
释放总线的时机、读取超时参数 和 引脚模式切换。