系列文章目录
本文旨在介绍DHT11在stm32中的使用和如何根据厂家所给参考手册书写代码
本次芯片选择stm32F103ZET6
本次硬件选择DHT11
本次编写软件选择cubeIDE(基于hal库的编写)
提示:以下是本篇文章正文内容,下面案例可供参考
一、DHT11模块基础介绍
1.简介
DHT11模块是一款常用的数字温湿度传感器,广泛应用于各种嵌入式系统、智能家居和气象站等领域,相比于 DS18B20 只能测量温度,DHT11 既能检测温度又能检测湿度,不过 DHT11 的精度和测量范围都要低于 DS18B20,其温度测量范围为 0~50℃,误差在±2℃;湿度的测量范围为 20%~90%RH(Relative Humidity 相对湿度—指空气中水汽压与饱和水汽压的百分比),误差在±5%RH
2.与MCU的通信方式
DHT11采用单总线通信
单总线通信的基本原理
单总线通信的基本原理是通过调整高低电平的时长来发送'0'和'1',并利用起始位和终止位来开始和结束通信。主机和从机之间的通信通常包括以下几个步骤:
- 初始化:主机通过拉低单总线480 ~ 960 us产生复位脉冲,然后释放总线,进入接收模式。
- 识别从机:主机发送ROM命令以识别从机,并等待从机应答。
- 交换数据:主机与从机之间进行数据传输,数据传输是半双工的,即接收和发送不能同时进行
我个人对单总线通信的理解就是一种不依赖于单片机内部通信外设(usart iic can)的通信方式,它只通过MCU发送高电平和低电平来进行主机与从机之间的通信,换句通俗易懂的话来说就是我们用手法来1写出协议来代替MCU外设进行通信
二、代码介绍
1.结合参考手册书写
1.电路讲解
在DHT11的电路中我们外接了一个上拉电阻,之所以要外接这个上拉电阻的原因是,我们在前面说过DHT11是单总线通信,在未进行通信时我们需要将DHT11的电平拉高,为了拉高电平使总线一直保持高电平,我们外接了一个上拉电阻。
Pin | 名称 | 解释 |
1 | VDD | 供电3.3—5v |
2 | GND | 接地,电源负极 |
3 | DATA | 随便初始化一个GPIO口PA5 |
4 | NC | 悬空不接线 |
2.DHT11通信协议及数据格式
DATA 用于微处理器与 DHT 11 之间的通讯 和 同步 , 采用单总线数据格式 , 一次
通讯时间 4ms 左右 , 数据分小数部分和整数部分 , 具体格式在下面说明 , 当前小数
部分用于以后扩展 , 现读出为零 . 操作流程如下 :
一次完整的数据传输为 40bit, 高位先出。
数据格式 :8bit 湿度整数数据 +8bit 湿度小数数据
+8bi 温度整数数据 +8bit 温度小数数据
+8bit 校验和
数据传送正确时校验和数据等于 “ 8bit 湿度整数数据 +8bit 湿度小数数据
+8bi 温度整数数据 +8bit 温度小数数据 ” 所得结果的末 8 位。
从上面手册中所给的说明,我们可以知道的是一次完整的数据传输位40bit,这40bit的数据分为五个部分 1.温度整数 2.温度小数数据 3.湿度数据 4.湿度小数数据 5. 校验位 数据发送的顺序是高位在前,低位在后的顺序传输。
3.操作时序(整个流程的框架)
上图是我们读取DHT11的整个流程图,我们接下来将其细分为三个阶段 1.MCU唤醒DHT11 2.DHT11接收信号做出回应 3.DHT11发送信号
1.MCU唤醒DHT11
图中的黑线代表我们的主机信号,而黄线代表从机(DHT11)信号,首先我们主机将电平拉低也就是输出一个低电平信号,这个低电平信号持续18ms,之后再将信号线拉高20—40us也就是输出高电平
我们首先配置GPIO模式为推挽输出模式,选择引脚为PA5,选择高速输出。
void DHT_GPIO_SET_OUTPUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin=GPIO_PIN_5;
GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
}
然后配置PA5先输出18ms的低电平信号将信号线拉低,再控制PA5输出高电平 ,这样一个过程就完成了MCU唤醒DHT11的全过程
DHT_GPIO_SET_OUTPUT();
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
HAL_Delay(18);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
Delay_us(20);
然后就是从机DHT11做出反应,DHT11收到主机的唤醒后,先将高电平拉低80us,然后再将低电平信号拉高80us,在这之后DHT11开始发送信号。(对应下图)
从机响应之后,就会开始发送信号,所以这个时候我们要手动判断DHT11是否唤醒,我们需要手动编写代码来读取GPIO口的信号变化。
我们首先将GPIO口设置为输入模式来保证我们能读取DHT11的信号(如下图)
void DHT_GPIO_SET_INPUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin=GPIO_PIN_5;
GPIO_InitStructure.Mode=GPIO_MODE_INPUT;
GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
}
我们先读取PA5是否为0,也就对应上图中的DHT11是否将信号拉低,然后就是while循环,这个循环的判断是在判断PA5是否为0 retry是否小于100,如果满足这个条件就进入函数,进入while后先延迟1us,然后对retry进行累加,直到我们将信号拉高或者retry大于100才会跳出循环,这个过程就是我们在等待DHT11将信号拉低后再将信号拉高的这个瞬间,一但DHT11将信号拉高的这个瞬间,PA5就不满足等于0的条件,就会跳出第一个循环,而之所以让retry<100 对应的就是80us的低电平时间,然后第二个while循环就是在判断电平是否又被DHT11拉低(这个过程比较绕多看几遍代码就懂了)那这里为什么又要将电平拉低呢? 原因就是在信号开始发送前,电平又会被拉为低电平50us(如下图)。
uint8_t retry=0;
uint8_t i;
DHT_GPIO_SET_INPUT();
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0)
{
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0 && retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==1 && retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
}
完成了上面的唤醒过程,这个时候我们就可以开始接收信号,而在DHT11的信号发送过程中26—28us时长的高电平代表0,70us时长的高电平代表1.
有了上面的补充我们知道在DHT11中0和1的表示,接下来我们来编写数据读写函数,如下图首先定义了四个变量 ,然后是一个for循环,循环八次,(之所以要循环八次的原因是我们的信号发送是以bit为单位的,我们数据的格式是每次八个bit位)进入for循环之后我们先判断电平信号是否为0 retry是否小于100,如果符合这两个条件就进入while循环,而这个循环存在的实际意义则是为了判断我们是否从50us的低电平信号跳转到数据所代表的高电平信号,往下走先让retry为0,再延时40us,在延迟40us后如果信号仍为高电平,那么就代表我们接受的信号数字为1,如果不是我们接受的信号数字就是0,具体代码呈现在temp的值,然后接下来就又是一个while判断,这个判断就是在判断下一个bit的发送(即将电平拉低的信号,上图中有显示),然后最后我们将我们得到的数据通过移位操作存入ReadData中(之所以要移位,是因为我们DHT11是高位先行,而一个完整的数据是由8个bit位组成的二进制数字)
uint8_t DHT_Read_Byte(void)
{
uint8_t ReadData=0;
uint8_t temp;
uint8_t retry=0;
uint8_t i;
for(i=0; i<8; i++)
{
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0 && retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
Delay_us(40);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==1) temp=1;
else temp=0;
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==1 && retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
ReadData<<=1;
ReadData |= temp;
}
return ReadData;
}
最后就是for循环四次,之所以循环四次的原因是我们传输了4个数据1.温度整数 2.温度小数数据 3.湿度数据 4.湿度小数数据 ,而这每个数据又是由八个bit位组成的二进制数字(也对应上面那个for循环八次的原因)
for(i=0; i<5; i++)
{
Data[i] = DHT_Read_Byte();
}
完整代码如下,唯一没有讲的是,就是最后我们要进行一次数据校验,也就是我们的第五位校验数据。
uint8_t Data[5]={0x00,0x00,0x00,0x00,0x00};
void Delay_us(uint16_t us){
uint16_t differ = 0xffff-us;
__HAL_TIM_SET_COUNTER(&htim1,differ);
HAL_TIM_Base_Start(&htim1);
while(differ < 0xffff){
differ = __HAL_TIM_GET_COUNTER(&htim1);
}
HAL_TIM_Base_Stop(&htim1);
}
void DHT_GPIO_SET_OUTPUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin=GPIO_PIN_5;
GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP;
// GPIO_InitStructure.Pull=;
GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
}
void DHT_GPIO_SET_INPUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin=GPIO_PIN_5;
GPIO_InitStructure.Mode=GPIO_MODE_INPUT;
GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
}
uint8_t DHT_Read_Byte(void)
{
uint8_t ReadData=0;
uint8_t temp;
uint8_t retry=0;
uint8_t i;
for(i=0; i<8; i++)
{
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0 && retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
Delay_us(40);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==1) temp=1;
else temp=0;
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==1 && retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
ReadData<<=1;
ReadData |= temp;
}
return ReadData;
}
uint8_t DHT_Read(void)
{
uint8_t retry=0;
uint8_t i;
DHT_GPIO_SET_OUTPUT();
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
HAL_Delay(18);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
Delay_us(20);
DHT_GPIO_SET_INPUT();
Delay_us(20);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0)
{
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0 && retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==1 && retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
for(i=0; i<5; i++)
{
Data[i] = DHT_Read_Byte();
}
Delay_us(50);
}
uint32_t sum=Data[0]+Data[1]+Data[2]+Data[3];
if((sum)==Data[4]) return 1;
else return 0;
}
总结
DHT11的协议是比较繁琐的,需要大家仔细观看,重点在于要结合DHT11参考手册进行详细阅读,还要懂一定的编程技巧和原理,希望大家能仔细琢磨和观看,每看一次都是对自己编程能力的提升。