目录
前言
1. DHT11简介
2. DHT11数据结构
3. DHT11的传输时序
3.1 DHT11开始发送数据流程
3.2 主机复位信号和DHT11响应信号
3.3 数字 “0” 信号表示方法
3.4 数字 “1” 信号表示方法
4. 硬件分析
5. 实验程序详解
5.1 main.c
5.2 DHT11.c
5.3 DHT11.h
前言
DHT11数字温湿度传感器不但能测温度,还能测湿度。本节我们将利用STM32F4来读取DHT11的温湿度传感器,获取环境的温度和湿度值。
1. DHT11简介
DHT11是广州奥松有限公司生产的一款温湿度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个NTC测温元件,并与一个高性能8位单片机相连。通过单片机等微处理器简单的电路连接就能实时的采集本地的温度和湿度。DHT11 与单片机之间能采用简单的单总线进行通信,仅仅需要一个I/O 口。
性能指标和特性:
- 工作电压范围:3.5V~5.5V
- 工作电流:0.5mA
- 湿度测量范围:20-90%RH
- 温度测量范围:0-50℃
- 湿度分辨率:1%RH 8位
- 温度分辨率:1℃ 8位
- 采样周期:1S
- 单总线结构
- 与TTL兼容(5V)
2. DHT11数据结构
DHT11数字温湿度传感器采用单总线数据格式。通过单个数据的引脚端口就可以完成输入输出双向传输。其数据包由5Byte(5*8=40Bit)组成。数据分小数部分和整数部分。
一次完整的数据传输为40bit,高位先行。
数据格式:8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和
其中校验和数据为前四个字节相加。
DHT11和DS18B20相同,传感器输出的都是未编码的二进制数字。数据(湿度、温度、整数、小数)之间应该分开进行处理。
比方说:
DHT11温湿度传感器一次完整的数据传输位如上图所示
那么:humidity(湿度)=byte4 . byte3=45.0(%RH)
temperature(温度)=byte2 . byte1=28.0(℃)
校验位byte0:byte4+byte3+byte2+byte1=73(humidity+temperature)
因此:温湿度的读取方式就是整数部分的二进制转换成十进制放在整数部分;小数部分的二进制转换成十进制放在小数部分。
注意:DHT11和MCU的一次通讯时间最大3ms,主机连续向DHT11采样的间隔建议不小于100ms。
3. DHT11的传输时序
3.1 DHT11开始发送数据流程
主机发送开始信号后,延时等待20us~40us后读取DHT11的应答信号,正常的话,DHT11 会拉低数据线,保持40~50us时间,读取总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高,保持40-50us,准备发送数据,每一位 bit 数据都以低电平开始。
如果读取响应为高电平,则DHT11没有响应,检查线路是否连接正常。
3.2 主机复位信号和DHT11响应信号
主机发送复位信号后,主机拉低总线,延时至少18ms,然后将主机拉高,延迟20-40us等待DHT11的应答信号。读取总线电平,如果为低电平,则表示从机DHT11发送应答信号成功。然后从机把总线拉高,准备发送数据。之所以拉高,是因为每发送一位数据都是以低电平开始的,为发送数据做准备。
3.3 数字 “0” 信号表示方法
因为DHT11也是遵循单总线协议的。所以也是通过高低电平延时不同时间进行组合来发送逻辑1或者逻辑0.
从机DHT11拉低总线,延时12-14us,然后从机释放总线,延时26-28us。表示发送数字 “0”
3.4 数字 “1” 信号表示方法
从机DHT11拉低总线,延时12-14us,然后从机释放总线,延时116-118us。表示发送数字 “1”
4. 硬件分析
- VCC:正电源,支持3.5V~5.5V
- Dout:输出引脚
- NC:空脚
- GND:地
DHT11数字温湿度传感器的第一脚接电源正极,第四脚接电源地端。数据端为第二脚。第二端数据引脚可以直接接在主机(单片机)的IO口。同时为了提高稳定性,一般在数据端和电源正极之间接一只4.7K的上拉电阻。第三脚NC为空脚,此管脚悬空不用。
5. 实验程序详解
实验现象:开机的时候先检测是否有DHT11存在,如果没有,则提示错误。只有在检测到DHT11之后才会开始读取温湿度值。如果发现了DHT11,则程序每隔100ms左右读取一次数据。
5.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "Key.h"
#include "usmart.h"
#include "MyI2C.h"
#include "AT24C02.h"
#include "DS18B20.h"
#include "DHT11.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
int main(void)
{
u8 t=0;
u8 temperature;
u8 humidity;
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"DHT11 Text");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTER");
LCD_ShowString(30,110,200,16,16,"2023/20/23");
while(DHT11_Init())//返回1表示没有检测到DHT11的存在,报错
{
LCD_ShowString(30,130,200,16,16,"DHT11 Rrror");
delay_ms(200);
LCD_Fill(30,130,239,130+16,WHITE); //清除这块区域 x 30~239 y 130~130+16
delay_ms(200);
}
LCD_ShowString(30,130,200,16,16,"DHT11 OK");
POINT_COLOR=BLUE;
LCD_ShowString(30,150,200,16,16,"Temperature: C");
LCD_ShowString(30,170,200,16,16,"Humidity: %");
while(1)
{
if(t%10==0) //每100ms读取一次
{
DHT11_Read_Data(&temperature,&humidity); //读取温湿度的值
LCD_ShowNum(30+12*8,150,temperature,2,16); //显示温度
LCD_ShowNum(30+9*8,170,humidity,2,16); //显示湿度
}
delay_ms(10);
t++;
if(t==20)
{
t=0;
LED0=!LED0;
}
}
}
5.2 DHT11.c
#include "stm32f4xx.h"
#include "DHT11.h"
#include "delay.h"
//复位DHT11
void DHT11_Reset(void)
{
//复位DHT11的时序:主机拉低总线,延时至少18ms,然后主机拉高总线,延时20-40us,等待从机应答
DHT11_IO_OUT(); //复位时序是主机来完成的,所以首先设置主机IO引脚输出模式
DHT11_DQ_OUT=0; //主机拉低总线
delay_ms(20); //延时至少18ms
DHT11_DQ_OUT=1; //主机拉高总线
delay_us(30); //延时20-40us
}
//等待DHT11的应答信号
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_CheckExist(void)
{
//应答信号的时序是:主机发送应答信号后,从机DHT11会拉低信号线,保持40-50us。此时,若读取主机总线为低电平,这说明DHT11发送了应答信号
//DHT11发送应答信号之后,再把总线拉高,保持40-50us,准备发送数据,每一位bit数据都是以低电平开始的。
u8 Existence=0;
DHT11_IO_IN(); //主机IO引脚设置为输入模式,等待从机的应答信号输入
while(DHT11_DQ_IN&&Existence<100) //从机DHT11会拉低信号线,保持40-80us,等待应答 按位&&操作必须两个条件都为真才是真
{
//DHT11_DQ_IN为真,就一直在这里循环,表示始终没有接收到从机DHT11的应答信号;因为总线拉低,读取高电平是始终没有响应的
//DHT11_DQ_IN是从机DHT11发送主机MCU的信号,应答信号时,从机会拉低信号线,因此,如果主机收到的是高电平,那么意味着没有发送应答信号
//Existence<100表示:设置一个缓冲时间去等待从机发送应答,这里设置的就是100us
Existence++;
delay_us(1);
}
if(Existence>=100)
{
return 1; //主机给从机的缓冲时间是100us,主机等了从机100us还是没有接收到应答信号,那么返回信息,未检测到DHT11的存在
}
else //在给定的100us内接收到了应答信号,将缓冲计时变量Existence清0,为后续总线拉高后延迟做准备
Existence=0;
while(!DHT11_DQ_IN&&Existence<100) //拉低后再次拉高,准备发送数据,延迟40-80us
{
//!DHT11_DQ_IN表示主机接收到了应答信号,也就是主机接收到了低电平,延迟100us
Existence++;
delay_us(1);
}
if(Existence>=100)
{
return 1;
}
return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{
//DHT11读取一位的时序:不管读的那一位是高电平1还是低电平0,首先从机DHT11都需要拉低总线
//数据0:从机拉低总线,延时12-14us,然后从机释放总线,延时26-28us
//数据1:从机拉低总线,延时12-14us,然后从机释放总线,延时116-118us
u8 data=0;
while(DHT11_DQ_IN&&data<100)//等待变为低电平
//主机从DHT11读取一位,从机要拉低总线,在没有拉低之前,主机收到的DHT11_DQ_IN一定是高电平1,而通过观察低电平的时序可以发现,不管是逻辑1还是0,低电平延时12-14us
//所以设置的100us是远远大于低电平延时时间的,所以data<100这个条件在此while循环中一定为真,跳出循环的条件一定是DHT11_DQ_IN=0,也就是等待变为低电平
{
data++;
delay_us(1);
}
data=0; //离开while循环时主机一定收到了低电平,此时DHT11_DQ_IN=0;设置data=0是为后续等待高电平做准备
while(!DHT11_DQ_IN&&data<100) //等待变为高电平
{
//从上面的循环出来时,DHT11_DQ_IN=0,取反就为真
//条件data<100:低电平0的整个发送时序延时时间(包括低电平延时12-14us,高电平延时26-28us)也远远小于100us,所以data<100在该循环中也是始终为真的
// 高电平1的整个发送时间是大于100us的(低电平延时12-14us,高电平延时116-118us)
//经分析,离开循环的条件是DHT11_DQ_IN变为高电平=1,取反等于0,为假。在低电平0的12-14us过去以后,高电平信号就会来临
data++;
delay_us(1);
}
//delay_us(40) 没有这个延迟40us是无法判断是数据1还是数据0的
delay_us(40);//从上一个while循环出来以后,时序停留在刚刚变为高电平的瞬间,这个时候延时40us,数据0会跳过高电平,数据1会停留在高电平
if(DHT11_DQ_IN) //这个时候根据主机读到的是1还是0设置返回值
return 1;
else
return 0;
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{
u8 i,data;
data=0;
for(i=0;i<8;i++)
{
data=data<<1; //DHT11读取的字节是高位先行的,所以每循环一次都要将上次读取的那一位左移,右侧最低位补0,每次都将新读取的那一位数据和0进行|或运算。
data=data|DHT11_Read_Bit();
}
return data;
}
//从DHT11读取一位数据
//temperature:温度值(范围:0~50℃)
//humidity:湿度值(范围:20%~90%)
//返回值:0 正常;1 读取失败
u8 DHT11_Read_Data(u8 *temperature,u8 *humidity)
{
//DHT11的数据格式是:5个字节,40位数据,前两位是湿度的整数和小数部分,第三、第四位是温度的整数和小数部分,最后一位是校验位
u8 Buf[5]; //定义一个数组来存放收到的5个字节的数组
u8 i;
DHT11_Reset();
if(DHT11_CheckExist()==0) //应答信号返回值为0,表示检测到了DHT11应答信号
{
for(i=0;i<5;i++) //调用读字节函数将读取到的5个字节存放到Buf数组中
{
Buf[i]=DHT11_Read_Byte();
}
if((Buf[0]+Buf[1]+Buf[2]+Buf[3])==Buf[4]) //检验成功
{
*humidity=Buf[0]; //这里只显示了温湿度的整数部分
*temperature=Buf[2];
}
}
else
return 1; //DHT11_CheckExist()==1 表示没有检测到DHT11的应答信号,报错
return 0; //返回正常情况
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOG时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //默认设置为输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOG,&GPIO_InitStructure);
DHT11_Reset();
return DHT11_CheckExist(); //初始化返回检测DHT11是否成功,0:成功 1:收不到应答信号,报错
}
5.3 DHT11.h
#ifndef _DHT11__H_
#define _DHT11__H_
#include "sys.h"
//IO方向设置
//IO方向是通过位段设置的,调用GPIO模式寄存器,以下代码已经多次使用,如有不理解,可回头温习DS18B20的实验代码
#define DHT11_IO_IN() {GPIOG->MODER&=~(3<<(9*2));GPIOG->MODER|=0<<9*2;} //PG9输入模式
#define DHT11_IO_OUT() {GPIOG->MODER&=~(3<<(9*2));GPIOG->MODER|=1<<9*2;} //PG9输出模式
//IO引脚设置
#define DHT11_DQ_OUT PGout(9) //PG9输出
#define DHT11_DQ_IN PGin(9) //PG9输入
void DHT11_Reset(void);
u8 DHT11_CheckExist(void);
u8 DHT11_Read_Bit(void);
u8 DHT11_Read_Byte(void);
u8 DHT11_Read_Data(u8 *temperature,u8 *humidity);
u8 DHT11_Init(void);
#endif
本节程序每一步都进行了详细的注释,有哪里解释的不对的,欢迎指正!!!