STM32实战指南——DHT11温湿度传感器驱动开发与避坑指南
知识点1【DHT11的概述】
1、概述
DHT是一款温湿度一体化的数字传感器(无需AD转换)。
2、驱动方式
通过单片机等微处理器简单的电路连接就能实时采集本地湿度和温度。DHT11与单片机之间采用单总线进行通信,仅需要一个IO口。
相对于单片机是片下外设,因此配置的时候无需使用复用方式,使用通用方式即可。
3、DHT11的数据结构
数据长度:40位
8bit的湿度整数,8bit的湿度小数,8bit的温度整数,8bit的温度小数+8bit的校验和位
知识点2【DHT11的使用】
DHT11一次通讯时间最大3ms,主机连续采样间隔建议不小于100ms
以上是理论,但实际使用中有所不同:
我们在实际使用中,连续采样间隔建议是 1s以上
补充
1、第一次采样前,我们打开DHT11后,即我们下面介绍的复位(void DHT11_Reset(void);
),需要等待2s以上,因为开启需要一个过程:DHT11 上电后内部有加热片和采集电路,需要约 1–2 s 的时间才能稳定到正常工作温度和电压;如果太快去读,传感器还没“热起来”,数据就不准。这里大家注意一下。
2、复用是针对于片上外设的,片下外设用通用模式即可
1、复位信号
①、DHT的复位信号,主机掌握数据总线
(1)拉低 至少18ms
(2)再拉高20-40us
②、DHT的响应信号,从机掌握数据总线
(1)拉低 40-50us
(2)再拉高40-50us
注意:此时DHT11对主机复位信号的响应信号
在DHT11中,数据(0和1)都是低电平开始的
2、DHT11表示1的方法
(1)拉低12-14us
(2)拉高116-118us
3、DHT11表示0的方法
(1)拉低12-14us
(2)拉高26-28us
因此我们这里区别 0 和 1 的方法就是利用的高电平的持续时间不同,利用这个时间差来判断是0还是1。
知识点3【代码演示】
main.c
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "delay.h"
#include "usart.h"
#include "DHT11.h"int flag = 0;int main(void)
{u8 data[5] = {0};//有限级组的配置Systick_Init(72);Usart1_Init(9600);while(1){ Delay_us(2000000);DHT11_RcvData(data);}
}
DHT11.c
**#include "DHT11.h"//端口结构体声明
GPIO_InitTypeDef GPIO_DHT11_InitStruct;//端口初始化
void DHT11_GPIO_Init(void)
{//开启时钟RCC_APB2PeriphClockCmd(DHT11_CLOCK,ENABLE);//配置GPIO引脚GPIO_StructInit(&GPIO_DHT11_InitStruct);GPIO_DHT11_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_DHT11_InitStruct.GPIO_Pin = DHT11_PIN;GPIO_DHT11_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOD,&GPIO_DHT11_InitStruct);}//复位
void DHT11_Reset(void)
{//时钟+模式配置DHT11_GPIO_Init();//复位信号发送//发0 20msGPIO_ResetBits(DHT11_GPIO,DHT11_PIN);Delay_us(20000);//发1 30msGPIO_SetBits(DHT11_GPIO,DHT11_PIN);Delay_us(30);//切换模式:上拉输入模式,准备接收DHT11应答GPIO_DHT11_InitStruct.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(GPIOD,&GPIO_DHT11_InitStruct);//等待引脚电平被拉低(等待DHT11的应答)while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN));
}//应答
u8 DHT11_Ack(void)
{int flag = 0;while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == 0){Delay_us(1);flag++;if(flag == 100){return 0;}}flag = 0;while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == 1){Delay_us(1);flag++;if(flag == 100){return 0;}}return 1;
}//接收应答 5个byte
void DHT11_RcvData(u8 *data)
{int i;DHT11_Reset();if(DHT11_Ack() == 1){for(i = 0; i < 5;i++){data[i] = DHT11_RcvByte();}if(data[4] == data[0] + data[1] + data[2] + data[3] ){printf("湿度是:%u.%u 温度是:%u.%u\\n",data[0],data[1],data[2],data[3]);}else{printf("采集错误\\n");}}
}//接收应答 1个byte
u8 DHT11_RcvByte(void)
{int i;u8 data = 0;for(i = 0;i < 8;i++){data <<= 1;data |= DHT11_RcvBit();}return data;
}//接收应答 1个bit
u8 DHT11_RcvBit(void)
{while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == SET);if(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == RESET){while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == RESET);Delay_us(40);if(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == SET){return 1;}else{return 0;}}return 1;
}**
DHTT11.h
#ifndef _DHT11_H_
#define _DHT11_H_
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "delay.h"
#include "usart.h"
//这里我使用的是PD0 GPIOD GPIO_Pin_0
#define DHT11_GPIO GPIOD
#define DHT11_PIN GPIO_Pin_0
#define DHT11_CLOCK RCC_APB2Periph_GPIOD//端口初始化
void DHT11_GPIO_Init(void);//复位
void DHT11_Reset(void);//接收应答 5个byte
void DHT11_RcvData(u8 *data);//接收应答 1个byte
u8 DHT11_RcvByte(void);//接收应答 1个bit
u8 DHT11_RcvBit(void);//应答处理 返回0没收到正确应答,返回1接收到正确应答
u8 DHT11_Ack(void);
#endif
usart.c 和 delay.c我这里不再展示,前面的课程配置过很多次了。
代码运行结果
知识点4【代码所犯错误】
1、在复位的时候,是先拉低18ms以上,写代码途中配置成us。
**2、**在下面代码中,while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == SET);
忽略掉这个,默认引脚是上拉输入,也会进行数据采集,因此出现了采集错误的现象,这个很难差错,希望大家能够避免这个坑。(重要)
//接收应答 1个bit
u8 DHT11_RcvBit(void)
{while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == SET);if(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == RESET){while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == RESET);Delay_us(40);if(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN) == SET){return 1;}else{return 0;}}return 1;
}
补充拓展
1、使用定时器进行周期性的采样
但是我们知道,在调用TIM_Cmd(TIMx, ENABLE);
的时候,是以下的流程:
- 重新装载预分频器
- 如果 ARR 预装载打开,还会把新 ARR 写入实际计数寄存器
- 同时置位更新中断标志位 UIF
这个过程就说明,触发一次update中断。中断函数中执行的是数据采集工作。
这时候会出现另一个问题
我们上面提过:第一次采集的空闲状态需要至少2s,让我们的传感器完成加热,确保数据采集的正确性。
那么这个第一个中断就势必要关闭
思路
- 先开定时器,不使能更新中断;
- 清除一次 UIF 标志;
- 再使能更新中断并开 NVIC。
代码演示:
// 1. 配置好 TIMx 的时基单元(TIM_TimeBaseInit)……
// 2. 开启时钟、初始化 NVIC 中断优先级(但不使能)// 不开中断,先使能定时器产生一次 UEV 并清掉标志
TIM_Cmd(TIMx, ENABLE);
// 清除可能残留的 UIF 标志
TIM_ClearFlag(TIMx, TIM_FLAG_Update);// 现在再开更新中断
TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE);
NVIC_EnableIRQ(TIMx_IRQn);
主要内容我讲完了,这里实现定时器 定时触发 数据采集中断就很简单了,大家可以当作一个小练习,自己尝试一下。
2、代码健壮性的补充
以我们在void DHT11_Reset(void);
为例
//等待引脚电平被拉低(等待DHT11的应答)
while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN));
如果一直是高电平,就会阻塞,会影响程序的正常执行。
因此这里我们可以加入判断
//等待引脚电平被拉低(等待DHT11的应答)int time = 0;while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN)){time++;Delay_us(1);if(time < 100){return 0;}}
这样配置即可避免阻塞的情况发生。
结束
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏加关注,谢谢大家!!!