第三十三章DHT11数字温湿度传感器实验
本章,我们将介绍数字温湿度传感器DHT11的使用,与前一章的DS18B20温度传感器相比,该传感器不但能测温度,还能测湿度。我们将学习如何获取DHT11传感器的温湿度数据,并把数据通过串口打印出来。
本章分为如下几个小节:
33.1、DHT11简介;
33.2、硬件设计;
33.3、程序设计;
33.4、编译和测试;
33.1 DHT11简介
33.1.1 DHT11简介
DHT11是一款温湿度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。DHT11与单片机之间能采用简单的单总线进行通信,仅仅需要一个I/O口。传感器内部湿度和温度数据共有40 bit的数据一次性传给单片机,数据采用校验和方式进行校验,有效的保证数据传输的准确性。DHT11功耗很低,5V电源电压下,工作平均最大电流0.5mA。
DHT11的技术参数如下:
- 工作电压范围:3.3V ~ 5.5V
- 工作电流:平均0.5mA
- 输出:单总线数字信号
- 测量范围:湿度20~90%RH,温度0~50℃
- 精度:湿度±5%,温度±2℃
- 分辨率:湿度1%,温度1℃
DHT11的管脚排列如图43.1.1所示:
图33.1.1.1 DHT11管脚排列图
DHT11广泛应用于暖通空调、除湿器、农业、冷链仓储、测试及检测设备、消费品、
汽车、自动控制、数据记录器、气象站、家电、湿度调节器、医疗等相关温湿度检测控制领域。
33.1.2 DHT11工作时序简介
1. 单总线结构
DHT11器件采用单总线通信,只有一根数据线,系统中的数据交换、控制均由单总线完成。单总线接一个约4.7kΩ的上拉电阻,当总线空闲时,总线状态为高电平。关于单总线我们在前面的DS18B20章节已经讲解过,后面编写程序时注意的是,要配置总线占用的IO口为开漏输出模式。
2. 数据结构
单总线上的设备属于主从结构,只有主机呼叫从机时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主机。虽然DHT11与DS18B20类似,都是单总线访问,但是DHT11的访问,相对DS18B20来说简单很多。下面我们先来看看DHT11的数据结构。
单总线上一次传送5 byte(40 bit)的数据,且高位先发送,数据分小数部分和整数部分,DHT11的数据格式为:
8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验位。
其中校验和数据为前面四个字节相加。传感器数据输出的是未编码的二进制数据,数据(湿度、温度、整数、小数)之间应该分开处理。
(1)温度是正数的情况
例如,某次从DHT11读到的数据如下图所示:
byte4 byte3 byte2 byte1 byte0
00110101 00000000 00011000 00000100 01010001
湿度高8位 湿度低8位 温度高8位 温度低8位 校验位
整数 小数 整数 小数 校验位
根据以上的数据进行计算:
1)检查数据接收
00110101+00000000+00011000+00000100=01010001
计算结果和校验位相等,接收数据正确,如果计算得到的值和校验位不一致,那么本次接收的数据不正确,放弃,重新接收数据。
2)计算湿度和温度的值
湿度:
整数)=35H=53%RH
小数)=00H=0.0%RH
最后湿度=>53%RH+0.0%RH=53.0%RH
温度:
整数)=18H=24℃
小数)=04H=0.4℃
最后温度=>24℃+0.4℃=24.4℃
(2)温度是负数的情况
如果,当温度低于0℃时,温度数据的低8位的最高位置为1,计算时,该位可以视为0。如果温度是负数,校验位和湿度计算和上面的情况一样,这里说明一下温度计算,如-10.1℃表示为00001010 10000001,计算方法为:
0000 1010(整数)=0AH=10℃
00000001(小数)=01H=0.1℃
最后温度=>-(10℃+0.1℃)=-10.1℃
3. DHT11时序
DHT11上电后(DHT11上电后要等待1S以越过不稳定状态,在此期间不能发送任何指令),待1s过后,可以测试环境温湿度数据,DHT11的DATA数据线由上拉电阻拉高一直保持高电平,此时DHT11的DATA引脚处于输入状态,时刻检测外部信号。下面我们看看DHT11的时序:
1)复位信号和应答信号时序
首先主机发送开始信号(也叫复位信号),即:拉低数据线,保持t1(至少18ms,最大不得超过30ms)时间,然后主机拉高数据线t2(20~40us)时间,然后主机读取DHT11的响应,正常的话,DHT11会拉低数据线,并保持t3(40~50us)时间来作为响应信号,然后DHT11拉高数据线,保持t4(40~50us)时间后,DHT11开始输出数据。
图33.1.2.1 开始信号和应答信号
2)DHT11输出数字‘0’时序
DHT11每发送的1 bit 数据都以低电平开始,低电平延时时间约为12~14us,然后DHT11拉高总线,如果总线拉高26~28us,表示发送0,然后DHT11再将总线拉低12~14us,为下一次发送数据做准备。(注意,此时序图中,为按位发送。)
图33.1.2.2 DHT11输出数字‘0’
3)DHT11输出数字‘1’时序
如果DHT11拉低总线12~14us后,再将总线拉高116~118us,表示发送数字1,然后DHT11再将总线拉低12~14us,为下一次发送数据做准备。(注意,此时序图中,为按位发送。)
图33.1.2.3 DHT11输出数字‘1’
33.2硬件设计
1. 例程功能
把DHT11传感器插在预留的接口上,测试当前的环境温度和湿度,并将温度和湿度通过串口打印出来。实验中通过观察LED0闪烁提示程序在运行。预留的接口在开发板底板的位置如下:
图33.2.1 开发板硬件示意图
上图中的开发板预留接口的4个引脚从左到右依次为VCC、DQ、GND、GND,所以DHT11这样插入开发板(注意,不要插错!):
图33.2.2硬件连接示意图
2. 硬件资源
1)LED灯:LED0
2)UART4
3)DS18B20(接在PF2上)
LED0 | DHT11 | UART4_TX | UART4_RX |
PI0 | 接在PF2上 | PG11 | PB2 |
表33.2.1硬件资源
3. 原理图
如下图,DS18B20接在PF2上,我们通过该IO口模拟单总线的时序来控制DS18B20:
图33.2.3原理图部分
33.3 程序设计
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验22 DHT11数字温湿度传感器实验。
33.3.1 程序设计流程
关于GPIO的HAL库驱动以及寄存器我们在前面章节已经分析过,这里就不再重复讲解。下面,我们根据前面的时序分析总结出本节实验的控制过程:
1)初始化总线引脚
配置IO口为开漏输出、上拉和高速模式。
2)复位DHT11
1、主机先将总线拉低;
2、延时18ms(至少18ms);
3、主机释放总线(将总线拉高);
4、延时30us(延时20~40us)。
3)DHT11应答
1、DHT11将总线拉低;
2、延时40~80us(DHT11是通过发送40~80us的低信号来产生应答);
3、DHT11释放总线,上拉电阻将总线拉高;
4、延时40~80us。
4)DHT11输出(0或1)
1、等待DHT11将总线拉低(每位的传输开始都是以低电平0开始);
2、判断DHT11是输出0还是1,0的高电平持续时间约为26-28us,1的高电平持续时间约为116~118us;
5)温度转换
设置5个字节的Buffer,用于存放获取到的40位的数据,因为是且高位先发送,所以Buffer0和Buffer1存放的是湿度的整数和小数,Buffer2和Buffer3存放的是温度的整数和小数部分。
33.3.2 添加用户代码
DHT11驱动代码我们把它放在dht11.c和dht11.h文件中。首先我们先看一下dht11.c头文件里面的内容。
1. dht11.h文件代码
dht11.h文件代码如下:
#ifndef __DHT11_H
#define __DHT11_H
#include "./SYSTEM/sys/sys.h"
/* DHT11 引脚 定义 */
#define DHT11_DQ_GPIO_PORT GPIOF
#define DHT11_DQ_GPIO_PIN GPIO_PIN_2
/* PF口时钟使能 */
#define DHT11_DQ_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)
/* IO操作函数 */
#define DHT11_DQ_OUT(x) do{ x ? \
HAL_GPIO_WritePin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN, GPIO_PIN_SET) : \ HAL_GPIO_WritePin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN, GPIO_PIN_RESET); \ }while(0) /* 数据端口输出 */
/* 数据端口输入 */
#define DHT11_DQ_IN HAL_GPIO_ReadPin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN)
uint8_t dht11_init(void); /* 初始化DHT11 */
uint8_t dht11_check(void); /* 检测是否存在DHT11 */
uint8_t dht11_read_data(uint8_t *temp,uint8_t *humi); /* 读取温湿度 */
#endif
以上主要是对DHT11的相关引脚、时钟使能和IO操作函数进行宏定义,方便程序中调用。
2. dht11.c文件代码
根据前面对DHT11的时序分析,我们按照时序部分进行程序设计,首先是复位DHT11函数,如下:
(1)复位DHT11
/**
* @brief复位DHT11
* @param无
* @retval无
*/
static void dht11_reset(void)
{
DHT11_DQ_OUT(0); /* 拉低DQ */
delay_ms(20); /* 拉低至少18ms */
DHT11_DQ_OUT(1); /* DQ=1 */
delay_us(30); /* 主机拉高20~40us */
}
(2)等待DHT11应答
/**
* @brief等待DHT11的回应
* @param无
* @retval正常
* 1, DHT11异常/不存在
*/
uint8_t dht11_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DHT11_DQ_IN && retry < 100) /* DHT11会拉低40~80us */
{
retry++;
delay_us(1);
}
if (retry >= 100) /* 超时,DHT11异常 */
{
rval = 1;
}
else
{
retry = 0;
while (!DHT11_DQ_IN && retry < 100) /* DHT11拉低后会再次拉高40~80us */
{
retry++;
delay_us(1);
}
if (retry >= 100) rval = 1; /* 超时,DHT11异常 */
}
return rval;
}
应答信号也是按照前面的时序图进行编写的,首先DHT11将总线拉低,所以程序先等待总线变为低电平,这里在测试引脚电平的时候,设置最大的检测时间是100us,如果超过100us内电平没有变化,则认为DHT11异常。如果DHT11拉低总线后,再释放总线,则总线为高电平,所以,在测试总线拉低以后,再检测总线是否被拉高。
(3)从DHT11读取一个位
和DS18B20不一样,DHT11不需要写的过程,直接可以读取温湿度数据:
1 /**
2 * @brief从DHT11读取一个位
3 * @param无
4 * @retval读取到的位值: 0 / 1
5 */
6 uint8_t dht11_read_bit(void)
7 {
8 uint8_t retry = 0;
9 while (DHT11_DQ_IN && retry < 100) /* 等待变为低电平 */
10 {
11 retry++;
12 delay_us(1);
13 }
14 retry = 0;
15 while (!DHT11_DQ_IN && retry < 100) /* 等待变高电平 */
16 {
17 retry++;
18 delay_us(1);
19 }
20 delay_us(40); /* 等待40us */
21 if (DHT11_DQ_IN) /* 根据引脚状态返回 bit */
22 {
23 return 1;
24 }
25 else
26 {
27 return 0;
28 }
29 }
DHT11输出数字0或者1都是先以低电平开始的,所以第9~13行在等待DHT11变为低电平,同样,测试的最大时间为100us。DHT11通过拉高总线发送数字0或1,第15~19行在等待总线拉高,总线拉高后,延时40us后检查DHT11的IO的状态,如果此时引脚为高电平,则认为从DHT11读取到1,反之则从DHT11读取到0。
(4)从DHT11读取一个字节
读取一个字节需要对读取一个位循环8次,如下:
/**
* @brief从DHT11读取一个字节
* @param无
* @retval读到的数据
*/
static uint8_t dht11_read_byte(void)
{
uint8_t i, data = 0;
for (i = 0; i < 8; i++) /* 循环读取8位数据 */
{
data <<= 1; /* 高位数据先输出, 先左移一位 */
data |= dht11_read_bit(); /* 读取1bit数据 */
}
return data;
}
(5)从DHT11读取一次数据
DHT11的数据组成我们前面已经分析过了,40位(5个字节)数据中,高位先发送,我们设置5和Buffer用于存储这5个字节的数据,所以接收到的数据中,Buffer4是检验位,Buffer3和Buffer2分别是温度的小数和整数部分,而Buffer1和Buffer0分别是湿度的小数和整数部分。 我们先获取到这5个字节的数据,然后先通过校验位检查接收到的数据是否正确,如果正确的话,通过指针的方式指向温度和湿度的整数位。
/**
* @brief从DHT11读取一次数据
* @param温度值(范围:0~50°)
* @param湿度值(范围:20%~90%)
* @retval正常.
失败
*/
uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5];
uint8_t i;
dht11_reset();
if (dht11_check() == 0)
{
for (i = 0; i < 5; i++) /* 读取40位数据 */
{
buf[i] = dht11_read_byte();/* 读到的值存在buf[i]中 */
}
/* 通过校验位检查读取到的数据是否正确 */
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
*humi = buf[0]; /* 湿度整数和小数分别在buf[0]和buf[1]中 */
*temp = buf[2]; /* 温度整数和小数分别在buf[2]和buf[3]中 */
}
}
else /* DHT11没有应答 */
{
return 1;
}
return 0;
}
(6)初始化DHT11的IO口
最后,不要忘了要初始化IO口才可以使用IO口,如下是DHT11的IO口初始化函数,配置IO口为开漏输出、上拉、高速模式,单总线中一定要配置总线为开漏输出,这点我们在前面部分已经分析过。
/**
* @brief初始化DHT11的IO口 DQ 同时检测DHT11的存在
* @param无
* @retval正常
不存在/不正常
*/
uint8_t dht11_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
DHT11_DQ_GPIO_CLK_ENABLE(); /* 开启DQ引脚时钟 */
gpio_init_struct.Pin = DHT11_DQ_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
/* 初始化DHT11_DQ引脚 */
HAL_GPIO_Init(DHT11_DQ_GPIO_PORT, &gpio_init_struct);
/* DHT11_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */
dht11_reset(); /* 复位DHT11 */
return dht11_check(); /* 等待DHT11的回应 */
}
3. main.c文件代码
main.c文件的部分代码如下:
int main(void)
{
uint8_t t = 0;
uint8_t temperature;
uint8_t humidity;
HAL_Init(); /* 初始化HAL库 */
/* 初始化M4内核时钟,209M */
if(IS_ENGINEERING_BOOT_MODE())
{
sys_stm32_clock_init(34, 2, 2, 17, 6826);
}
usart_init(115200); /* 串口初始化为115200 */
delay_init(209); /* 延时初始化 */
//usmart_dev.init(209); /* 初始化USMART */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
while (dht11_init()) /* DHT11初始化 */
{
printf("DHT11 Error!\r\n"); /* 如果DHT11不在,打印信息 */
delay_ms(1000); /* 延时1s */
}
while (1)
{
t++;
if (t == 20)
{
t = 0;
dht11_read_data(&temperature, &humidity);/* 读DHT11 */
/* 打印温度值和湿度值 */
printf("DHT11 Temp=%d\r ℃, Humi=%d%%\r\n", temperature, humidity);
LED0_TOGGLE(); /* LED0闪烁 */
}
delay_ms(10); /* 延时10ms*/
}
}
33.4 编译和测试
开发板上电,进入仿真运行后结果如下,串口打印此时测试的环境温度为24℃,湿度为68%,且LED0在闪烁。
图33.4.1 运行结