手把手教你玩转DHT11(原理+驱动)

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

1. 源码下载及前置阅读

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

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

www.lxlinux.net/e/stm32/dht...

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

www.lxlinux.net/e/stm32/stm...

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

www.lxlinux.net/e/stm32/fiv...

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

www.lxlinux.net/e/stm32/cre...

2. DHT11介绍

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

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

2.1 DHT11型号介绍

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

同样可以测量温湿度的还有 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

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

DHT11 STM32
VCC 3.3/5V
DATA 任意一个GPIO口
GND GND

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

DHT11 STM32 USB 转 TTL
VCC 3.3/5V
DAT A8
GND G
A10 TX
A9 RX
G GND

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

接线如下图:

4.2 加载DHT11模块

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

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

4.3 微秒级延时实现

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

C 复制代码
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 引脚上配置输入和输出。

C 复制代码
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 开始传输数据。

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

C 复制代码
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 。

C 复制代码
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 秒。

C 复制代码
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文件内容如下:

C 复制代码
#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 随你挑!

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

推荐阅读:

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

相关推荐
正在努力的小河2 小时前
Linux设备树简介
linux·运维·服务器
荣光波比3 小时前
Linux(十一)——LVM磁盘配额整理
linux·运维·云计算
LLLLYYYRRRRRTT3 小时前
WordPress (LNMP 架构) 一键部署 Playbook
linux·架构·ansible·mariadb
轻松Ai享生活3 小时前
crash 进程分析流程图
linux
大路谈数字化5 小时前
Centos中内存CPU硬盘的查询
linux·运维·centos
luoqice5 小时前
linux下查看 UDP Server 端口的启用情况
linux
倔强的石头_6 小时前
【Linux指南】动静态库与链接机制:从原理到实践
linux
赏点剩饭7787 小时前
linux中的hostpath卷、nfs卷以及静态持久卷的区别
linux·运维·服务器
神鸟云7 小时前
DELL服务器 R系列 IPMI的配置
linux·运维·服务器·网络·边缘计算·pcdn
herderl7 小时前
**僵尸进程(Zombie Process)** 和**孤儿进程(Orphan Process)**
linux·运维·服务器·网络·网络协议