欢迎光临
我们一直在努力

如何在STM32中使用DHT11(含详细代码)

一、基本概述

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 引脚模块(主流)

  • VCC:电源正极(3.3V/5V)
  • DATA:单总线数据引脚(必须外接4.7kΩ 上拉电阻到 VCC)(短距离用上拉输入模式同样可以)
  • GND:电源地
  • 4 引脚直插模块

  • VCC、2.DATA、3.NC(空脚,悬空)、4.GND
  • 四、单总线通信时序(官方标准时序)

    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("读取失败")
    }
    }
    }

    赞(0)
    未经允许不得转载:171主机测评 » 如何在STM32中使用DHT11(含详细代码)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址