HAL库STM32CubeMX+DHT11温湿度传感器串口显示模块化封装

一、工具原理部分

1.DHT11温湿度传感器基本原理

1.1 DHT11传感器概述

DHT11是一款低成本、数字输出的温湿度复合传感器,适用于对精度要求不高的场景。传感器包括一个电阻式感湿元件和一个NTC测温元件,每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达20米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选则。

1.2 DHT11接口说明

在单片机简单应用中,DHT11的供电电压为3-5.5V,本文接线使用5V供电,连接线长度低于20m时采用5k上拉电阻,在实际使用情景超过20m的环境中需要根据实际需求调整上拉电阻。下图为其典型应用电路:
在这里插入图片描述

1.3 DHT11通讯过程

DHT11共有三根线,VCC、GND和DATA,其中DATA采用单总线数据模式,一次完整的数据传输为40bit,高位先出。得到的数据格式:(湿度高8位即第1字节即湿度的整数部分)+(湿度低8位即第2字节即湿度的小数部分)+(温度高8位即第3字节即温度的整数部分)+(温度低8位即第4字节即温度的小数部分)+(校验和即第5字节即前4字节的和的低8位),其中校验和=(湿度高8位+湿度低8位+温度高8位+温度低8位)mod256。若接收到的第5字节(校验和)等于前4字节的和的低8位,则数据有效;否则需重新读取。
1.主机发送起始信号:
主机将单总线拉低至少18ms,确保DHT11检测到起始信号。随后主机拉高总线,保持20−40μs,进入接收状态。
2.DHT11响应:
DHT11检测到起始信号后,先将总线拉低80μs作为响应信号,告知主机已准备好。接着DHT11再将总线拉高80μs,完成响应并准备输出数据。
3.数据传输:
DHT11开始传输数据(如湿度、温度等)。每个数据位通过高低电平的持续时间区分:
数据 “0”:拉低总线后,高电平持续时间较短。
数据 “1”:拉低总线后,高电平持续时间较长。
主机通过检测高电平持续时间解析数据位。在整个通讯过程中,主机通过起始信号触发交互,DHT11响应后按约定格式输出数据,主机完成数据接收与解析,实现通讯。

通讯过程如图所示:
在这里插入图片描述
在这里插入图片描述

2.所需工具和具体配置

2.1 STM32CubeMX

STM32CubeMX 是意法半导体(STMicroelectronics)推出的一款图形化配置工具,专为简化 STM32 微控制器的嵌入式软件开发而设计。本次采用正点原子STM32F103ZET6开发板(其他开发板如典型的C8T6也可以使用),基于HAL库封装。首先打开CubeMX软件进行配置。

2.1.1SYS配置

调试接口配置(Debug):如截图中选择 Serial Wire,用于设定芯片调试方式,方便后续代码调试、下载程序。
在这里插入图片描述

2.1.2RCC时钟配置

首先,开启RCC时钟源,使用外部晶体或陶瓷谐振器作为高速、低速时钟源,为系统提供高频、低频时钟信号。
在这里插入图片描述
在时钟配置处,选择 72 MHZ 输出。
在这里插入图片描述

2.1.3 TIM定时器设置

由于DHT11需要定时采集数据,需要开启基本定时器,这里我开启了TIM3标准定时器(可以自行选择定时器,高级定时器这种简单情况下应该用不到)。
选择TIM3并将时钟源(Clock Source )开启为内部时钟(Internal Clock),预分频系数(Prescaler)设为 72-1 ,计数周期(Counter Period)默认设置为 65536 ,默认采用向上计数。
在这里插入图片描述

2.1.4 USART串口配置

通过串口通信显示在电脑窗口上,由于DHT11单总线模式通信,仅通过一根数据线与主机通信,无法像同步通信(如 SPI)那样提供额外的时钟线。所以在USART模块开启异步通信(Asynchronous),无需共享时钟信号,双方依据约定的时序规则即可完成数据交互。波特率可默认设置为115200(其它也可,需要匹配串口助手),数据长度、停止位、校验位默认。
在这里插入图片描述

2.1.5 GPIO配置

这里我采用PA7作为DHT11的DATA引脚(用户可以自定修改引脚),双击PA7可以设置为输出(GPIO_Output)模式,另外将PA7设置了用户标签(User Label),方便在代码里引用该标签,所以建议学习者可以跟我一样配置,编译显示串口效果后可以自行修改。
在这里插入图片描述

2.1.6 工程命名和开发环境

保存文件名、保存路径。选择完成后,STM32CubeMX 将生成适用于 Keil MDK - ARM 开发环境的工程代码,方便后续在 Keil 中进行编译、调试等操作。
在这里插入图片描述

2.1.7 管理工程并生成代码

在这里插入图片描述

2.2 Keil 5

点击魔术棒-Target,这里我是用的是version6,勾选Use Micro LIB(直接使用 Micro LIB 可以方便地实现基本的 C 语言功能,如输入输出、字符串处理等)。
在这里插入图片描述
点击小扳手,选择GB2312,方便显示汉字等。
在这里插入图片描述

2.3 串口助手

串口助手小软件可以选择江协科技,正点原子等等,都可以达到效果,本文使用后者。
在这里插入图片描述
以上配置完成后就是代码部分模块化封装。

二、代码封装部分

1.添加DHT11.c和DHT11.h文件

在Keil中添加生成.c和.h文件:
在这里插入图片描述
在这里插入图片描述

2.DHT11.c

/* DHT11.c */
#include "DHT11.h"
#include "tim.h"

extern TIM_HandleTypeDef DHT11_TIM_HANDLE;

// 私有函数声明
static void DHT11_IO_Out(void);
static void DHT11_IO_In(void);
static void DHT11_StartSignal(void);
static uint8_t DHT11_CheckResponse(void);
static uint8_t DHT11_ReadBit(void);
static uint8_t DHT11_ReadByte(void);

// 微秒级延时函数
static void Delay_us(uint16_t delay)
{
    __HAL_TIM_SET_COUNTER(&DHT11_TIM_HANDLE, 0);
    HAL_TIM_Base_Start(&DHT11_TIM_HANDLE);
    while(__HAL_TIM_GET_COUNTER(&DHT11_TIM_HANDLE) < delay);
    HAL_TIM_Base_Stop(&DHT11_TIM_HANDLE);
}

// 设置IO为输出模式
static void DHT11_IO_Out(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = DHT11_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
}

// 设置IO为输入模式
static void DHT11_IO_In(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = DHT11_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
}

// 发送起始信号
static void DHT11_StartSignal(void)
{
    DHT11_IO_Out();
    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET);
    HAL_Delay(18);
    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET);
    Delay_us(30);
}

// 检测DHT11响应
static uint8_t DHT11_CheckResponse(void)
{
    uint8_t retry = 0;
    DHT11_IO_In();
    
    // 等待DHT11拉低总线
    while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) && retry < 100)
    {
        retry++;
        Delay_us(1);
    }
    if(retry >= 100) return DHT11_ERROR_NO_RESPONSE;
    
    retry = 0;
    // 等待DHT11拉高总线
    while(!HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) && retry < 100)
    {
        retry++;
        Delay_us(1);
    }
    if(retry >= 100) return DHT11_ERROR_NO_RESPONSE;
    
    return DHT11_OK;
}

// 读取单个bit
static uint8_t DHT11_ReadBit(void)
{
    uint8_t retry = 0;
    while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) && retry < 100)
    {
        retry++;
        Delay_us(1);
    }
    
    retry = 0;
    while(!HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) && retry < 100)
    {
        retry++;
        Delay_us(1);
    }
    
    Delay_us(40);
    return HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN);
}

// 读取一个字节
static uint8_t DHT11_ReadByte(void)
{
    uint8_t value = 0;
    for(uint8_t i = 0; i < 8; i++)
    {
        value <<= 1;
        value |= DHT11_ReadBit();
    }
    return value;
}

// 主读取函数
DHT11_Status DHT11_ReadData(uint8_t *temperature, uint8_t *humidity)
{
    uint8_t data[5] = {0};
    
    DHT11_StartSignal();
    
    if(DHT11_CheckResponse() != DHT11_OK)
        return DHT11_ERROR_NO_RESPONSE;
    
    for(uint8_t i = 0; i < 5; i++)
    {
        data[i] = DHT11_ReadByte();
    }
    
    // 校验数据
    if(data[4] != (data[0] + data[1] + data[2] + data[3]))
        return DHT11_ERROR_CHECKSUM;
    
    *humidity = data[0];
    *temperature = data[2];
    
    return DHT11_OK;
}

3.DHT11.h

/* dht11.h */
#ifndef __DHT11_H
#define __DHT11_H

#include "stm32f1xx_hal.h"

// 硬件配置宏(用户根据实际硬件修改)
#define DHT11_PORT        GPIOA
#define DHT11_PIN         GPIO_PIN_7
#define DHT11_TIM_HANDLE  htim3

// 错误代码定义
typedef enum {
    DHT11_OK = 0,
    DHT11_ERROR_NO_RESPONSE,
    DHT11_ERROR_CHECKSUM
} DHT11_Status;

// 函数声明
DHT11_Status DHT11_ReadData(uint8_t *temperature, uint8_t *humidity);

#endif /* __DHT11_H */

4.在tim.c中添加

/* USER CODE BEGIN 1 */
void Delay_us(uint16_t delay)
{
	__HAL_TIM_DISABLE(&htim3);
	__HAL_TIM_SET_COUNTER(&htim3,0);
	__HAL_TIM_ENABLE(&htim3);
	uint16_t curCnt=0;
	while(1)
	{
		curCnt=__HAL_TIM_GET_COUNTER(&htim3);
		if(curCnt>=delay)
			break;
	}
	__HAL_TIM_DISABLE(&htim3);
}

/* USER CODE END 1 */

5.在main.c中添加

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "DHT11.h"
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */
  /* USER CODE BEGIN 1 */
	
    // 系统初始化...
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM3_Init();
    MX_USART1_UART_Init();

    uint8_t temp = 0, humi = 0;
    char buffer[50];
	
  /* USER CODE END 1 */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		DHT11_Status status = DHT11_ReadData(&temp, &humi);
        
        if(status == DHT11_OK)
        {
            sprintf(buffer, "Temp: %d℃, Humi: %d%%\r\n", temp, humi);
            HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), 100);
        }
        else
        {
            sprintf(buffer, "Error: %d\r\n", status);
            HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), 100);
        }
        
        HAL_Delay(2000);
    
    /* USER CODE END WHILE */

最后,编译下载,打开串口工具。

三、接线图和串口效果

1.实物接线图

在这里插入图片描述

2.串口显示效果

在这里插入图片描述

3.写在最后

————本文仅供学习交流使用,有不足之处欢迎大家批评指正,代码有部分参考@cl777_,谢谢大家!————

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐