引言
通过查找多方数据,缺少对这方面的解释,或者感觉和我想的不合的,所有写了这篇博文。
本篇博文不详细讲解DHT11是什么?或者有什么用,毕竟这种东西网上一大把,本文更注重从其DHT11的实现原理,到代码实现发表一些自己微小的想法和见解。
难点
各部分时间的不确定性
部分内容图片为参考其他,如有意见,请私聊
DHT11硬件电路
下面是UART1的原理图(我暂时还没有写UART方面的博客,有时间再补充)
DHT11原理
DHT11就个人看来主要分为四个阶段
1.唤醒阶段
2.响应阶段
3.数据传输阶段
4.结束阶段
先来看一张总图,然后在一步一步阐述我的观点
下面的t1和t多少均来自此图
唤醒阶段
主要是pc去唤醒DHT11(为输出模式)
上图深色部分为唤醒阶段
t1和t2为唤醒阶段,主要用于pc去唤醒DHT11功能。
为什么需要唤醒呢?
(先吐槽一下,DHT11好像是国产,勤俭节约---->美德,但是误差还是有点大的)
需要唤醒是没有唤醒期间处于低功耗阶段,节省输出。
唤醒过程
首先主机发送开始信号,即:拉低数据线,保持 t1(至少 18ms低电平)时间,用于作为唤醒信号,然后拉高数据线 t2(20~40us)时间,用于等待DHT11有时间去接收唤醒(即检测到唤醒信号)
(有人会问20-40us,到底多少?如果还没有唤醒呢,额,看代码注释)响应阶段
主要获取DHT11的状态,所以为输入模式
图中浅色部分为响应阶段
对应第一个图中的t3和t4
响应阶段分为2点,一个低电平(80us),然后拉高(80us),也就是在主机释放总线(也就是转为输出,交给外部电路)后,DHT11要有一个将电平拉低,然后拉高的一个变化,用来告诉主机,我成功被唤醒了,准备接受俺的数据吧(这种通信总让我情不自禁想到TCP的相关内容)数据传输阶段
数据传输阶段分为两点,让我先讲stm32是怎么判断接收的数据是高电平还是低电平。
主要是通过数据传输中高电平的持续时间来判断高低电平的。
如下图
低电平在这个波中高电平持续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加起来的和。如果前面四个加起来等于校验和,说明数据传输成功,否则说明数据传输有问题。
代码实现
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;
}
结果
本次在串口工具实现,实际上在其他元器件显示一样可以,因为两者的交点,仅仅只是一个获取,然后再主函数送到uart。
补充说明:
DHT11的误差对于要求高的条件下是不精确的,所有也不要太对数值要求过高。
结语
如果有什么疑惑或者其他的,可以留下评论或者私信一起讨论,如果感觉对你有那么一点点帮助,希望可以右上角点个小赞,谢谢!