从零开始制作一个基于STM32和ESP8266-01S的智能时钟(2)DHT11温湿度传感器模块

本文介绍了如何使用STM32单片机与DHT11温湿度传感器进行通信,包括DHT11模块的工作原理、通信协议分析以及具体的代码实现。在通信过程中,通过单总线协议,STM32控制IO口产生特定时序与DHT11交互,接收数据并进行校验,确保数据准确性。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:
本项目需要基础的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左右再测量。应该就能够得到以下结果
在这里插入图片描述

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值