引言

通过查找多方数据,缺少对这方面的解释,或者感觉和我想的不合的,所有写了这篇博文。
本篇博文不详细讲解DHT11是什么?或者有什么用,毕竟这种东西网上一大把,本文更注重从其DHT11的实现原理,到代码实现发表一些自己微小的想法和见解。
难点
各部分时间的不确定性

部分内容图片为参考其他,如有意见,请私聊

DHT11硬件电路

grafana 制作温湿度看板 温湿度读取流程图_grafana 制作温湿度看板


下面是UART1的原理图(我暂时还没有写UART方面的博客,有时间再补充)

grafana 制作温湿度看板 温湿度读取流程图_DHT11_02

DHT11原理

DHT11就个人看来主要分为四个阶段
1.唤醒阶段
2.响应阶段
3.数据传输阶段
4.结束阶段

先来看一张总图,然后在一步一步阐述我的观点

下面的t1和t多少均来自此图

grafana 制作温湿度看板 温湿度读取流程图_DHT11_03


唤醒阶段

主要是pc去唤醒DHT11(为输出模式)

grafana 制作温湿度看板 温湿度读取流程图_grafana 制作温湿度看板_04


上图深色部分为唤醒阶段

t1和t2为唤醒阶段,主要用于pc去唤醒DHT11功能。

为什么需要唤醒呢?

(先吐槽一下,DHT11好像是国产,勤俭节约---->美德,但是误差还是有点大的)

需要唤醒是没有唤醒期间处于低功耗阶段,节省输出。

唤醒过程

首先主机发送开始信号,即:拉低数据线,保持 t1(至少 18ms低电平)时间,用于作为唤醒信号,然后拉高数据线 t2(20~40us)时间,用于等待DHT11有时间去接收唤醒(即检测到唤醒信号)

(有人会问20-40us,到底多少?如果还没有唤醒呢,额,看代码注释)响应阶段

主要获取DHT11的状态,所以为输入模式

grafana 制作温湿度看板 温湿度读取流程图_UART_05


图中浅色部分为响应阶段

对应第一个图中的t3和t4

响应阶段分为2点,一个低电平(80us),然后拉高(80us),也就是在主机释放总线(也就是转为输出,交给外部电路)后,DHT11要有一个将电平拉低,然后拉高的一个变化,用来告诉主机,我成功被唤醒了,准备接受俺的数据吧(这种通信总让我情不自禁想到TCP的相关内容)数据传输阶段

数据传输阶段分为两点,让我先讲stm32是怎么判断接收的数据是高电平还是低电平。

主要是通过数据传输中高电平的持续时间来判断高低电平的。

如下图

grafana 制作温湿度看板 温湿度读取流程图_UART_06


grafana 制作温湿度看板 温湿度读取流程图_DHT11_07


低电平在这个波中高电平持续26-28us,而高电平则持续70us,我们可以通过检测高电平与低电平之间的40us的电平来判断(尽量选中间点,毕竟这里的声明26-28us或者70us不是绝对)

然后让我们来关注一下这个波前面的50us的低电平,主要有两个作用,一个作为数据传输的开始,用于与响应的高电平相区分,告诉芯片,我准备好要发数据了,记得重点关注后面的高电平的持续时间。另一个就是用于辅助区分高低电平,因为你实际检测的是26-28us之后的电平,实际上也就有可能是这个50us的低电平。
至于为什么是50us呢?
我想可能是设计者为了尽可能的扩大检测区间,70-(26-28)不就是是40us吗,然后还要留点时间给芯片检测作为下一个bit到来标志电平。
结束阶段
结束是芯片,也就是你决定什么时候结束,所以是转换为输出模式
度过50us的缓冲区,然后芯片主动拉高总线,将其置为空闲模式即可,实际上结束也是开始。

数据传输的数据

一次完整的数据传输为40bits,也就是32位机的5个字节(byte),假如数据为buf[0]~buf[4],每个buf为一个unsigned char,或者其他8位的数据类型,buf[0]就是湿度的整数数据,buf[1]就是其湿度的小数部分,然后buf[2]就是温度的整数部分,buf[3]就是其温度的小数部分,buf[4]是校验和,也就是前面4个buf加起来的和。如果前面四个加起来等于校验和,说明数据传输成功,否则说明数据传输有问题。

grafana 制作温湿度看板 温湿度读取流程图_grafana 制作温湿度看板_08

代码实现

1.uart和dht11代码块

#include<stdio.h>
#include"uart.h"
#include"delay.h"
#include"stm32f4xx.h"

void uart1_init(void)//uart初始化
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;//记得模式为复用
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_10;//为位控可以或
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);

	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9,GPIO_AF_USART1);//功能复用初始化
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10,GPIO_AF_USART1);//功能复用初始化

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//uart时钟使能
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate=9600;//波特率为9600
	USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//没有硬件流控
	USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//为全双工,可接收与发送
	USART_InitStruct.USART_Parity=USART_Parity_No;//没有校验
	USART_InitStruct.USART_StopBits=USART_StopBits_1;//停止位设置为1位
	USART_InitStruct.USART_WordLength=USART_WordLength_8b;//数据位为8位
	USART_Init(USART1, &USART_InitStruct);

	USART_ITConfig(USART1,USART_IT_RXNE, ENABLE);//uart1中断使能

	USART_Cmd(USART1,ENABLE);//使能串口uart1
}

void dht11_init_out(void)//DHT11输出初始化
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOI,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;//无上下拉,硬件已经上拉
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOI,&GPIO_InitStruct);
	GPIO_WriteBit(GPIOI,GPIO_Pin_11,Bit_SET);//默认高电平
}
void dht11_out(int x)//输出电平为@x
{
	if(Bit_SET==x)
	{
		GPIO_WriteBit(GPIOI,GPIO_Pin_11,Bit_SET);//对I组11号引脚写入高电平
	}
	else if(Bit_RESET==x)
	{
		GPIO_WriteBit(GPIOI,GPIO_Pin_11,Bit_RESET);//对I组11号引脚写入低电平
	}
}
void dht11_init_in(void)//串口DHT11输入初始化
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOI,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN;
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOI,&GPIO_InitStruct);
}

int  dht11_in_read(void)//读取DHT11串口电平
{
	return GPIO_ReadInputDataBit(GPIOI,GPIO_Pin_11);
}
void dht11_rst(void)//DHT11起始准备
{
	dht11_init_out();//设置为输出模式
	dht11_out(Bit_RESET);
	delay_time_ms(20);//将电平拉低20ms保证DHT11能够检测到起始信号
	dht11_out(Bit_SET);
	delay_time_us(30);//拉高电平30us等待DHT11检测
}

int dht11_check(void)//检测DHT11的响应信号,成功返回0,失败返回1
{
	int k=0;
	dht11_init_in();//设置串口为输入模式,用于获取电平
	while(dht11_in_read()&&k<100)//判断电平是否为低,并且等待低电平的到来,用于判断第一个响应电平
	{
		k++;
		delay_time_us(1);
	}
	if(k>=100)//等待时间超过100us,低电平未到,第一个响应电平未到
	{
		return 1;//未检测到
	}
	else
	{
		k=0;//检测到第一个电平,进行下一个电平响应检测
	}
	while(!dht11_in_read()&&k<100)//用于过滤上次的低电平(即上一个响应电平,并且等待下一个响应电平到来)
	{
		k++;
		delay_time_us(1);
	}
	if(k>=100)//超时,第二响应信号高电平未到
	{
		return 1;//未检测到
	}
	return 0;
}
int dht11_read_bit(void)//读取一个bit位的数据,作为返回值
{
	int k=0;
	while(dht11_in_read())//过滤第二个响应判断信号,当上一个bit为1时辅助过滤上一个bit
	{
		k++;
		delay_time_us(1);
	}
	k=0;
	while(!dht11_in_read())//辅助过滤上一个bit,当其为0时,也用于电平判断前的低电平等待
	{
		k++;
		delay_time_us(1);
	}
	delay_time_us(40);//跳到高低电平判断期间,多的或者少的交给上面两个while
	if(dht11_in_read())//获取电平,高为1,di为0
	{
		return 1;
	}
	return 0;
}
int dht11_read_byte(void)//读取一个字节
{
	int i,b_data;
	b_data=0;
	for(i=0;i<8;i++)
	{
		b_data<<=1;
		b_data|=dht11_read_bit();
	}
	return b_data;
}
/*
	dht11_read_data:读取所有信息
	@temp:(特意查的字典,温度)保存温度值
	@humi:保存湿度的值
	成功返回0
	失败返回1
*/
int dht11_read_data(int *temp,int *humi)
{
	uint8_t buf[5];//用于存储获取的信息
	int i;
	dht11_rst();//DHT11的唤醒
	if(dht11_check()==0)//DHT11检测,成功为0,失败1
	{
		for(i=0;i<5;i++)
		{
			buf[i]=dht11_read_byte();
		}
		if(buf[0]+buf[1]+buf[2]+buf[3]==buf[4])//判断校验是否一致
		{
			*humi=buf[0];//保存湿度
			*temp=buf[2];//保存温度
		}
		else
		{
			return 1;
		}
	}
	else
	{
		return 1;
	}
	return 0;	
}

2.主函数部分

#include<stdio.h>
#include"led.h"
#include"key.h"
#include"stm32f4xx.h"
#include"stm32f4xx_gpio.h"
#include"beep.h"
#include"delay.h"
#include"uart.h"
#include"at24c02.h"

unsigned char data1[20];
int data1_len=0;
void USART1_IRQHandler(void)//uart1中断函数
{
	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)//判断是否是有数据来(的中断)
	{
		data1[data1_len++]=USART_ReceiveData(USART1);//存入data1中,且等待下一个数据
	}
	USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除中断标志位
}

int fputc(int c, FILE *stream)//输出
{
	USART_SendData(USART1, c);//发送c
	while (USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);//等待发送完成

	return c;
}
int main()
{
	uart1_init();//uart1初始化
	int c,s;
	c=s=0;	
	int t=0;
	while(1)
	{
		if(t%10==0)//1s检测一次
		{
			dht11_read_data(&s, &c);//获取信息
			printf("%d %d",s,c);//发送给uart1,在串口工具显示
		}
		delay_time_ms(100);
		t++;
	}
	return 0;
}

结果

grafana 制作温湿度看板 温湿度读取流程图_UART_09


本次在串口工具实现,实际上在其他元器件显示一样可以,因为两者的交点,仅仅只是一个获取,然后再主函数送到uart。

补充说明:

DHT11的误差对于要求高的条件下是不精确的,所有也不要太对数值要求过高。

grafana 制作温湿度看板 温湿度读取流程图_stm32_10

结语

如果有什么疑惑或者其他的,可以留下评论或者私信一起讨论,如果感觉对你有那么一点点帮助,希望可以右上角点个小赞,谢谢!