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

cpp 复制代码
#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

cpp 复制代码
**#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 20ms
	GPIO_ResetBits(DHT11_GPIO,DHT11_PIN);
	Delay_us(20000);
	
	//发1 30ms
	GPIO_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

cpp 复制代码
#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);

忽略掉这个,默认引脚是上拉输入,也会进行数据采集,因此出现了采集错误的现象,这个很难差错,希望大家能够避免这个坑。(重要)

cpp 复制代码
//接收应答 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,让我们的传感器完成加热,确保数据采集的正确性。

那么这个第一个中断就势必要关闭

思路

  1. 先开定时器,不使能更新中断
  2. 清除一次 UIF 标志
  3. 再使能更新中断并开 NVIC

代码演示:

cpp 复制代码
// 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); 为例

cpp 复制代码
//等待引脚电平被拉低(等待DHT11的应答)
while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN));

如果一直是高电平,就会阻塞,会影响程序的正常执行。

因此这里我们可以加入判断

cpp 复制代码
	//等待引脚电平被拉低(等待DHT11的应答)
	int time = 0;
	while(GPIO_ReadInputDataBit(DHT11_GPIO,DHT11_PIN))
	{
		time++;
		Delay_us(1);
		if(time < 100)
		{
			return 0;
		}
	}

这样配置即可避免阻塞的情况发生。

结束

代码重在练习!

代码重在练习!

代码重在练习!

今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏加关注,谢谢大家!!!

相关推荐
森焱森1 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白2 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D2 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术5 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt6 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘6 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang6 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n8 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件
Despacito0o11 小时前
ESP32-s3摄像头驱动开发实战:从零搭建实时图像显示系统
人工智能·驱动开发·嵌入式硬件·音视频·嵌入式实时数据库
门思科技12 小时前
设计可靠 LoRaWAN 设备时需要考虑的关键能力
运维·服务器·网络·嵌入式硬件·物联网