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 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

#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,让我们的传感器完成加热,确保数据采集的正确性。

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

思路

  1. 先开定时器,不使能更新中断
  2. 清除一次 UIF 标志
  3. 再使能更新中断并开 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;
		}
	}

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

结束

代码重在练习!

代码重在练习!

代码重在练习!

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

<think>首先,用户正在询问有关使用STM32F1微控制器驱动DHT11温湿度传感器的代码示例。他们指定了“无分段”,意思是响应不应被分割,所以我应该在一个连贯的块中提供它。但根据系统指令,我需要使我的回答结构清晰并逐步进行,所以我将确保它是组织化的,但免分段标记。系统指令提醒:-使用行内数学表达式,例如:$x^2$-独立公式使用$$...$$并单独成段-使用中文回答-在最后自然添加引用标识-生成相关问题用户正在寻求引用中的特定信息:-引用[1]:STM32入门教程:温湿度传感器应用。-引用[2]:DHT11温湿度传感器的工作流程。-引用[3]:STM32收集DHT11数据并通过WiFi发送。-引用[4]:DHT11驱动代码细节,包括时序和数据结构。用户的需求是:“我想查找关于使用STM32F1微控制器DHT11温湿度传感器进行集成的相关信息或代码示例请问dht11温湿度传感器stm32f1驱动代码示例不分段”所以,我需要提供DHT11STM32F1的驱动代码示例。来自引用[4]的关键细节:-DHT11使用单总线通信。-数据传输为40位:湿度整数、湿度小数、温度整数、温度小数、校验和。-时序:主机发送起始信号(拉低>18ms,然后拉高20-40us),然后DHT11响应(拉低40-50us,然后拉高40-50us),最后数据位。-每个数据位:0位是低电平26-28us后高电平,1位是低电平26-28us后高电平70us。参考引用[3]:-DHT11连接到STM32F103,例如PA1引脚。-硬件连接:VCC到3.3V,GND到GND,DATA到GPIO引脚。引用[2]简要描述了DHT11的工作流程。我将创建一个简单的驱动代码示例使用STM32F1,基于引用[4]。在我的回答中:-用中文回复。-确保代码块格式正确。-添加引用在结束时。-最后生成相关问题。既然用户说“不分段”,我会免使用分节标题,但保持内容流畅自然。系统说“回答结构清晰”,所以我可以有引言、代码和解释,但不要明确标明章节。结构:1.简要介绍。2.代码示例。3.简要说明代码如何工作。4.在结尾添加参考文献。5.生成相关问题。对于数学:如果有公式,使用$...$,例如校验和计算在引用[4]中提到:湿度+温度=校验和。所以如果提及,我会这样使用。在引用[4]中:湿度=byte4.byte3,但这似乎是打字错误;应为byte3和byte4用于湿度(整数和小数),byte1和byte2用于温度?在文本中:"8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据",然后"湿度=byte4.byte3"——这似乎不一致。根据标准DHT11文件,顺序是:byte0是湿度整数,byte1是湿度小数,byte2是温度整数,byte3是温度小数,byte4是校验和。但在描述中:"湿度=byte4.byte3=45.0",可能是错误。常见情况是湿度整数、湿度小数、温度整数、温度小数、校验和。我将假设标准顺序。在引用[4]中:它说"8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和",然后在计算中"湿度=byte4.byte3",我认为这是一个错误;更可能的是byte0是湿度整数,等等。我会在代码中使用标准方式。代码大纲:-定义GPIO引脚(例如PA1)。-设置引脚为输出模式发送起始信号。-发送起始:拉低20ms,然后拉高20-40us。-然后设置引脚为输入模式,读取响应。-响应:等待低电平约40-50us,然后高电平。-然后读取40位数据。-对于每一位:等低电平(约50us),然后等高电平,高位时间短于30us表示0,长于30us表示1(通常)。-然后计算温度和湿度,验证校验和。-校验和:byte0+byte1+byte2+byte3应该等于byte4。使用HAL库或标准外设库。由于用户没有指定,我将使用通用的寄存器级访问或简单示例。既然是一个示例,我将使用标准外设库风格。最后,添加参考文献:[^1],[^2],[^3],[^4]生成相关问题。</think> 以下提供DHT11温湿度传感器STM32F1微控制器上的驱动代码示例(不分段),使用STM32标准外设库实现。示例代码基于单总线通信协议实现数据采集,包括引脚配置、时序控制和数据解析。DHT11连接到STM32F103的PA1引脚(可修改),并假设系统时钟为72MHz。 ```c #include "stm32f10x.h" #define DHT11_GPIO_PORT GPIOA #define DHT11_GPIO_PIN GPIO_Pin_1 #define DHT11_RCC RCC_APB2Periph_GPIOA // DHT11数据结构 typedef struct { uint8_t humidity_integer; // 湿度整数部分 uint8_t humidity_decimal; // 湿度小数部分 uint8_t temperature_integer; // 温度整数部分 uint8_t temperature_decimal; // 温度小数部分 uint8_t checksum; // 校验和 } DHT11_Data; // 配置GPIO引脚模式 void DHT11_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(DHT11_RCC, ENABLE); GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure); GPIO_SetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN); // 初始高电平 } // 设置引脚为输入模式 void DHT11_Set_Input(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入 GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure); } // 设置引脚为输出模式 void DHT11_Set_Output(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure); } // 读取单个数据位(返回0或1) uint8_t DHT11_Read_Bit(void) { uint32_t timeout = 10000; // 超时计数 while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == Bit_SET); // 等待低电平开始 for (volatile int i = 0; i < 10; i++); // 延时约26us if (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == Bit_RESET) { return 0; // 低电平持续时间短,表示位0 } else { while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == Bit_SET); // 等待位1结束 return 1; // 高电平持续时间长,表示位1 } } // 读取完整40位数据 uint8_t DHT11_Read_Data(DHT11_Data *data) { DHT11_Set_Output(); GPIO_ResetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN); // 拉低起始信号 Delay_ms(20); // 至少18ms GPIO_SetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN); // 拉高 Delay_us(30); // 20-40us DHT11_Set_Input(); // 切换输入模式 if (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) != Bit_RESET) return 1; // 设备未响应 while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == Bit_RESET); // 等待响应低电平 while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == Bit_SET); // 等待高电平 for (uint8_t i = 0; i < 5; i++) { uint8_t byte = 0; for (uint8_t j = 0; j < 8; j++) { byte <<= 1; byte |= DHT11_Read_Bit(); // 读取每个位 } switch(i) { // 解析字节到数据结构 case 0: data->humidity_integer = byte; break; case 1: data->humidity_decimal = byte; break; case 2: data->temperature_integer = byte; break; case 3: data->temperature_decimal = byte; break; case 4: data->checksum = byte; break; } } DHT11_Set_Output(); // 恢复输出模式 GPIO_SetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN); // 高电平空闲 // 验证校验和:byte0 + byte1 + byte2 + byte3 = byte4 if (data->checksum == (data->humidity_integer + data->humidity_decimal + data->temperature_integer + data->temperature_decimal)) return 0; // 成功 else return 2; // 校验和错误 } // 主函数示例 int main(void) { DHT11_GPIO_Config(); DHT11_Data sensor_data; SystemInit(); // 初始化系统时钟 while (1) { if (DHT11_Read_Data(&sensor_data) == 0) { // 成功读取 float humidity = sensor_data.humidity_integer + sensor_data.humidity_decimal / 10.0; // 湿度计算(如45.0%) float temperature = sensor_data.temperature_integer + sensor_data.temperature_decimal / 10.0; // 温度计算(如28.0℃) // 通过串口输出数据(需用户实现UART部分) } Delay_ms(2000); // 2秒采样周期(DHT11最小采样间隔约1秒) } } ``` ### 代码解释 此代码实现了DHT11的完整驱动: - **硬件连接**:DHT11 VCC→3.3V, GND→GND, DATA→PA1(可根据需修改引脚)。 - **时序控制**:使用`Delay_ms`和`Delay_us`函数(需用户基于系统时钟实现),严格遵守DHT11时序规范(起始信号拉低 >18ms,响应等待40-50us,数据位读取基于高电平持续时间区分为0或1)[^4]。 - **数据解析**:读取40位数据并解析为湿度(整数和小数)、温度(整数和小数)及校验和。计算方法:湿度值 = 湿度整数 $+ \frac{\text{湿度小数}}{10}$,温度类似。校验和验证采用和检查 $\text{humidity\_integer} + \text{humidity\_decimal} + \text{temperature\_integer} + \text{temperature\_decimal} = \text{checksum}$,确保数据正确[^4]。 - **精度范围**:湿度20-90%RH ±5%,温度0-50℃ ±2℃[^4]。 - **注意事项**: - 需自行实现延时函数(例如用SysTick定时器)。 - 添加UART初始化代码以输出数据(参考STM32 UART库)。 - 免高频采样(DHT11最小间隔1秒)。 使用时将代码集成到工程中,调用`DHT11_Read_Data`即可获取数据。实际应用中可优化延时精度以减少时序误差[^3][^4]。 [^1]:STM32入门教程:温湿度传感器应用 [^2]:DHT11温湿度传感器工作流程 [^3]:STM32收集DHT11数据并通过WiFi发送到TCP服务器 [^4]:DHT11温湿度传感器驱动时序细节
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值