1、DHT11产品概述
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高 的可靠性与卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测 温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快 响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的 湿度校验室中进行校准。校准系数以程序的形式储存在OTP内存中,传感器内 部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集 成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达20米以上,使 其成为各类应用甚至最为苛刻的应用场合的最佳选则。产品为 4 针单排引脚 封装。连接方便,特殊封装形式可根据用户需求而提供(来自于数据手册)。
2、应用领域
暖通空调、测试及检测设备、汽车、数据记录器、消费品
自动控制 、气象站 、家电 、湿度调节器 、医疗 、除湿器
3、传感器性能说明
参数 | 条件 | Min | Typ | Max | 单位 |
湿度 | |||||
分辨率 | 1 | 1 | 1 | %RH | |
8 | Bit | ||||
重复性 | ±1 | %RH | |||
精度 | 25℃ | ±4 | %RH | ||
0-50℃ | ±5 | %RH | |||
互换性 | 可完全互换 | ||||
量程范围 | 0℃ | 30 | 90 | %RH | |
25℃ | 20 | 90 | %RH | ||
50℃ | 20 | 80 | %RH | ||
响应时间 | 1/e(63%)25℃, 1m/s 空气 | 6 | 10 | 15 | S |
迟滞 | ±1 | %RH | |||
长期稳定性 | 典型值 | ±1 | %RH/yr | ||
温度 | |||||
分辨率 | 1 | 1 | 1 | ℃ | |
8 | 8 | 8 | Bit | ||
重复性 | ±1 | ||||
精度 | ±1 | ±2 | ℃ | ||
量程范围 | 0 | 50 | ℃ | ||
响应时间 | 1/e(63%) | 6 | 30 | S |
4、接口说明
建议连接线长度短于20米时用5K上拉电阻,大于20米时根据实际情况使 用合适的上拉电阻
5、 电源引脚
DHT11的供电电压为 3-5.5V。传感器上电后,要等待 1s 以越过不稳定状态在此 期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去 耦滤波。
6、原理分析
简介中的重点,传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接,也就是说DHT11的组成实际是单片机通过ADC采集电压,并将采集处理好的数据根据一定的协议方式与其他设备进行通信。
感湿电阻、NTC(温感电阻)随温湿度的变化阻值会产生变化,这样我们采集到的电压值就会跟着变化。(概念电路图如下)
7、DHT11串行接口 (单线双向)
DATA 用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次 通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数 部分用于以后扩展,现读出为零。操作流程如下: 一次完整的数据传输为40bit,高位先出。
数据格式:8bit湿度整数数据+8bit湿度小数数据 +8bi温度整数数据+8bit温度小数数据 +8bit校验和
数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据 +8bi温度整数数据+8bit温度小数数据”所得结果的末8位。
用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主 机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集, 用户可选择读取部分数据.从模式下,DHT11接收到开始信号触发一次温湿度采集, 如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集.采集数据后 转换到低速模式。
8、时序图分析
(1)总体通信过程
首先我们对总的通信过程进行分析:从上图可以看出单片机作为主机,首先输出低电平(数据总线由高变低),持续一段时间的低电平,单片机再拉高总线。之后总线由输出变为输入 ,准备接收DHT11发送的低电平信号,这个低电平持续一段时间之后,又拉高(DHT11拉高的)一段时间,之后进行数据传输,可以看到,传输数据是0和1都是通过高电平实现的,通过高电平持续的时间来判断是数据1还是数据0,这里还有一个需要注意的点,每个数据传输有一段低电平的间隔时间。
(2)起始和响应时序图分析(唤醒、握手)
上部分提到了,总的通信过程。后面的时序图都是详细的介绍单个部分时序图。根据上图我们可以看到, 最开始单片机将总线电平由高拉低,持续时间为至少18ms,然后再拉高20-40us,这个过程是起始信号,然后总线状态由输出变为输入,如果DHT11正常且接收到了单片机发送的起始信号,DHT11会将总线拉低80us,之后再拉高80us,作为回应。这段主要就是握手的过程,单片机告诉DHT11我要接数据了,然后DHT11回应我准备好了。之后DHT11就开始发送温湿度数据了。
(3)数据接收数据时序图分析
根据数据接收时序图可以看出,每次数据发送的开始会有一段50us的低电平信号,之后就进行1bit的数据传输了,如果传输数据0,高电平持续26us-28us,如果传输数据1,高电平持续70us。
9、测量分辨率
测量分辨率分别为 8bit(温度)、8bit(湿度)。
10、程序设计
(1)由于我们需要对一个GPIO口实现,收发两种功能所以首先写两个函数,分别是输入初始化和输出初始化。
//初始化配置IO口输出函数
void DHT11_OUT ()
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pin : PB8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //设置为推挽输出
GPIO_InitStruct.Pull = GPIO_PULLUP; //上拉输入,这个无所为
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //输出速度非常高
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//初始化配置IO口输入函数
void DHT11_IN (void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pin : PB8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //设置为输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP; //上拉输入
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //输出速度非常高
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
(2)准备工作完毕,接下来按照通信时序图一步一步写函数
2.1 单片机发送起始信号函数(发送唤醒信号)
uint8_t DHT11_Reset()
{
DHT11_OUT(); //将PB8设为输入状态
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET); //拉低DQ
HAL_Delay(20); //拉低至少18ms
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET); //拉高DQ
Delay_us(30); //主机拉高20~40u
}
起始信号函数,根据时序图,GPIO为输出模式,总线由高电平变为低电平,这里我们可以使用GPIO输出函数,直接输出低电平(因为DHT11数据总线默认硬件连了上拉电阻,所以他默认是高电平的)。低电平持续时间最少18ms,所以我们延时20ms来保障持续时间足够(实现上图红色部分)。之后拉高电平20到40us,这里我们通过输出高电平,并延时30us来实现(实现上图蓝色部分)。
2.2 检测DHT11响应函数
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
uint8_t DHT11_Check()
{
uint8_t timer = 0; //定义临时变量
DHT11_IN(); //将PB8设为输入状态
//如果DHT11的数据线输入为低电平,且 retry 小于100,则将 retry 加1,并延时1微秒,
//重复这个过程直到 retry 大于等于100 或者DHT11的数据线输入变成高电平。
//如果 retry 大于等于100,表示检测失败,返回1;否则,返回0,表示检测成功。
while ((HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == 0) && timer < 100) //DHT11拉低后会再次拉高40~80us
{
timer++;
Delay_us(1);
}
if(timer >= 100)
{
return 1; //DHT11响应失败
}
else
{
timer=0;
}
//如果DHT11的数据线输入为高电平,且 retry 小于100,则将 retry 加1,并延时1微秒,
//重复这个过程直到 retry 大于等于100 或者DHT11的数据线输入变成低电平。
//如果 retry 大于等于100,表示检测失败,返回1;否则,将 retry 重置为0。
while ((HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == 1) && timer < 100) //DHT11会拉低40~80us
{
timer++;
Delay_us(1);
}
if(timer >= 100)
{
return 1; //DHT11响应失败
}
else
{
timer=0;
}
return 0; //DHT11响应成功
}
这里目的是读取DHT11的响应信号,首先我们将IO的模式配置为输入模式。读取DHT11响应的电平。可以看到,如果IO电平为低电平且retry变量小于100,我们进入while循环,进1次延时1us并retry加1,这里得到的retry如果一直到大于100us(其实这里耗时会比100us大,因为执行retry加1也需要耗时间,这里忽略不计),IO口的电平状态还是低电平,执行下面语句if(retry>=100)return 1;返回1证明我们没有检测到DHT11响应,因为根据时序图我们可以看到DHT11如果正常响应了会回应80us的响应信号(上图红色部分)。下面判断上图中蓝色部分,如果读取到的是高电平,且retry小于100时延时1us,retry加1。这里得到的retry如果一直到大于100us,IO口的电平状态还是高电平,执行下面语句if(retry>=100)return 1;返回1证明我们没有检测到DHT11响应,因为根据时序图我们可以看到DHT11如果正常响应了会回应80us的响应信号(上图蓝色部分)。
2.3 读取一位数据
//读取一个位
uint8_t DHT11_Read_Bit()
{
uint8_t timer = 0; //定义临时变量
DHT11_IN(); //将PB8设为输入状态
//先检测DHT11拉高的电平
while((HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == 1) && timer < 100) //等待变为低电平
{
timer++;
Delay_us(1);
}
timer = 0;
//用while的方法等待50us的低电平
while((HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == 0) && timer < 100) //等待变为高电平
{
timer++;
Delay_us(1);
}
timer = 0;
Delay_us(40);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == 1) //延时之后如果还是高电平,就证明是数据1
{
return 1;
}
else //反之,延时之后如果不是高电平,就证明是数据0
{
return 0;
}
}
这里实现的是读取1位数据,程序中首先同样用while的方法等待50us的低电平(上图红色部分),之后再等待高电平,这里的高电平就是我们要接收的数据0或者1,当识别到高电平时不进入第二个while函数,执行延时40us这里40us远远大于28us(图中蓝色部分),且远远小70us(70us为数据1,上图只画出了0)。延时之后执行 if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == 1) 函数,也就是说,如果40us之后检测IO电平还是高电平,那么数据肯定是1,那么我们返回1,如果变低了那么就返回0。
2.4 读取一个字节数据
这里读取1个字节,我们直接调用上面实现读取1位的函数,然后循环8次就可以了。
//从DHT11读取一个字节
//返回值:读到的数据
uint8_t DHT11_Read_Byte()
{
uint8_t i, byte;
byte = 0;
for (i = 0; i < 8; i++)
{
byte <<= 1; //左移运算符,byte左移1位
byte |= DHT11_Read_Bit(); //"|"表示按位或等于
}
return byte;
}
2.5 读取一次完成数据(共40bit)
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失0败
uint8_t DHT11_Read_Data(uint8_t *humiH,uint8_t *humiL,uint8_t *tempH,uint8_t *tempL)
{
uint8_t buf[5];
uint8_t i;
DHT11_Reset(); //DHT11端口复位,发出起始信号
if(DHT11_Check() == 0) //等待DHT11回应,0为成功回应
{
for(i = 0; i < 5; i++) //读取40位数据
{
buf[i] = DHT11_Read_Byte(); //读出数据
}
if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) //数据校验
{
*humiH=buf[0]; //湿度整数
*humiL=buf[1]; //湿度小数
*tempH=buf[2]; //温度整数
*tempL=buf[3]; //温度整数
}
}
else
{
return 1; //读取失败
}
return 0; //读取成功
}
这里我们读取一次完整的数据即40bit ,首先判断DHT11_Check()的返回值,这是我们前面写的函数(DHT11响应函数),如果返回1证明DHT11没有回响应信号,不进入if,返回1证明读取失败;如果DHT11_Check()的返回值为0,证明DHT11正确回复了响应信号,程序进入if执行,if里先循环5次执行读出完整数据,接下来验证校验和,如果前4个字节数加起来等于第五个字节(等于校验和)数据正确进入if,返回读取到的温湿度数据。这样我们需要读取DHT11数据的地方,直接调用DHT11_Read_Data函数就可以了。
2.6 上电初始化
//初始化DHT11的IO口,同时复位DHT11并检测DHT11的存在
//返回1:不存在
//返回0:存在
uint8_t FS_DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //设置为推挽输出
GPIO_InitStruct.Pull = GPIO_PULLUP; //上拉输入
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); //现将PB8,设置为输出高电平
DHT11_Reset(); //复位DHT11
return DHT11_Check();//等待DHT11的回应
}
这里函数,是为了上电初始化使用,在主函数中进行调用。先配置为推挽输出模式,然后输出高电平,这里的目的是为了软件端保障DHT11上电默认总线为高电平。之后使用DHT11_Reset(); 对DHT11发送唤醒信号,然后等待DHT11响应使用了return DHT11_Check();函数,即如果正确响应了返回0,没有正确响应返回0。
2.7 微妙延时函数
主要用来进行精确的微妙延时
//微秒延时函数
static uint32_t fac_us = 0; //us延时倍乘数
void delay_init(uint8_t SYSCLK)
{
fac_us = SYSCLK;
}
void Delay_us(uint8_t timer_us)
{
delay_init(64);
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;//told:旧的时间 tnow:现在的时间 tcnt:计数器的值
uint32_t reload = SysTick->LOAD; //LOAD的值 获取重加载寄存器的值
ticks = timer_us * fac_us; //需要的节拍数,即我们要延时的时间
told = SysTick->VAL; //24 刚进入时的计数器值
while (1)
{
tnow = SysTick->VAL;//22 20 0 //获取计数器现在的值
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
}
else
{
tcnt = reload - t now + told;
}
told = tnow;
if (tcnt >= ticks)
{
break; //时间超过/等于要延迟的时间,则退出.
}
}
}
}
到这里我们的DHT11程序编写就讲解完成了,使用时上电初始化调用FS_DHT11_Init()函数,之后需要读取温湿度数据的地方调用 DHT11_Read_Data(uint8_t *humiH,uint8_t *humiL,uint8_t *tempH,uint8_t *tempL)函数就可以了。