1、DHT11
DHT11是一个检测温湿度的模块,将温湿度传感器获取的信号传给内部8位的单片机,单片机进行处理,得到数字的温湿度数据。
DHT11与外部通信方式是单总线。
2、单总线通信过程
主机是ESP8266,从机是DHT11。
很容易理解的过程:
- 由主机ESP8266发出起始信号
- DHT11发现主机在呼唤,给出应答
- DHT11发送温湿度的数据,以及结束信号
ESP8266:在么?
DHT11:我在。
DHT11:现在的温度是26℃,湿度是70%,完毕!
3、各个时序
空闲电平:单总线通常要求外接一个约 4.7kΩ 的上拉电阻,这样,当总线闲置时,其状态为高电平。
3.1、起始信号及应答
1)ESP8266输出低电平,且低电平保持时间18~30ms,典型值为20ms,程序中取25ms。
DHT11_OUT(0); //ESP8266输出低电平
DHT11_delay_ms(25); //范围18~30ms,典型值为20ms
2)ESP8266的IO设为输入,等待DHT11输出低电平的到来,最多等待100us,超时则认为通信失败。
DHT11_IN();
while(GPIO_INPUT_GET(GPIO_ID_PIN(5))){ //等待低电平的到来
retry++;
os_delay_us(1);
if(retry > 100)
return START_ERR; //等100us都没有低电平响应
}
3)有低电平到来之后,计算低电平的时间,范围在78~88us,典型值是83us的低电平,故超过90us可认为低电平时间过长了。
retry = 0;
while((GPIO_INPUT_GET(GPIO_ID_PIN(5))) == 0){
retry++; //计算低电平的时间
os_delay_us(1);
if(retry > 90)
return START_ERR; //低电平响应时间过长
}
4)能运行到这里,单总线上已经是高电平了,高电平持续范围80~92us,可取60us之后,再读取一次总线状态,确认是否仍然为高电平。
os_delay_us(60); //等待60us再检测是否为高电平,确认一次
if(GPIO_INPUT_GET(GPIO_ID_PIN(5)) == 0)
return START_ERR;
else
return DHT_OK;
}
3.2、发送温湿度数据
3.2.1、 数据格式
8bit检验位是前4字节数据的和校验。
3.2.2、读取一位数据
位数据“0”的格式为: 54us(50~58us)的低电平和 23-27us的高电平;
位数据“1”的格式为: 54us(50~58us)的低电平加 68-74us的高电平。
1)首先要等待低电平的到来,超过50us认为读取出错。
2)有低电平了,要计时低电平的时间,根据手册超过58us认为出错,程序中稍稍延长,取62us.
3)此时进入高电平的阶段,如果是23-27us的高电平认为是0,如果是68~74us的高电平认为是1,所以要跳过0的高电平时间,但又处于1的高电平期间,检测47us时(27和68的中间值)的总线状态,为0则0,为1则1。
//读一位数据
u8 ICACHE_FLASH_ATTR DHT11_Read_Bit(void)
{
u8 retry = 0;
u8 data;
while(GPIO_INPUT_GET(GPIO_ID_PIN(5))){ //等待低电平
retry ++;
os_delay_us(1);
if(retry > 50)
return BIT_ERR; //出错
}
retry = 0;
while(GPIO_INPUT_GET(GPIO_ID_PIN(5))==0){ //等待低电平结束
retry ++;
os_delay_us(1);
if(retry > 62)
return BIT_ERR; //出错
}
os_delay_us(47); // 跳过"0"的高电平时序,取27和68的中间值
if(GPIO_INPUT_GET(GPIO_ID_PIN(5)))
data = 1;
else
data = 0;
return data;
}
3.2.3、读取一个字节
注意,数据在总线上传输是高位在前,也就是说先接收到的位是最高位。
//读DHT11传回来的一个字节
u8 ICACHE_FLASH_ATTR DHT11_Read_Byte(void)
{
u8 i,data = 0;
for(i=0;i<8;i++){
data <<= 1;
data |= DHT11_Read_Bit(); //高位在前
}
return data;
}
3.2.4、读取40位数据(5个字节)
循环5次读取字节就OK了,把数据放在数组里存着。
for(i=0;i<5;i++) //读取原始的5个字节
DHT11_Data_Array[i] = DHT11_Read_Byte();
3.3、结束信号
DHT11会输出54us的低电平,然后释放总线(拉高)。
下面这一段代码就是等待低电平,并计算低电平的时间是否过长。
while(GPIO_INPUT_GET(GPIO_ID_PIN(5))){ //等待低电平到来
retry++;
os_delay_us(1);
if(retry > 50)
return END_ERR; //低电平响应时间过长
}
//计数低电平的时间
retry = 0;
while(GPIO_INPUT_GET(GPIO_ID_PIN(5)) == 0){
retry++;
os_delay_us(1);
if(retry > 60)
return END_ERR; //低电平时间过长
}
4、处理及串口打印
4.1、校验
和校验,就是把前4个字节加起来,看看是不是第5个字节的内容。
if(DHT11_Data_Array[4] != DHT11_Data_Array[0]+DHT11_Data_Array[1]+DHT11_Data_Array[2]+DHT11_Data_Array[3])
return CHECK_ERR; //返回校验错误
4.2、温度为负的情况
当温度为负时,温度小数部分的最高位会置1,所以可以据此来判断温度的正负并修正零下时的温度数据。
DHT11_Data_Array[5]用来存放温度的正负,温度为正为0,温度为负为1.
DHT11_Data_Array[3]是温度的小数部分,温度为负时把最高位去除。
if(DHT11_Data_Array[3] & 0x80){ //温度为负
DHT11_Data_Array[3] = DHT11_Data_Array[3] & 0x7F;
DHT11_Data_Array[5] = 1;
}else{
DHT11_Data_Array[5] = 0;
}
4.3、串口打印温湿度
//打印湿度
os_printf("Humi:%d.%dRH\r\n",DHT11_Data_Array[0],DHT11_Data_Array[1]);
//打印温度
os_printf("Temp:%d.%d℃\r\n",DHT11_Data_Array[2],DHT11_Data_Array[3]);