一、基本概述
DHT11 是低成本单总线数字温湿度复合传感器,内置湿敏电阻、NTC 热敏电阻与专用 8 位处理芯片,出厂已校准,直接输出数字信号,无需外接 ADC,是单片机入门最常用环境传感器。 优点:价格极低、接线简单、3.3V/5V 通用供电; 缺点:精度差、刷新慢、仅输出整数、高低温 / 极端湿度误差大。
二、核心电气参数
| 供电电压 | 3.3V ~ 5.5V | 兼容 STM32、Arduino、ESP32 |
| 工作电流 | 0.5~2.5mA | 低功耗,适合电池设备 |
| 温度量程 | 0℃ ~ 50℃ | 无法测零下 |
| 温度精度 | ±2℃ | 分辨率 1℃,无小数 |
| 湿度量程 | 20% ~ 90%RH | 5%~20%、90% 以上误差剧增 |
| 湿度精度 | ±5%RH | 分辨率 1% RH,无小数 |
| 通信方式 | 单总线(半双工) | 仅 1 根 GPIO 双向通信 |
| 最小采样间隔 | ≥1000ms(1s) | 频繁读取会读取失败 |
| 最大传输距离 | 20 米 | 长线需 4.7kΩ 上拉电阻(短距离使用GPIO口的上拉输入就行) |
| 单次数据长度 | 40bit(5 字节) | 带校验和防出错 |
三、引脚定义(两种封装)
3 引脚模块(主流)
4 引脚直插模块
四、单总线通信时序(官方标准时序)
DHT11采用主机(单片机)主动发起、从机(传感器)被动应答的半双工单总线通信,全程时序严格、微秒级精准,时序偏差会直接导致通信失败、数据乱码。完整通信分为四大阶段:总线空闲、主机启动信号、传感器应答信号、40bit数据逐位传输、传输结束复位。
1. 总线空闲状态
常态下,4.7kΩ上拉电阻将DATA总线持续拉高,保持恒定高电平,传感器处于休眠待命状态,无数据传输、无信号输出。
2. 主机发送启动信号(通信触发核心)

主控GPIO配置为输出模式,主动拉低总线电平,触发传感器唤醒,标准时序参数:
-
总线拉低时长:18ms ~ 30ms(必须≥18ms,确保休眠的DHT11稳定检测到启动信号,不可过短;不建议超过30ms,避免传感器超时复位)
-
拉低结束后,主机立即释放总线、拉高电平,持续等待:20μs ~ 40μs
-
阶段作用:强制唤醒传感器,告知传感器准备接收数据上传指令
3. DHT11传感器应答握手信号(关键校验阶段)

传感器检测到完整启动信号后,从休眠唤醒,输出固定应答时序,告知主机“准备就绪,即将传输数据”,全程80μs精准时序:
-
第一步:传感器主动拉低DATA总线,持续80μs(应答低电平,主机可通过检测低电平确认传感器正常唤醒)
-
第二步:传感器释放总线、拉高电平,持续80μs(准备数据传输,作为数据帧起始缓冲)
若主机未检测到该80μs低电平应答,直接判定通信失败,大概率是接线错误、上拉电阻缺失、传感器未上电或采样间隔不足1s。
4. 40bit数据逐位传输(核心编码规则)
握手完成后,传感器连续输出40bit(5字节)数据,包含湿度整数、湿度小数、温度整数、温度小数、校验和。每一位数据的起始格式完全一致,通过高电平持续时长区分逻辑0和逻辑1,无例外:
每位数据固定前置:总线先拉低50μs(位同步信号,用于主机对齐时序)

-
逻辑0编码:50μs低电平结束后,总线拉高26μs ~ 28μs,随后立即拉低,代表当前位数据为0

-
逻辑1编码:50μs低电平结束后,总线拉高70μs左右,随后立即拉低,代表当前位数据为1
核心区分要点:低电平时长固定50μs,高电平短为0、高电平长为1,微秒级时长差异是数据解码的唯一依据。
5. 40bit数据帧固定格式(不可更改)
5字节共40bit,顺序固定,DHT11硬件限制湿度、温度小数位恒为0:
-
Byte1:湿度整数数据(有效测量数值)
-
Byte2:湿度小数数据(固定为0,无精度)
-
Byte3:温度整数数据(有效测量数值)
-
Byte4:温度小数数据(固定为0,无精度)
-
Byte5:校验和(校验和 = Byte1+Byte2+Byte3+Byte4)
主机接收完成后,必须进行校验计算,校验一致则数据有效,不一致判定为传输干扰、数据作废。
四、程序源码(STM32标准库)
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus;//设置定时器重装值
SysTick->VAL = 0x00;//清空当前计数值
SysTick->CTRL = 0x00000005;//设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000));//等待计数到0
SysTick->CTRL = 0x00000004;//关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms–)
{
Delay_us(1000);
}
}
#define DHT_GPIO GPIOB
#define DHT_PIN GPIO_Pin_5 /*引脚定义*/
//主机机模式主机发送总线电平
void DHT11_SendBit(uint8_t bitvalue)
{
GPIO_WriteBit(DHT_GPIO,DHT_PIN,(BitAction)bitvalue);
}
//从机模式主机读取总线电平
uint8_t DHT11_ReBit(void)
{
uint8_t bitvalue;
bitvalue=GPIO_ReadInputDataBit(DHT_GPIO,DHT_PIN);
return bitvalue;
}
//主机模式,即推挽输出
void DHT11_Out(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=DHT_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(DHT_GPIO,&GPIO_InitStructure);
GPIO_WriteBit(DHT_GPIO,DHT_PIN,Bit_SET);
}
//从机模式,上拉输入
void DHT11_Int(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=DHT_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(DHT_GPIO,&GPIO_InitStructure);
}
/*DHT11复位,退出待机模式*/
uint8_t DHT11_Ready(void)
{
uint16_t i;
DHT11_Out();//主机输出模式
DHT11_SendBit(0);
Delay_ms(20);//总线拉低至少18ms
DHT11_SendBit(1);
Delay_us(30);//总线拉高20—40us
DHT11_Int();//从机输入模式
Delay_us(10);//稍微延时,防止未受到响应
if(DHT11_ReBit()==1)//是否受到响应信号
return 0;//如果没有受到从机输入的低电平,直接退出
i=7200;
while(DHT11_ReBit()==0)//等待总线被从机拉高
{
i–;
if(i==0)
return 0;//超时退出,防止死循环
}
i=7200;
while(DHT11_ReBit()==1)//等待总线被从机拉低,即响应完成,1bit开始信号
{
i–;
if(i==0)//超时退出,防止死循环
return 0;
}
return 1;
}
/*读取一字节数据*/
uint8_t DHT11_ReByte(void)
{
uint8_t i;
uint16_t count;
uint8_t Data=0;
for(i=0;i<8;i++)
{
count=7200;
while(DHT11_ReBit()==0)//等待总线被从机拉高,从机发送bit位
{
count–;
if(count==0)
return 0;
}
Delay_us(40);//延时40us后判断总线是否为高电平,如果传输的数据是0,那么40us后,从机会进入下一个bit开始信号,此时总线0
if(DHT11_ReBit())
{
Data|=(0x80>>i);
}
count=7200;
while(DHT11_ReBit()==1)
{
count–;
if(count==0)
return 0;
}
}
return Data;
}
/*读取40bit*/
void DHT_ReadDate(uint8_t* R_H,uint8_t* R_L,uint8_t* T_H,uint8_t* T_L)
{
uint8_t ready=0;
uint8_t klaok;
ready=DHT11_Ready();
if(ready==1)//成功受到响应信号,开始读取数据
{
*R_H=DHT11_ReByte();
*R_L=DHT11_ReByte();
*T_H=DHT11_ReByte();
*T_L=DHT11_ReByte();
klaok=DHT11_ReByte();
Delay_us(20);
if((*R_H+*R_L+*T_H+*T_L)==klaok)
{
//Printf("读取成功")
}
else
{
//Printf("读取失败")
}
}
}






