嵌入式面试题:DHT11温湿度模块是怎么传输数据到单片机的?你是怎么配置的?多少数据位?

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

五、关键注意点

  1. 时序精度要求极高:us 级延时误差(如 ±10us)会导致数据读取错误,因此主机的延时函数(delay_us)必须精准(建议用示波器校准);
  2. 总线上拉:DHT11 数据引脚需外接 4.7KΩ 上拉电阻,否则释放总线后无法拉高,导致通信失败;
  3. 采样间隔:DHT11 两次数据传输间隔必须≥1s,频繁读取会导致模块无响应。

DHT11 与 STM32 通信采用单总线协议(1-Wire),无需额外外设,仅通过一根 GPIO 线完成双向数据传输,核心是严格遵循时序要求实现数据读写。

一、DHT11 通信时序(关键)

  1. 主机发起起始信号
    • 拉低总线(GPIO 输出低)至少 18ms,然后释放总线(GPIO 设为输入),等待 DHT11 响应。
  2. DHT11 响应信号
    • DHT11 检测到起始信号后,拉低总线 80us,再拉高总线 80us,主机检测到这一高低电平后,准备接收数据。
  3. 数据传输
    • 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
    }
}

三、关键注意事项

  1. 延时精度 :单总线协议对 us 级延时要求极高,必须确保 delay_us() 函数精准(可通过示波器校准),否则会导致数据读取失败。
  2. 引脚配置:通信过程中需频繁切换 GPIO 输入 / 输出模式,寄存器操作比库函数更高效,减少延时误差。
  3. 校验机制:必须校验最后 1 字节的校验位,避免读取到错误数据(如环境干扰导致的位翻转)。
  4. 采样周期:DHT11 两次采样间隔必须≥1s,不可频繁读取,否则模块无响应。
  5. 硬件上拉:建议在 DHT11 数据引脚与 VCC 之间接 4.7KΩ 上拉电阻,增强总线驱动能力,减少干扰。

四、面试回答逻辑(分两步)

  1. 通信原理:先说明 DHT11 采用单总线协议,无需时钟线,仅通过一根 GPIO 完成通信,再简述 "主机起始信号→从机响应→数据传输" 的时序流程,强调每 bit 数据的高低电平表示规则。
  2. 代码实现:核心分为引脚初始化(输入 / 输出模式切换)、起始信号发送、响应信号检测、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 级的延时误差都可能导致数据传输失败。
相关推荐
bubiyoushang8884 小时前
基于STM32F103与A3988驱动芯片的两相四线步进电机控制方案
stm32·单片机·嵌入式硬件
bai5459368 小时前
STM32 备份寄存器
stm32·单片机·嵌入式硬件
cold_Mirac8 小时前
stm32-freertos和逻辑编程下堆栈功能的区分
stm32·单片机·嵌入式硬件
youcans_8 小时前
【动手学STM32G4】(3)上位机实时显示多路波形
stm32·单片机·嵌入式硬件·上位机
铁手飞鹰9 小时前
[HAL库分析—GPIO]
c语言·stm32·单片机·嵌入式硬件
徐某人..9 小时前
网络编程学习--第一天
arm开发·单片机·学习·arm
yrx02030710 小时前
STM32F103通过L298N驱动两相4线步进电机【42步进电机】
stm32·单片机·嵌入式硬件·步进电机
是大强10 小时前
3d打印材料asa和abs区别
嵌入式硬件
周周记笔记10 小时前
LC项目实战一:原理图DRC(二)
嵌入式硬件·pcb
安当加密10 小时前
基于 SLA 的操作系统双因素安全登录:USB Key 与 OTP 动态口令实践
单片机·嵌入式硬件·安全