手把手教你玩转DHT11(原理+驱动)
大家生活中一定经常使用温湿度数据,比如:天气预报、智能家居、智慧大屏等等。这些数据可以通过温湿度传感器进行获取。在嵌入式开发中,温湿度传感器是一种十分常用的传感器。本文将为大家介绍温湿度传感器 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-beginner.html
如果你连代码都不知道怎么烧录到 STM32 的,可以参考下文,提供了 5 种代码烧录方式:
https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html
文中所使用的芯片是 STM32F103C8T6 ,配套了一个工程模板,如果你需要自己搭建一个工程模板,可以参考下文:
https://www.lxlinux.net/e/stm32/create-stm32-hal-project-template.html
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 工作参数:
- 湿度测量范围:20~90%RH
- 湿度测量精度:±5%RH
- 温度测量范围:0~50℃
- 温度测量精度:±2℃
- 工作电压: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 数据(湿度数据+温度数据+校验值),结束信号(可选)。具体过程如下:
-
总线空闲状态为高电平,主机拉低总线等待 DHT11 响应, 主机把总线拉低必须大于 18ms,保证 DHT11 能检测到起始信号;
-
主机发送开始信号结束后,拉高总线电平并延时等待 20-40us 后,读取 DHT11 的响应信号;
-
DHT11 接收到主机的开始信号后,等待微处理器开始信号结束,发送 80us 低电平响应信号;
-
DHT11 发送 80us 高电平准备发送数据;
-
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
。
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!