Hu Weijun
Hu Weijun
发布于 2025-03-20 / 30 阅读
0
0

使用STM32F407驱动DHT11温湿度传感器

全文大约5600字,阅读需要15分钟。

1. DHT11工作原理

1.1 DHT11功能特性

DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。同时,DHT11只有一根数据传输线,也就是单总线接口(Digital Single-Wire),用来测量温湿度。典型应用电路如下所示。测量的范围为湿度20%-90%(±5%),温度0-50℃(±2℃)。使用中建议连接线长度短于20米时用5K上拉电阻,大于20米时根据实际情况使用合适的上拉电阻。

1.2 DHT11数据特性

从上面典型应用电路中能看到,单片机与DHT11之间只存在一根总线进行交互。因此单片机与DHT11传感器之间的通讯和同步,都经过这根总线来进行,一次通讯时间为4ms左右,一次性能够完整的传输40bit的数据。其中,40bit=湿度整数数据(8bit)+湿度小数数据(8bit)+温度整数数据(8bit)+温度小数数据(8bit)+校验和(8bit)。在数据正确传输的时候,校验和等于湿度整数数据(8bit)+湿度小数数据(8bit)+温度整数数据(8bit)+温度小数数据(8bit)的和。接下来举个例子,格式如下:

  • 湿度整数部分(8bit) → 示例:0011 0010 表示50%RH

  • 湿度小数部分(8bit) → 固定为0(DHT11不支持小数湿度)

  • 温度整数部分(8bit) → 示例:0001 1110 表示30℃

  • 温度小数部分(8bit) → 固定为0(DHT11不支持小数温度)

  • 校验和(8bit) → 前4字节的和(取低8位)

假如最终的校验和正确的话,校验和得到的值应该是00110010+00000000+00011110+00000000=01010000

2. 单总线通讯协议

DHT11的通信分为3个阶段,需严格遵循时序:

(1) 主机启动信号

拉低总线至少 18ms(建议20ms)。

释放总线(切换为输入模式,等待DHT11响应)。主机在发送开始信号结束后,延时等待20-40μs后,准备读取DHT11的响应信号。

(2) DHT11响应

DHT11检测到总线释放后,会拉低总线 80μs。

再次拉高总线 80μs,表示准备发送数据。

(3) 数据传输

每bit数据以 50μs低电平开始,高电平的长短确定了数据位是0还是1。

数据“0”:高电平持续 26~28μs

数据“1”:高电平持续 70μs

总数据:40位(5字节)连续传输,无需应答机制。

3. CubeMX配置

烧录环境、启用外部时钟以及串口配置配置参考其他文章,在本次实验需要注意的是时钟树配置,需要配置HCLK为72MHZ。同时,需要打开定时器1(TIM1)。使用定时器1的原因在于,DHT11通讯中,需要使用到微秒级的延时函数,但是hal库中自带的hal_Delay延时函数是毫秒级的,所以不能够直接使用hal库内置的延时函数。前面也已经提到,本次实验中时钟树配置了HCLK为72MHZ因此系统分配给定时器1的频率就为72MHZ,所以我们需要将预分频系数Prescaler修改为72-1,这样子就可以得到频率为1MHZ的定时器1,也就是1秒钟有1000000个脉冲,每个脉冲周期为1μs,所以就是每计一次数就是1μs。在所有的配置结束之后,生成对应的工程文件,并在keil-MDK中打开。

4. 代码部分

4.1 USART中添加重定向函数

首先需要做的就是在usart.c中添加重定向函数。

int fputc(int ch,FILE *f)
{
  uint8_t temp[1]={ch};
  HAL_UART_Transmit(&huart3,temp,1,2);
  return ch;
}

并且在usart.h中添加头文件和函数声明。

#include "stdio.h"

int fputc(int ch,FILE *f);

4.2 建立DHT11驱动文件

DHT11.c文件中需要包含以下内容。

#include "dht11.h"
#include "main.h"
#include "tim.h"


void my_delay_us(uint32_t us)
{
    __HAL_TIM_SET_COUNTER(&htim1, 0);
    
    HAL_TIM_Base_Start(&htim1);
    
    while (__HAL_TIM_GET_COUNTER(&htim1) != us);
    
    HAL_TIM_Base_Stop(&htim1);
}

/**
 * ************************************************************************
 * @brief 将DHT11配置为推挽输出模式
 * ************************************************************************
 */
static void DHT11_PP_OUT(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.Pin = DHT11_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;	
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
}

/**
 * ************************************************************************
 * @brief 将DHT11配置为上拉输入模式
 * ************************************************************************
 */
static void DHT11_UP_IN(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.Pin = DHT11_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_PULLUP;	//上拉
	HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
}

/**
 * ************************************************************************
 * @brief 读取字节
 * @return temp
 * ************************************************************************
 */
uint8_t DHT11_ReadByte(void)
{
	uint8_t i, temp = 0;

	for (i = 0; i < 8; i++)
	{
		while (DHT11_ReadPin == 0);		// 等待低电平结束
		
		my_delay_us(40);			//	延时 40 微秒
		
		if (DHT11_ReadPin == 1)
		{
			while (DHT11_ReadPin == 1);	// 等待高电平结束
			
			temp |= (uint8_t)(0X01 << (7 - i));			// 先发送高位
		}
		else
		{
			temp &= (uint8_t)~(0X01 << (7 - i));
		}
	}
	return temp;
}

/**
 * ************************************************************************
 * @brief 读取一次数据
 * @param[in] DHT11_Data  定义的结构体变量
 * @return 0或1(数据校验是否成功)
 * @note 它首先向DHT11发送启动信号,然后等待DHT11的应答。如果DHT11正确应答,
 * 		 则继续读取湿度整数、湿度小数、温度整数、温度小数和校验和数据,
 * 		 并计算校验和以进行数据校验
 * ************************************************************************
 */
uint8_t DHT11_ReadData(DHT11_Data_TypeDef *DHT11_Data)
{
	DHT11_PP_OUT();			// 主机输出,主机拉低
	DHT11_PULL_0;	
	HAL_Delay(18);				// 延时 18 ms
	
	DHT11_PULL_1;					// 主机拉高,延时 30 us
	my_delay_us(30);	

	DHT11_UP_IN();				// 主机输入,获取 DHT11 数据
	
	if (DHT11_ReadPin == 0)				// 收到从机应答
	{
		while (DHT11_ReadPin == 0);		// 等待从机应答的低电平结束
		
		while (DHT11_ReadPin == 1);		// 等待从机应答的高电平结束
		
		/*开始接收数据*/   
		DHT11_Data->humi_int  = DHT11_ReadByte();
		DHT11_Data->humi_dec = DHT11_ReadByte();
		DHT11_Data->temp_int  = DHT11_ReadByte();
		DHT11_Data->temp_dec = DHT11_ReadByte();
		DHT11_Data->check_sum = DHT11_ReadByte();
		
		DHT11_PP_OUT();		// 读取结束,主机拉高
		DHT11_PULL_1;	
		
		// 数据校验
		if (DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_dec + DHT11_Data->temp_int + DHT11_Data->temp_dec)	
		{
			return 1;
		}		
		else
		{
			return 0;
		}
	}
	else		// 未收到从机应答
	{
		return 0;
	}
}

DHT11.h中需要包含以下内容。

#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f4xx_hal.h"

#define DHT11_PORT			GPIOE
#define DHT11_PIN			GPIO_PIN_6

#define DHT11_PULL_1		HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET)
#define DHT11_PULL_0		HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET)

#define DHT11_ReadPin		HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)

/**
 * ************************************************************************
 * @brief 存储传感器数据的结构体
 * 
 * 
 * ************************************************************************
 */
typedef struct
{
	uint8_t humi_int;			// 湿度的整数部分
	uint8_t humi_dec;	 		// 湿度的小数部分
	uint8_t temp_int;	 		// 温度的整数部分
	uint8_t temp_dec;	 		// 温度的小数部分
	uint8_t check_sum;	 		// 校验和

} DHT11_Data_TypeDef;


uint8_t DHT11_ReadData(DHT11_Data_TypeDef* DHT11_Data);


#endif

在DHT11实际使用中,通过查阅原理图可以看到,该开发板上的DHT11的DATA总线连到了PE6这一根线上。因此,在DHT11.h中,需要使用#define DHT11_PORT GPIOE #define DHT11_PIN GPIO_PIN_6来定义DHT11的数据线。

4.3 main函数中内容编写

在确认上面所有的代码无误之后,打开main.c这个主程序,将DHT11.h的头文件包含进去。同时也需要在main主程序前定义一个结构体变量,也就是DHT11_Data_TypeDef DHT11_Data;并且在while(1)循环中将我们需要读取的内容通过重定向后的函数printf发送到我们的电脑上。最终通过波特律动这个网站,接收我们从单片机端发送到电脑的串口数据。

main函数中内容需要自行进行编写。

为保持后续博客的可阅读性,准备考虑将驱动代码都上传到GitHub仓库,可自行下载。GitHub地址如下:

https://github.com/ykgshwj/STM32/tree/main


评论