提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
本项目需要基础的stm32单片机知识,这里我推荐
链接:https://www.bilibili.com/video/BV1th411z7sn?p=1&vd_source=e9ab6ae9ee7c74bb73c9334f2da0a743
如果不想看那么多,看到4-2 OLED显示屏就差不多。我使用的是他的OLED基本例程。
提示:以下是本篇文章正文内容,下面案例可供参考
一、DHT11温湿度传感器模块
DHT11数字温湿度传感器是一款能够检测温湿度的复合传感器,其内置一个测温元件、一个电阻式感湿元件和一个单片机。
它使用单总线通信,有效范围、实物、引脚定义如图。本节我们只需要将它的DATA引脚接到STM32的某个IO口上,使用IO口写一个通信时序与它通信,即可得到我们想要的温湿度。这里我选择的是GPIOA的GPIO_Pin_8口。
如想看更多原理可看https://blog.csdn.net/lin5103151/article/details/103193089
二、DHT11通信方式
1.通信图分析
下图为DATA线的高低电平变化和DHT11的通信协议。如图所示,要想让DHT11模块传输数据,需要主机IO口先处于默认高电平状态,然后把IO口拉低至少18ms,再拉高20-40ms,然后就可以把IO口的控制权交由DHT11模块控制。DHT先会将DATA线拉低80us代表响应,然后将DATA线拉高80us代表它要开始传输数据了。
DHT11的1和0并不是用普通的高低电平代表的,它是使用二段不同的时序代表0和1。
数字0信号时序如下图所示,在DHT11拉高80usDATA线后,它会将DATA线拉低50us,代表它要传输1bit数据,然后它会将DATA线拉高26us-28us代表这一bit为数字‘0’。然后再次拉低DATA线代表下一bit要开始传输。
数字1信号时序如下图所示,与数字0的时序基本一致,唯一不同的是高电平的时间变为了70us,所以我们通过判断维持高电平的时间,即可知道传输回来的1bit为0还是1。
2.代码分析
根据上面的解析,我们得知IO口需要控制输出电平并且读取输入电平并且不需要较大的驱动能力,所以这里我使用了IO口的开漏输出模式,本来按理来说应该要加上上拉电阻作为默认高电平状态,但是经测试发现不加也可以,应该是DHT11模块内部DATA引脚已经加上了上拉电阻(这里我初始化IO口后读取了一下输入电平发现默认电平是1)。然后和I2C通信一样,IO口配置为输出模式,也是可以读取到输入电平的,即STM32的GPIO输出模式下不会关闭输入接口。
这里我看过别人的方法,有的是在一开始配置IO口为输出模式,后续接收的时候重新配置为输入模式,但是我觉得没必要因为STM32的GPIO输出模式下也可以读取输入电平。但为了兼容也写了一个可以修改IO口模式的初始化函数。
/*DHT11初始化函数,把GPIOA的GPIO_Pin_8口作为DATA的接口,并且将IO口模式设置为输入值
,方便更改IO口的模式*/
void DHT11_Init(GPIOMode_TypeDef GPIO_Mode)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET);
GPIO_Init(GPIOA,&GPIO_InitStruct);
}
上面提到主机发送开始信号后,DHT先会将DATA线拉低80us,随后将DATA线拉高80us,但是经过实测发现,两个时间都不是80us,我买的模块分别是35、36us左右(可以使用下面的函数进行测量,输出time就可得知多少us)。
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==0)
{
time_0++;
Delay_us(1);
}
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==1)
{
time_1++;
Delay_us(1);
}
但是我们不能保证每次都是这个时间,所以这里我直接在主机输出20us高电平后,通过判断电平是否被拉低来判断DHT11是否响应,如果被拉低了,就证明DHT11响应了。然后使用while循环等待DHT11拉低的80us和拉高的80us(因为不一定是80us,所以直接使用while循环等待,为了避免循环卡死的情况,应该加上溢出时间,超过时间就不等待了。(一开始我也是直接使用延时的方式读取数据,后面发现数据总是错的就发现了这个问题,并改用while循环等待的方式))。等待完后,DHT11开始发送40bit的时序。数据格式为,并且是高位先行。
8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和
同样的我这里接收1bit数据的方法是先while循环等待DHT11拉低的50us(不一定是50us),再延时30us,延时30us后判断DATA线的高低电平,根据上面的高低电平时序的差异,如果这时候还为高电平,证明这1bit数据为1,如果为低电平证明这1bit数据为0。判断完后如果数据为1还需要使用while循环等待DHT11剩下拉高的时间,然后再次循环接收下一bit。
接收完成后需要使用第5byte数据校验一下接收的数据是否接收正确。校验方式如下
“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据”相加所得结果的末8位等于8bit校验和
校验正确就可以输出结果在OLED上了。实测发现湿度的小数部分一直为0,说明DHT11模块的湿度只精确到个位,温度的小数部分取值范围为0~9,所以小数显示1位即可。
uint8_t Temp_Humidity_Data[5]; //定义接收温湿度数据的数组
/*获取温度、湿度并将其显示在OLED上函数。*/
void DHT11_Get_Temp_Humidity(void)
{
uint8_t i, j; //定义循环变量
uint32_t timeout; //定义时间溢出变量
DHT11_Init(GPIO_Mode_Out_OD); /*配置IO口为开漏输出模式*/
GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET); /*先将DATA线置为默认高电平状态*/
GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_RESET); /*将DATA线拉低20ms*/
Delay_ms(20);
GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET); /*再将DATA线拉高20us*/
Delay_us(20);
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == 0) /*判断DHT11是否响应*/
{
timeout = 10000;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==0)if (timeout == 0) break; /*等待DHT响应信号*/
timeout = 10000;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==1)if (timeout == 0) break; /*等待DHT响应信号*/
for(j=0; j<5; j++) /*接收5byte数据,按顺序存到变量Temp_Humidity_Data数值里*/
{
for(i=0; i<8; i++) /*接收1byte数据*/
{
timeout = 10000;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==0)if (timeout == 0) break; /*等待1bit数据开始信号*/
Delay_us(30); /*在DATA高电平延时30us*/
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == 1) /*如果30us后,DATA仍为高电平,表明数据为1*/
{
Temp_Humidity_Data[j] <<= 1;
Temp_Humidity_Data[j] |= 1;
timeout = 10000;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==1)if (timeout == 0) break; /*数据为1时,等待剩余的高电平*/
}
else /*如果30us后,DATA为低电平,表明数据为0*/
{
Temp_Humidity_Data[j] <<= 1;
Temp_Humidity_Data[j] |= 0;
}
}
}
}
if(Temp_Humidity_Data[0]+Temp_Humidity_Data[1]+Temp_Humidity_Data[2]+Temp_Humidity_Data[3] == Temp_Humidity_Data[4]) /*校验数据是否正确*/
{
OLED_ShowString(3,1,"H:");
OLED_ShowNum(3,3,Temp_Humidity_Data[0],2);
OLED_ShowString(3,5,".");
OLED_ShowNum(3,6,Temp_Humidity_Data[1],1);
OLED_ShowString(3,7,"%");
OLED_ShowString(3,9,"T:");
OLED_ShowNum(3,11,Temp_Humidity_Data[2],2);
OLED_ShowString(3,13,".");
OLED_ShowNum(3,14,Temp_Humidity_Data[3],1);
OLED_ShowString(3,15,"C");
}
}
3.完整代码
DHT11.h
#ifndef __DHT11_H
#define __DHT11_H
void DHT11_Init(GPIOMode_TypeDef GPIO_Mode);
void DHT11_Get_Temp_Humidity(void);
#endif
DHT11.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "oled.h"
uint8_t Temp_Humidity_Data[5]; //定义接收温湿度数据的数组
/*DHT11初始化函数。把GPIOA的GPIO_Pin_8口作为DATA的接口,并且将IO口模式设置为输入值的模式,方便更改*/
void DHT11_Init(GPIOMode_TypeDef GPIO_Mode)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET);
GPIO_Init(GPIOA,&GPIO_InitStruct);
}
/*获取温度、湿度并将其显示在OLED上函数。*/
void DHT11_Get_Temp_Humidity(void)
{
uint8_t i, j; //定义循环变量
uint32_t timeout; //定义时间溢出变量
DHT11_Init(GPIO_Mode_Out_OD); /*配置IO口为开漏输出模式*/
GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET); /*先将DATA线置为默认高电平状态*/
GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_RESET); /*将DATA线拉低20ms*/
Delay_ms(20);
GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET); /*再将DATA线拉高20us*/
Delay_us(20);
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == 0) /*判断DHT11是否响应*/
{
timeout = 10000;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==0)if (timeout == 0) break; /*等待DHT响应信号*/
timeout = 10000;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==1)if (timeout == 0) break; /*等待DHT响应信号*/
for(j=0; j<5; j++) /*接收5byte数据,按顺序存到变量Temp_Humidity_Data数值里*/
{
for(i=0; i<8; i++) /*接收1byte数据*/
{
timeout = 10000;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==0)if (timeout == 0) break; /*等待1bit数据开始信号*/
Delay_us(30); /*在DATA高电平延时30us*/
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == 1) /*如果30us后,DATA仍为高电平,表明数据为1*/
{
Temp_Humidity_Data[j] <<= 1;
Temp_Humidity_Data[j] |= 1;
timeout = 10000;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==1)if (timeout == 0) break; /*数据为1时,等待剩余的高电平*/
}
else /*如果30us后,DATA为低电平,表明数据为0*/
{
Temp_Humidity_Data[j] <<= 1;
Temp_Humidity_Data[j] |= 0;
}
}
}
}
if(Temp_Humidity_Data[0]+Temp_Humidity_Data[1]+Temp_Humidity_Data[2]+Temp_Humidity_Data[3] == Temp_Humidity_Data[4]) /*校验数据是否正确,正确则在OLED上显示*/
{
OLED_ShowString(3,1,"H:");
OLED_ShowNum(3,3,Temp_Humidity_Data[0],2);
OLED_ShowString(3,5,".");
OLED_ShowNum(3,6,Temp_Humidity_Data[1],1);
OLED_ShowString(3,7,"%");
OLED_ShowString(3,9,"T:");
OLED_ShowNum(3,11,Temp_Humidity_Data[2],2);
OLED_ShowString(3,13,".");
OLED_ShowNum(3,14,Temp_Humidity_Data[3],1);
OLED_ShowString(3,15,"C");
}
}
总结
确保接线正确,然后在main.c中调用DHT11_Get_Temp_Humidity();注意上电之后不能立刻使用DHT11测量,需要等待1s左右再测量。应该就能够得到以下结果