DHT11 单总线通信的核心是严格的时序约定 ,所有数据传输都基于主机与从机(DHT11)的电平交互时序,分为三大阶段:主机起始信号→DHT11 响应信号→数据传输时序,具体细节如下:
一、主机起始信号(主机→DHT11)
- 主机将总线(GPIO)设为输出模式,拉低电平(0V),持续时间≥18ms(必须满足,否则 DHT11 不响应);
- 主机释放总线(拉高电平),并将 GPIO 切换为输入模式,等待 DHT11 的响应(此阶段总线由上拉电阻拉高);
- 主机释放总线后,需等待 20~40us,让 DHT11 检测到起始信号。
二、DHT11 响应信号(DHT11→主机)
- DHT11 检测到起始信号后,主动拉低总线电平,持续 80us(告知主机 "已准备好");
- DHT11 再拉高总线电平,持续 80us(告知主机 "即将传输数据");
- 主机需检测到这 "80us 低 + 80us 高" 的电平变化,才算响应成功,否则判定通信失败。
三、数据传输时序(DHT11→主机)
- DHT11 响应后,连续传输 40bit 数据(高位在前),顺序为:湿度整数位 (8bit)→湿度小数位 (8bit,DHT11 恒为 0)→温度整数位 (8bit)→温度小数位 (8bit,DHT11 恒为 0)→校验位 (8bit);
- 每 1bit 数据的时序定义 (核心):
- 每个 bit 以 50us 低电平开头(DHT11 拉低总线,作为 bit 起始标记);
- 低电平结束后,DHT11 拉高总线:
- 若高电平持续 26~28us → 表示该 bit 为0;
- 若高电平持续 70us → 表示该 bit 为1;
- 主机通过检测 "低电平后高电平的持续时间",判断每一位是 0 还是 1,逐位读取 40bit 后完成数据传输。
四、时序关键参数(精准到 us)
| 阶段 | 电平状态 | 持续时间 | 发起方 |
|---|---|---|---|
| 主机起始拉低 | 低电平 | ≥18ms | 主机 |
| 主机释放总线 | 高电平 | 20~40us | 主机 |
| DHT11 响应拉低 | 低电平 | 80us(±5us) | DHT11 |
| DHT11 响应拉高 | 高电平 | 80us(±5us) | DHT11 |
| 单个 bit 起始低电平 | 低电平 | 50us(±5us) | DHT11 |
| bit=0 的高电平 | 高电平 | 26~28us | DHT11 |
| bit=1 的高电平 | 高电平 | 70us(±5us) | DHT11 |
五、关键注意点
- 时序精度要求极高:us 级延时误差(如 ±10us)会导致数据读取错误,因此主机的延时函数(delay_us)必须精准(建议用示波器校准);
- 总线上拉:DHT11 数据引脚需外接 4.7KΩ 上拉电阻,否则释放总线后无法拉高,导致通信失败;
- 采样间隔:DHT11 两次数据传输间隔必须≥1s,频繁读取会导致模块无响应。
DHT11 与 STM32 通信采用单总线协议(1-Wire),无需额外外设,仅通过一根 GPIO 线完成双向数据传输,核心是严格遵循时序要求实现数据读写。
一、DHT11 通信时序(关键)
- 主机发起起始信号 :
- 拉低总线(GPIO 输出低)至少 18ms,然后释放总线(GPIO 设为输入),等待 DHT11 响应。
- DHT11 响应信号 :
- DHT11 检测到起始信号后,拉低总线 80us,再拉高总线 80us,主机检测到这一高低电平后,准备接收数据。
- 数据传输 :
- DHT11 输出 40bit 数据(高位在前):湿度整数位 (8bit) + 湿度小数位 (8bit,DHT11 恒为 0) + 温度整数位 (8bit) + 温度小数位 (8bit,DHT11 恒为 0) + 校验位 (8bit,前 4 字节之和的低 8 位)。
- 每 bit 数据的表示:低电平 50us + 高电平 26~28us → 表示 0;低电平 50us + 高电平 70us → 表示 1。
二、STM32 驱动 DHT11 核心代码(寄存器版,以 PA0 为例)
1. 引脚初始化
#include "stm32f10x.h"
#include "delay.h" // 需自行实现us级延时函数
// 定义DHT11引脚
#define DHT11_PIN GPIO_Pin_0
#define DHT11_PORT GPIOA
#define DHT11_RCC RCC_APB2Periph_GPIOA
// 引脚输出/输入模式切换
#define DHT11_OUT() {GPIOA->CRL &= ~(0x0F<<0); GPIOA->CRL |= (0x03<<0);} // 推挽输出
#define DHT11_IN() {GPIOA->CRL &= ~(0x0F<<0); GPIOA->CRL |= (0x08<<0);} // 浮空输入
#define DHT11_HIGH() GPIOA->BSRR = DHT11_PIN // 拉高
#define DHT11_LOW() GPIOA->BRR = DHT11_PIN // 拉低
#define DHT11_READ() (GPIOA->IDR & DHT11_PIN) // 读取引脚电平
// DHT11初始化(仅初始化引脚为输出高)
void DHT11_Init(void)
{
RCC_APB2PeriphClockCmd(DHT11_RCC, ENABLE);
DHT11_OUT();
DHT11_HIGH();
delay_ms(100); // 等待DHT11稳定
}
2. 读取一次 DHT11 数据
// 等待引脚电平(返回0:超时,1:成功)
u8 DHT11_Wait_Level(u8 level, u32 timeout)
{
u32 t = 0;
while((DHT11_READ() == level) && (t < timeout))
{
t++;
delay_us(1);
}
return (t < timeout) ? 1 : 0;
}
// 读取DHT11数据(参数:humidity-湿度,temperature-温度;返回0:成功,1:失败)
u8 DHT11_Read_Data(u8 *humidity, u8 *temperature)
{
u8 buf[5] = {0}; // 存储40bit数据(5字节)
u8 i, j;
// 1. 主机发送起始信号
DHT11_OUT();
DHT11_LOW();
delay_ms(20); // 拉低≥18ms
DHT11_HIGH();
delay_us(30); // 释放总线后等待30us
DHT11_IN(); // 切换为输入模式
// 2. 等待DHT11响应(拉低80us)
if(!DHT11_Wait_Level(0, 100)) return 1; // 等待低电平超时
if(!DHT11_Wait_Level(1, 100)) return 1; // 等待高电平超时
// 3. 接收40bit数据
for(i = 0; i < 5; i++) // 5字节
{
for(j = 0; j < 8; j++) // 每字节8bit
{
// 等待低电平结束(50us)
if(!DHT11_Wait_Level(0, 70)) return 1;
// 延时40us后检测高电平持续时间
delay_us(40);
buf[i] <<= 1;
if(DHT11_READ()) buf[i] |= 1; // 高电平≥40us → 表示1
// 等待高电平结束
if(!DHT11_Wait_Level(1, 70)) return 1;
}
}
// 4. 校验数据
if(buf[4] != (buf[0] + buf[1] + buf[2] + buf[3])) return 1;
// 5. 提取有效数据(DHT11小数位为0,直接取整数位)
*humidity = buf[0];
*temperature = buf[2];
return 0;
}
3. 主函数调用示例
int main(void)
{
u8 humi, temp;
SystemInit(); // 初始化系统时钟(72MHz)
delay_init(); // 初始化延时函数
DHT11_Init(); // 初始化DHT11
while(1)
{
if(DHT11_Read_Data(&humi, &temp) == 0)
{
// 读取成功,humi为湿度值(0~95),temp为温度值(-20~60)
}
delay_ms(1000); // DHT11采样周期≥1s
}
}
三、关键注意事项
- 延时精度 :单总线协议对 us 级延时要求极高,必须确保
delay_us()函数精准(可通过示波器校准),否则会导致数据读取失败。 - 引脚配置:通信过程中需频繁切换 GPIO 输入 / 输出模式,寄存器操作比库函数更高效,减少延时误差。
- 校验机制:必须校验最后 1 字节的校验位,避免读取到错误数据(如环境干扰导致的位翻转)。
- 采样周期:DHT11 两次采样间隔必须≥1s,不可频繁读取,否则模块无响应。
- 硬件上拉:建议在 DHT11 数据引脚与 VCC 之间接 4.7KΩ 上拉电阻,增强总线驱动能力,减少干扰。
四、面试回答逻辑(分两步)
- 通信原理:先说明 DHT11 采用单总线协议,无需时钟线,仅通过一根 GPIO 完成通信,再简述 "主机起始信号→从机响应→数据传输" 的时序流程,强调每 bit 数据的高低电平表示规则。
- 代码实现:核心分为引脚初始化(输入 / 输出模式切换)、起始信号发送、响应信号检测、40bit 数据读取、校验位验证五部分,重点说明数据读取时如何通过延时判断 bit 值(0/1),以及校验的必要性。
传输数据的核心代码就是读取 40bit 数据的部分(包含起始信号、响应检测、逐位读取逻辑),我把这部分代码单独提炼出来,并标注关键细节,你可以直接参考:
// 核心:读取DHT11的40bit数据(传输数据的核心逻辑)
u8 DHT11_Read_Data(u8 *humidity, u8 *temperature)
{
u8 buf[5] = {0}; // 存储40bit数据(5字节:湿度整、湿小、温度整、温小、校验)
u8 i, j;
// 1. 主机发送起始信号(触发DHT11准备传输数据)
DHT11_OUT(); // 引脚设为输出模式
DHT11_LOW(); // 拉低总线
delay_ms(20); // 拉低≥18ms,满足DHT11起始信号要求
DHT11_HIGH(); // 释放总线(拉高)
delay_us(30); // 等待30us,让DHT11检测起始信号
DHT11_IN(); // 切换为输入模式,准备接收DHT11的响应和数据
// 2. 等待DHT11的响应信号(确认DHT11就绪,开始传输数据)
if(!DHT11_Wait_Level(0, 100)) return 1; // 等待DHT11拉低总线(80us),超时则失败
if(!DHT11_Wait_Level(1, 100)) return 1; // 等待DHT11拉高总线(80us),超时则失败
// 3. 逐位读取40bit数据(DHT11主动传输数据,主机接收)
for(i = 0; i < 5; i++) // 读取5个字节(40bit)
{
for(j = 0; j < 8; j++) // 每个字节8bit,高位先传
{
// 等待DHT11拉低总线(每个bit的起始低电平,约50us)
if(!DHT11_Wait_Level(0, 70)) return 1;
// 延时40us后检测高电平持续时间:
// - 高电平26~28us → 0;高电平70us → 1
delay_us(40);
buf[i] <<= 1; // 左移,准备存储当前bit
if(DHT11_READ()) // 检测当前引脚电平
{
buf[i] |= 1; // 高电平≥40us → 该bit为1
}
// 等待该bit的高电平结束,进入下一个bit的读取
if(!DHT11_Wait_Level(1, 70)) return 1;
}
}
// 4. 校验数据(确保传输过程无错误)
if(buf[4] != (buf[0] + buf[1] + buf[2] + buf[3]))
{
return 1; // 校验失败,说明数据传输出错
}
// 5. 提取有效数据(DHT11小数位恒为0,仅取整数位)
*humidity = buf[0]; // 湿度整数
*temperature = buf[2]; // 温度整数
return 0; // 数据传输成功
}
// 辅助函数:等待引脚达到指定电平,超时返回0(确保数据传输时序准确)
u8 DHT11_Wait_Level(u8 level, u32 timeout)
{
u32 t = 0;
// 循环等待,直到引脚电平等于目标电平,或超时
while((DHT11_READ() == level) && (t < timeout))
{
t++;
delay_us(1); // 1us计数,精准控制等待时间
}
return (t < timeout) ? 1 : 0; // 1=成功等待到目标电平,0=超时
}
关键说明:
- 数据传输的核心是DHT11 主动发送 40bit 数据,主机(STM32)通过检测高低电平的持续时间来识别每一位是 0 还是 1;
DHT11_Wait_Level是保障时序的关键,确保主机严格同步 DHT11 的传输节奏;- 整个传输流程必须严格遵循 DHT11 的单总线时序,哪怕 us 级的延时误差都可能导致数据传输失败。