导读:DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,内部由一个 8 位单片机控制一个电阻式感湿元件和一个 NTC 测温元件。DHT11凭借其超小的体积、极低的功耗在业界得以广泛的认可和应用。需要指出的是,由于本人水平有限,如有错误还请读者指出,非常感谢。那么,接下来让我们来一起学习一下DHT11这款温湿度传感器吧。
一、DHT11基础储备
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,内部由一个 8 位单片机控制一个电阻式感湿元件和一个 NTC 测温元件。DHT11 虽然也是采用单总线协议,但是该协议与 DS18B20 的单总线协议稍微有些不同之处。
相比于 DS18B20 只能测量温度,DHT11 既能检测温度又能检测湿度,不过 DHT11 的精度和测量范围都要低于 DS18B20,其温度测量范围为 0~50℃,误差在±2℃;湿度的测量范围为 20%~90%RH(Relative Humidity 相对湿度—指空气中水汽压与饱和水汽压的百分比),误差在±5%RH。DHT11 电路很简单,只需要将 Dout 引脚连接单片机的一个 I/O 即可,不过该引脚需要上拉一个 5K 的电阻,DHT11 的供电电压为 3~5.5V。
二、协议及数据格式
DHT11 采用单总线协议与单片机通信,单片机发送一次复位信号后,DHT11 从低功耗模式转换到高速模式,等待主机复位结束后,DHT11 发送响应信号,并拉高总线准备传输数据。一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输
数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验和,一共 5 字节(40bit)数据。由于 DHT11 分辨率只能精确到个位,所以小数部分是数据全为 0。校验和为前 4 个字节数据相加,校验的目的是为了保证数据传输的准确性。
DHT11 只有在接收到开始信号后才触发一次温湿度采集,如果没有接收到主机发送复位信号,DHT11 不主动进行温湿度采集。当数据采集完毕且无开始信号后,DHT11 自动切换到低速模式。
注意:由于 DHT11 时序要求非常严格,所以在操作时序的时候,为了防止中断干扰总线时序,先关闭总中断,操作完毕后再打开总中断。
三、操作时序
1、 主机发送复位信号
DHT11 的初始化过程同样分为复位信号和响应信号。首先主机拉低总线至少 18ms,然后再拉高总线,延时 20~40us,取中间值 30us,此时复位信号发送完毕。
2、DHT11 发送响应信号
DHT11 检测到复位信号后,触发一次采样,并拉低总线 80us 表示响应信号,告诉主机数据已经准备好了;然后 DHT11 拉高总线 80us,之后开始传输数据。如果检测到响应信号为高电平,则 DHT11 初始化失败,请检查线路是否连接正常。
当复位信号发送完毕后,如果检测到总线被拉低,就每隔 1us 计数一次,直至总线拉高,计算低电平时间;当总线被拉高后重新计数检测 80us 的高电平。如果检测到响应信号之后的80us 高电平,就准备开始接收数据。实际上 DHT11 的响应时间并不是标准的 80us,往往存在误差,当响应时间处于 20~100us 之间时就可以认定响应成功。
3、数据传输
DHT11 在拉高总线 80us 后开始传输数据。每 1bit 数据都以 50us 低电平时隙开始,告诉主机开始传输一位数据了。DHT11 以高电平的长短定义数据位是 0 还是 1,当 50us 低电平时隙过后拉高总线,高电平持续 26~28us 表示数据“0”;持续 70us 表示数据“1”。
当 最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,表示数据传输完毕,随后总线由上拉电阻拉高进入空闲状态。
4、区分数据0/1的巧法
还是像检测响应时间那样计算高电平持续时间那就太麻烦了!!!
数据“0”的高电平持续 26~28us,数据“1”的高电平持续70us,每一位数据前都有 50us 的起始时隙。如果我们取一个中间值 40us 来区分数据“0”和数据“1”的时隙。
当数据位之前的 50us 低电平时隙过后,总线肯定会拉高,此时延时 40us 后检测总线状态,如果为高,说明此时处于 70us 的时隙,则数据为“1”;如果为低,说明此时处于下一位数据 50us 的开始时隙,那么上一位数据肯定是“0”。
为什么延时 40us?由于误差的原因,数据“0”时隙并不是准确 26~28us,可能比这短,也可能比这长。当数据“0”时隙大于 26~28us 时,如果延时太短,无法判断当前处于数据“0”的时隙还是数据“1”的时隙;如果延时太长,则会错过下一位数据前的开始时隙,导致检测不到后面的数据
四、51及STM32例程
51对应的
dht11.c
#include "config.h"
#include "delay.h"
//1-检测到响应信号 0-未检测到
u8 DHT11RstAndCheck(void)
{
u8 timer = 0;
EA = 0;
DHT11 = 0;
Delay20ms();
DHT11 = 1;
Delay30us();
while(!DHT11)
{
timer++;
Delay1us();
}
if(timer>100 || timer<20)
{
EA = 1;
return 0;
}
timer = 0;
while(DHT11)
{
timer++;
Delay1us();
}
EA = 1;
if(timer>100 || timer<20)
{
return 0;
}
return 1;
}
//读一字节数据
u8 DHT11ReadByte(void)
{
u8 i;
u8 byt = 0;
EA = 0;
for(i=0; i<8; i++)
{
while(DHT11);
while(!DHT11);
Delay40us();
byt <<= 1;
if(DHT11)
{
byt |= 0x01;
}
}
EA = 1;
return byt;
}
//读取一次数据(整数) 0-读取失败 1-读取成功
u8 DHT11ReadData(u8 *Humi, u8 *Temp)
{
u8 sta = 0;
u8 i;
u8 buf[5];
if(DHT11RstAndCheck())
{
for(i=0; i<5; i++)
{
buf[i] = DHT11ReadByte();
}
if(buf[0]+buf[1]+buf[2]+buf[3] == buf[4])
{
*Humi = buf[0];
*Temp = buf[2];
}
sta = 1;
}
else
{
*Humi = 0xFF;
*Temp = 0xFF;
sta = 0;
}
return sta;
}
dht11.h
#ifndef DHT11_H
#define DHT11_H
u8 DHT11ReadData(u8 *Humi, u8 *Temp);
#endif
STM32对应的
dht11.c
#include "dht11.h"
/*DHT11复位和检测响应函数,返回值:1-检测到响应信号;0-未检测到响应信号*/
u8 DHT11RstAndCheck(void)
{
u8 timer = 0;
__set_PRIMASK(1); //关总中断
DHT11_OUT = 0; //输出低电平
delay_ms(20); //拉低至少18ms
DHT11_OUT = 1; //输出高电平
delay_us(30); //拉高20~40us
while (!DHT11_IN) //等待总线拉低,DHT11会拉低40~80us作为响应信号
{
timer++; //总线拉低时计数
delay_us(1);
}
if (timer>100 || timer<20) //判断响应时间
{
__set_PRIMASK(0); //开总中断
return 0;
}
timer = 0;
while (DHT11_IN) //等待DHT11释放总线,持续时间40~80us
{
timer++; //总线拉高时计数
delay_us(1);
}
__set_PRIMASK(0); //开总中断
if (timer>100 || timer<20) //检测响应信号之后的高电平
{
return 0;
}
return 1;
}
/*读取一字节数据,返回值-读到的数据*/
u8 DHT11ReadByte(void)
{
u8 i;
u8 byt = 0;
__set_PRIMASK(1); //关总中断
for (i=0; i<8; i++)
{
while (DHT11_IN); //等待低电平,数据位前都有50us低电平时隙
while (!DHT11_IN); //等待高电平,开始传输数据位
delay_us(40);
byt <<= 1; //因高位在前,所以左移byt,最低位补0
if (DHT11_IN) //将总线电平值读取到byt最低位中
{
byt |= 0x01;
}
}
__set_PRIMASK(0); //开总中断
return byt;
}
/*读取一次数据,返回值:Humi-湿度整数部分数据,Temp-温度整数部分数据;返回值: -1-失败,1-成功*/
u8 DHT11ReadData(u8 *Humi, u8 *Temp)
{
s8 sta = 0;
u8 i;
u8 buf[5];
if (DHT11RstAndCheck()) //检测响应信号
{
for(i=0;i<5;i++) //读取40位数据
{
buf[i]=DHT11ReadByte(); //读取1字节数据
}
if(buf[0]+buf[1]+buf[2]+buf[3] == buf[4]) //校验成功
{
*Humi = buf[0]; //保存湿度整数部分数据
*Temp = buf[2]; //保存温度整数部分数据
}
sta = 1;
}
else //响应失败返回-1
{
*Humi = 0xFF; //响应失败返回255
*Temp = 0xFF; //响应失败返回255
sta = -1;
}
return sta;
}
/*DHT11初始化函数*/
u8 DHT11Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//使能GPIOC端口时钟
GPIO_SetBits(GPIOC,GPIO_Pin_13); //设置PC13输出高电平,(先设置引脚电平可以避免IO初始化过程中可能产生的毛刺)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //设置DHT11数据引脚->PC13
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //设置为开漏输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置输出速率为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化GPIOC端口
return DHT11RstAndCheck(); //返回DHT11状态
}
dht11.h
#ifndef _DHT11_H
#define _DHT11_H
#include "config.h"
#define DHT11_OUT PC_OUT(13)
#define DHT11_IN PC_IN(13)
u8 DHT11Init(void);
u8 DHT11ReadData(u8 *Humi,u8 *Temp);
#endif
总结:此篇文章主要讲述了DHT11的驱动原理,接着引出了基于STM32和STC51两款主流单片机的具体驱动代码。以此抛砖引玉,希望读者一来可以快速上手(DHT11的使用),二来可以举一反三(其他类型的单片机驱动DHT11)。
希望大家多多支持我的原创文章。如有错误,请大家及时指正,非常感谢。