大家生活中一定经常使用温湿度数据,比如:天气预报、智能家居、智慧大屏等等。这些数据可以通过温湿度传感器进行获取。在嵌入式开发中,温湿度传感器是一种十分常用的传感器。本文将为大家介绍温湿度传感器 DHT11,内容包含模块介绍、工作原理、驱动方法,并提供编程实战示例。

1. 源码下载及前置阅读

本文首发 良许嵌入式网https://www.lxlinux.net/e/ ,欢迎关注!

本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):

https://www.lxlinux.net/e/stm32/dht11-tutorial.html

如果不知道如何搭建 STM32 编程环境,不知道如何烧录 STM32 代码,可以阅读这篇文章:

https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginne...

如果你连代码都不知道怎么烧录到 STM32 的,可以参考下文,提供了 5 种代码烧录方式:

https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to...

文中所使用的芯片是 STM32F103C8T6 ,配套了一个工程模板,如果你需要自己搭建一个工程模板,可以参考下文:

https://www.lxlinux.net/e/stm32/create-stm32-hal-project-temp...

2. DHT11介绍

DHT11(数字温湿度传感器)为 3 或 4 针单排引脚封装,连接方便。具有品质卓越、超快响应、抗干扰能力强、性价比极高、超小的体积、极低的功耗的优点,使其成为在测温、测湿应用,在苛刻应用场合的一个非常不错的选择。

DHT11 内置一个电阻式感湿元件和一个 NTC 测温元件,并与一个单片机相连接(DHT11 内部)。每个 DHT11 都在极为精确的湿度校验室中进行校准,校准系数以程序的形式存在传感器中,传感器内部在检测信号的处理过程中要调用这些校准系数。DHT11 采用简易快捷的单线制串行接口,方便系统集成。

2.1 DHT11型号介绍

DHT11 有 3 脚和 4 脚两款,在使用上没有差别,接线都一样,主要接三根,四脚的款式有一脚悬空。四脚款接杜邦线会有点不稳,只能插面包板上,建议直接买三脚的。

<img src="https://lxlinux.superbed.verylink.top/item/655b12b2c458853aef4b8a5b.jpg" style="zoom: 67%;" />

同样可以测量温湿度的还有 DHT20、DHT22 等,都是大同小异。

DHT11 虽然可以同时测量温湿度,但是测量范围是打不过专业测温传感器的,比如 ds18b20 测量的温度范围就有 -55°C ~ 125°C,而 DHT11 只有 0~50℃。

2.2 DHT11工作参数及引脚介绍

DHT11 工作参数:

  1. 湿度测量范围:20~90%RH
  2. 湿度测量精度:±5%RH
  3. 温度测量范围:0~50℃
  4. 温度测量精度:±2℃
  5. 工作电压:DC 3.3V/5V

接线如下,别把正负极接反啦,接反会烧坏掉的。

DHT11STM32
VCC3.3/5V
DATA任意一个GPIO口
GNDGND

DHT11 采用单总线协议,也就是使用一根 DATA 线进行数据的收发。DHT11 的 DATA 线一次通讯时间 4ms 左右,数据分整数部分、小数部分和校验位,具体为: 8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位。

3. DHT11工作原理

3.1 正常工作验证

上电后,「电源指示灯/POWER」红灯亮,表示上电成功,正常工作。

3.2 DHT11工作时序

3.2.1 整体工作时序

DHT11 整体工作时序为:主机发送开始信号、DHT11 响应输出、主机接收 40bit 数据(湿度数据+温度数据+校验值),结束信号(可选)。具体过程如下:

  1. 总线空闲状态为高电平,主机拉低总线等待 DHT11 响应, 主机把总线拉低必须大于 18ms,保证 DHT11 能检测到起始信号;
  2. 主机发送开始信号结束后,拉高总线电平并延时等待 20-40us 后,读取 DHT11 的响应信号;
  3. DHT11 接收到主机的开始信号后,等待微处理器开始信号结束,发送 80us 低电平响应信号;
  4. DHT11 发送 80us 高电平准备发送数据;
  5. DHT11 发送 40bit 数据(湿度数据+温度数据+校验值)。

过程主机DHT11
1拉低>18ms
2拉高20~40us
3 响应 80us 低电平
4 拉高 80us
5 发送 40bit 数据(湿度数据+温度数据+校验值)

3.2.2 起始及响应信号

总流程讲完介绍一下细分流程:

首先主机拉低总线至少 18ms,然后再拉高总线,延时 20~40us,此时起始信号(有时也叫复位信号)发送完毕。

DHT11 检测到复位信号后,触发一次采样,并拉低总线 80us 表示响应信号,告诉主机数据已经准备好了。DHT11 之后拉高总线 80us,然后开始传输数据。如果检测到响应信号为高电平,则 DHT11 初始化失败,请检查线路是否连接正常。

3.2.3 读时序

DHT11 开始传输数据。每 1bit 数据都以 50us 低电平开始,告诉主机开始传输一位数据了。DHT11 以高电平的长短定义数据位是 0 还是 1:当 50us 低电平过后拉高总线,高电平持续 26~28us 表示 0,高电平持续 70us 表示数据 1。

当最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,表示数据传输完毕,随后总线由上拉电阻拉高进入空闲状态。

位数据0表示方式:

以 50us 低电平开始,高电平持续 26~28us 表示 0。

位数据1表示方式:

以 50us 低电平开始,高电平持续 70us 表示 1。

3.3 DHT11数据格式

DHT11 的 DATA 传输一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输。

数据格式为:8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位,一共 5 字节(40bit)数据。

正常情况下,前四个字节的和刚好与校验位相等,通过这种机制可以保证数据传输的准确性。

4. 编程实战

4.1 硬件接线

本教程使用的硬件如下:

  • 单片机:STM32F103C8T6
  • 温湿度传感器:DHT11
  • 串口:USB 转 TTL
  • 烧录器:ST-LINK V2
DHT11STM32USB 转 TTL
VCC3.3/5V
DATA8
GNDG
A10TX
A9RX
GGND

我们使用 A8 作为 DHT11 的数据引脚,串口 1 进行 log 输出。

接线如下图:

4.2 加载DHT11模块

我们在模板工程里的 BSP 目录下创建一个 dht11 目录,然后创建 dht11.c 及 dht11.h 两个空文件。

打开工程,跟着我的贪吃蛇点点点:)

4.3 微秒级延时实现

DHT11 对时序要求严格,需要微妙级延时,我们常用的 HAL_Delay() 是毫秒级时延。实现微秒级延时的方法有很多,但是我们可以直接用模板工程中 delay 文件的 delay_us

void delay_us(uint32_t nus)
{
    uint32_t temp;
    SysTick->LOAD = nus * g_fac_us; /* 时间加载 */
    SysTick->VAL = 0x00;            /* 清空计数器 */
    SysTick->CTRL |= 1 << 0 ;       /* 开始倒数 */

    do
    {
        temp = SysTick->CTRL;
    } while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */

    SysTick->CTRL &= ~(1 << 0) ;    /* 关闭SYSTICK */
    SysTick->VAL = 0X00;            /* 清空计数器 */
}

4.4 DATA引脚配置

DHT11 采用单总线协议与单片机通信,有时作为输入有时作为输出,所以我们需要在 DATA 引脚上配置输入和输出。

void DHT_GPIO_INPUT(void) 
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.Pin=DHT11_PIN;
    GPIO_InitStructure.Mode=GPIO_MODE_INPUT;
    GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DHT11_IO,&GPIO_InitStructure);
}

void DHT_GPIO_OUTPUT(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.Pin=DHT11_PIN;
    GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DHT11_IO,&GPIO_InitStructure);
}

4.5 起始及响应信号实现

按照前面介绍的 DHT11 工作时序:

主机发送起始信号:先将总线设为输出模式,总线空闲状态为高电平,拉低总线至少 18ms,然后再拉高总线,延时 20~40us,此时复位信号发送完毕。

DHT11 发送响应信号:总线设为输入模式,使用 while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN)) 检测总线从高电平跳变到低电平,等待 DHT11 拉低总线 。DHT11 响应信号持续 80us,使用 while(!HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN)) 检测总线低电平跳变到高电平。之后 DHT11 拉高总线 80us ,使用 while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN)) 检测总线从高电平跳变到低电平,然后 DHT11 开始传输数据。

起始信号及响应信号代码如下:

void DHT11_Start()
{
    DHT_GPIO_OUTPUT();
    HAL_GPIO_WritePin(DHT11_IO, DHT11_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(DHT11_IO, DHT11_PIN, GPIO_PIN_RESET);
    HAL_Delay(20);                                               //拉低总线至少 18ms
    HAL_GPIO_WritePin(DHT11_IO, DHT11_PIN, GPIO_PIN_SET);
    
    DHT_GPIO_INPUT();

    while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN));               //上一步将总线设为高电平,等待DHT11响应低电平
    while(!HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN));              //上一步DHT11响应低电平,等待DHT11拉高总线
    while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN));               //上一步DHT11拉高了总线,等待DHT11拉低总线,开始传送数据
}

4.6 读取1字节数据

将 DHT11 发来的二进制数据存储到 ReadData 变量中,读取一位后,左移一位,循环8次,最终得到 1 byte 数据。

那么如何判断我们读到的数据是 0 还是 1 呢?

通过 3.2.3 的分析可以知道,0 和 1 的时序只是高电平持续时间不同,所以我们只需要在 DHT11 拉低电平之后延时 40~60 微秒(代码中使用 50 微秒),再读取电平状态就可以了,如果是高电平则为 1,低电平则为 0 。

uint8_t DHT_Read_Byte(void)  //从DHT11读取一位(8字节)信号
{
    uint8_t i;
    uint8_t ReadData = 0;    //ReadData用于存放8bit数据,即8个单次读取的1bit数据的组合
    uint8_t temp;            //临时存放信号电平(0或1)
    
    for(i=0;i<8;i++){
        while(!HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN));
        Delay_us(50);
        if(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN) == 1){
            temp = 1;
            while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN));
        }else{
            temp = 0;
        } 
        ReadData = ReadData << 1;
        ReadData |= temp;
    }
    return ReadData;
}

4.7 一次数据读取及显示

根据 3.2 的时序,我们就可以使用代码实现 DHT11 一次读取数据过程。

注意:DHT11 读取数据间隔至少为 2 秒,否则读取到的数据可能不稳定,所以在最后可以延时 2 秒。

void DHT_Read()
{
    uint8_t i;
    
    DHT11_Start();
    DHT_GPIO_INPUT();
    
    for(i= 0;i < 5;i++){
        Data[i] = DHT_Read_Byte();
    }
    if((Data[0]+Data[1]+Data[2]+Data[3])==Data[4])
    {
        printf("湿度: %d.%dRH ,", Data[0], Data[1]);
        printf("温度: %d.%d℃\r\n", Data[2], Data[3]);
    }else{
        printf("ERROR DATA\r\n");
    }
    HAL_Delay(2000);
}

dht11.h文件内容如下:

#ifndef __DHT11_H__
#define __DHT11_H__

#include "stdio.h"
#include "stm32f1xx.h"
 
#define DHT11_IO         GPIOA
#define DHT11_PIN        GPIO_PIN_8

void DHT_Read(void);
    
#endif

4.8 最终效果

5. 小结

通过本文的学习与实践,相信大家已经了解并掌握 DHT11 的特性和使用,能够更好地应用于嵌入式开发。无论是构建智能家居系统还是开发物联网设备,DHT11 都可以成为您的得力助手,让我们一起玩转 DHT11,love and peace!


另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。

刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

推荐阅读:

欢迎关注我的博客:良许嵌入式教程网,满满都是干货!


良许
1k 声望1.8k 粉丝