前置介绍
DHT11 温湿度传感器

温湿度传感器模块 DHT11 外观

DHT11 模块简介
DHT11 温湿度模块可以检测环境相对湿度和温度, 内部包括一个电容式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接, 使用单总线与主控通信,仅需使用一个 I/O 口
DHT11 使用说明 & 模块接口

DHT11 程序流程 (通信流程)


- DHT11 使用单线双向的串行通行方式
- 数据格式为: 16bit 湿度数据 (8bit 整数, 8bit 小数), 16bit 温度数据 (8bit 整数, 8bit 小数), 8bit 校验和数据. 共 40bit, 高位先出
- 通信时序为: 建立连接, 数据接收
-
- 建立连接 (主机发送起始信号):
-
-
- 上电后等待越过不稳定状态
- 由主机输出拉低数据线, 时间要大于 18ms
- 主机端口改为输入状态
-
-
- 建立连接 (从机响应信号):
-
-
- 从机接收到信号后, 拉低数据线代表应答
- 再拉高数据线表示 连接建立成功
-
-
- 数据接收:
-
-
- 根据低电平后的高电平时间来决定接收数据的种类
-
数据接收这里可以看到:
其通过判断低电平后的高电平时间来决定接收数据的种类, 在低电平后的高电平时间是 us 级别的, 所以这里需要用 DWT 来精确计量,
同时这里也可以使用不同的逻辑判断数据种类.
并不需要去判断其高电平时间是否为指定的时间长度, 只需要判断高电平来临这一刻往后的中间数的延迟时间 (比如: 40us) 后是否还是高电平, 如果是, 那就说明其发送为 位数据"1", 如果不是, 那就是 位数据"0"
同时, 需要注意, 每次采集间隔需大于 2 秒
如何实现该程序:

项目配置
复制上一节 "DWT基础应用与获取程序运行时间Debug练习" 项目以直接使用模板开始本节 DWT 应用
添加串口部分
- 找到之前的 串口发送之发送字符串与printf函数 项目
- 找到项目 .\Core\Inc 和 .\Core\Src 下的 usart.h 和 usart.h
- 在本节项目中 .\User\ 下创建 usart 文件夹 (用于存放串口模块代码)
- 将刚刚找到的两个文件拷贝到本节项目的 .\User\usart/ 下
- (可选) 因为这里的串口是板级支持的, 所以可以将两个文件前加 "bsp_" 前缀

修改本节项目名称

- 点击 Manage Project Items 打开配置界面
- 双击 Project Targets 下的列, 将其改为本节项目名 "DWT_DHT11"
添加 usart.c

添加 HAL 库文件
因为创建模板库的时候, 采取的是渐进式的引入方案,
所以这里我们用到了 usart 就需要引入对应的 HAL 库文件.
- Groups 选择 STM32F1xx_HAL_Driver, Files 列下面点击 "Add Files..."
- 转到 .\Libraries\STM32F1xx_HAL_Driver\Src\Legacy 文件夹中
- 找到 "stm32f1xx_hal_usart.c" "stm32f1xx_hal_dma.c" "stm32f1xx_hal_uart.c" 三个文件, 添加

这时, 在左侧即可看到对应文件被添加到项目中

对 usart 文件进行改造
-
删除 CubeMX 自动生成的注释及不相关的内容
-
增加 printf 重定向内容
/**
- @brief 重定向 printf 的输出到串口
- @param ch: 要发送的字符
- @param f: 文件指针 (标准库要求的参数, 一般不使用)
- @retval 返回发送的字符
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
-
在 h 文件中 引入 "main.h"
-
在 main.h 中引入 <stdio.h> 以使用 printf
解决 usart 的 Error_Handler() 报错问题

设置完成后会看到 MX_USART1_UART_Init() 中 Error_Handler() 报错
这是因为 HAL 库手动建模板的时候, 并没有错误的处理,
可以到之前的项目中的 main.c 中找到 Erro_Hanlder 复制添加到 mian.c 中
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
然后在 main.h 中添加该函数的声明
void Error_Handler(void);
在 main.c 中引入 usart.h
#include "usart/bsp_usart.h"
删除来自上一个项目的代码
将 main.c 中的 main 函数改为如下
删除 while 中的内容, 调用串口初始化函数.
int main(void)
{
HAL_Init(); // 初始化 HAL 库
SystemClock_Config(); // 配置系统时钟, 设置为 72MHz
DWT_Init(); // 启动 DWT 计数器, 用于精确测量程序运行时间
MX_USART1_UART_Init();
while (1)
{
}
}
创建添加 DHT11 模组相关文件
-
在 项目/User/ 文件夹下创建 dht11 文件夹
-
在 dht11 文件夹下创建 "bsp_dht11.h" 和 "bsp_dht11.c" 文件
-
为 .h 和 .c 文件添加 基础部分
#ifndef BSP_DHT11_H
#define BSP_DHT11_H/* Includes ------------------------------------------------------------------*/
#include "main.h"#endif /* BSP_DHT11_H */
/**
- @file bsp_dht11.c
- @brief 温湿度传感器底层函数接口
*/
#include "dht11/bsp_dht11.h"
DHT11 驱动代码
1. 完成 IO 引脚的初始化
默认配置成推挽输出
按之前的方式为使用 CubeMX 进行配置, 但这里不使用 CubeMX 直接用 bsp_led.c 中的 LED_GPIO_Config 将其修改后直接使用

需要将其改为 DHT11 使用的 GPIO 引脚和对应 IO 组
2. 配置对应引脚的宏定义
#ifndef __BSP_DHT11_H__
#define __BSP_DHT11_H__
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* 定义 DHT11_DATA 连接的 GPIO 端口, 用户只需要修改下面的代码即可改变受控制的 DHT11_DATA 引脚 */
#define DHT11_DATA_Pin GPIO_PIN_12 /* GPIO 端口 */
#define DHT11_DATA_GPIO_Port GPIOB /* 对应引脚 */
#endif /* __BSP_DHT11_H__ */
3. 修改 IO 引脚的初始化中的配置与注释
#include "dht11/bsp_dht11.h"
/**
* @brief 初始化 DHT11 的 GPIO 引脚
*/
void DHT11_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能GPIOB端口时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
GPIO_InitStruct.Pin = DHT11_DATA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉不下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速输出
HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct);
/* 初始化 DHT11 数据引脚 */
HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_SET);
}
4. 增加 DHT11_SetGPIOMode 函数
/**
* @brief 配置 DHT11 数据引脚的工作模式
* @param mode 引脚模式, 如输入, 输出等, 使用 HAL 库中的 GPIO_MODE_XXX 宏
* @param pull 上下拉配置, 使用 HAL 库中的 GPIO_PULL_XXX 宏
*/
void DHT11_SetGPIOMode(uint32_t mode, uint32_t pull)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
GPIO_InitStruct.Pin = DHT11_DATA_Pin;
GPIO_InitStruct.Mode = mode; // 推挽输出
GPIO_InitStruct.Pull = pull; // 不上拉不下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速输出
HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct); // 初始化 GPIO
}
5. 增加 DHT11 数据读取函数
/**
* @brief 读取DHT11一个字节数据
* @retval 返回8位数据(1字节)
*/
uint8_t DHT11_ReadByte(void)
{
uint8_t value = 0;
for (uint8_t i = 0; i < 8; i++)
{
// 等待信号线变高,表示开始传输第i位数据
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_RESET);
DWT_DelayUs(40); // 延时40微秒,判断数据位是0还是1
// 如果40us后线仍然为高,表示该位为1
if (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
{
value |= (1 << (7 - i)); // 把对应位设置为1
// 等待信号线变低,准备接收下一位
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET);
}
// 如果40us后线为低,则该位为0,直接继续下一位
}
return value;
}
/**
* @brief 读取DHT11传感器数据
* @param data 指向存放温湿度数据的结构体指针
* @retval HAL_OK 成功,HAL_ERROR 失败
*/
HAL_StatusTypeDef DHT11_ReadData(DHT11_DATA_TYPEDEF *data)
{
uint8_t retry = 0;
// 1. 主机拉低总线,发送起始信号(至少18ms)
DHT11_SetGPIOMode(GPIO_MODE_OUTPUT_PP, GPIO_NOPULL); // 设置为推挽输出
HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_RESET);
DWT_DelayMs(20); // 保持低电平20ms,通知DHT11开始传输
HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_SET);
DWT_DelayUs(30); // 延时30微秒
// 2. 设置引脚为输入,等待DHT11响应信号
DHT11_SetGPIOMode(GPIO_MODE_INPUT, GPIO_PULLUP);
// 3. 等待DHT11拉低响应信号(最大等待100us)
retry = 0;
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
{
if (++retry > 100)
return HAL_ERROR; // 超时无响应,读取失败
DWT_DelayUs(1);
}
// 等待DHT11拉高信号(最大等待100us)
retry = 0;
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_RESET)
{
if (++retry > 100)
return HAL_ERROR; // 超时,读取失败
DWT_DelayUs(1);
}
// 等待DHT11再次拉低信号(最大等待100us)
retry = 0;
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
{
if (++retry > 100)
return HAL_ERROR; // 超时,读取失败
DWT_DelayUs(1);
}
// 4. 读取5字节数据(湿度整数、小数,温度整数、小数,校验和)
data->humi_int = DHT11_ReadByte();
data->humi_deci = DHT11_ReadByte();
data->temp_int = DHT11_ReadByte();
data->temp_deci = DHT11_ReadByte();
data->check_sum = DHT11_ReadByte();
// 5. 校验数据是否正确
uint8_t sum = data->humi_int + data->humi_deci + data->temp_int + data->temp_deci;
return (sum == data->check_sum) ? HAL_OK : HAL_ERROR;
}
DHT11 应用层代码
1. 项目\ 目录下, 创建 App 文件夹. App 文件夹下创建 dht11 文件夹, 分别创建 app_dht11.h , app_dht11.c
项目
└── App
└── dht11
├── app_dht11.h
└── app_dht11.c
2. 在 Keil 中, 添加 组 App, 和 app_dht11.c

3. 在 "魔术棒" 中添加头文件目录

4. 编写应用层 DHT11 (APP)
#ifndef __APP_DHT11_H
#define __APP_DHT11_H
#include "main.h"
/* 函数声明 */
void Dht11_ReadAndPrint(void);
#endif /* __APP_DHT11_H */
/**
* @file app_dht11.c
* @brief 读取DHT11温湿度传感器实验应用层函数接口
*/
#include "dht11/app_dht11.h"
#include "dht11/bsp_dht11.h"
static DHT11_DATA_TYPEDEF dht11_data = {0};
/**
* @brief 读取DHT11传感器数据并打印结果
* @param 无
* @retval 读取成功返回1,失败返回0
*/
void Dht11_ReadAndPrint(void)
{
if(DHT11_ReadData(&dht11_data) == HAL_OK)
{
printf("当前数据传输校验正确: ");
if(dht11_data.humi_deci & 0x80) // 湿度负数判断(一般DHT11无负湿度,保留)
{
printf("湿度为 -%d.%d %%RH, ", dht11_data.humi_int, dht11_data.humi_deci);
}
else
{
printf("湿度为 %d.%d %%RH, ", dht11_data.humi_int, dht11_data.humi_deci);
}
if(dht11_data.temp_deci & 0x80) // 温度负数判断
{
printf("温度为 -%d.%d ℃ \r\n", dht11_data.temp_int, dht11_data.temp_deci);
}
else
{
printf("温度为 %d.%d ℃ \r\n", dht11_data.temp_int, dht11_data.temp_deci);
}
}
else
{
printf("读取DHT11数据错误! \r\n");
}
}
5. 在 main.c 中添加对应头文件引用
/**
* @file main.c
* @brief DWT应用与DHT11温湿度传感器
*/
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "led/bsp_led.h"
#include "dwt/bsp_dwt.h"
#include "usart/bsp_usart.h"
#include "dht11/bsp_dht11.h"
#include "dht11/app_dht11.h"
代码部分
#ifndef __MAIN_H
#define __MAIN_H
/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"
#include <stdio.h> // 使用 printf
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
void Error_Handler(void);
#endif /* __MAIN_H */
/**
* @file main.c
* @brief DWT应用与DHT11温湿度传感器
*/
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "led/bsp_led.h"
#include "dwt/bsp_dwt.h"
#include "usart/bsp_usart.h"
#include "dht11/bsp_dht11.h"
#include "dht11/app_dht11.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint64_t begin = 0; // 记录计时起始值
uint64_t end = 0; // 记录计时结束值
uint64_t duration = 0; // 记录程序运行所花的时间 (单位: DWT 计数器周期)
uint64_t task_us = 0; // 记录程序运行所花的时间 (单位: DWT 计数器周期)
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* Private functions ---------------------------------------------------------*/
int main(void)
{
HAL_Init(); // 初始化 HAL 库
SystemClock_Config(); // 配置系统时钟, 设置为 72MHz
MX_USART1_UART_Init(); // 初始化串口, 用于打印调试信息
DWT_Init(); // 启动 DWT 计数器, 用于精确测量程序运行时间
DHT11_GPIO_Config(); // 初始化 DHT11 传感器相关 GPIO 引脚
DWT_DelayS(1); // 启动前先延时1秒, 确保 DHT11 完成上电初始化 (建议上电后等待至少1秒)
while (1)
{
/* DHT11 温湿度传感器读取与打印任务 */
Dht11_ReadAndPrint(); // 读取传感器数据并通过串口打印出来
DWT_DelayS(2); // 延时2秒后再读取 (DHT11每2秒更新一次数据, 不能太快读取)
}
}
/**
* @brief System Clock Configuration
* The system Clock is configured as follow :
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 72000000
* HCLK(Hz) = 72000000
* AHB Prescaler = 1
* APB1 Prescaler = 2
* APB2 Prescaler = 1
* HSE Frequency(Hz) = 8000000
* HSE PREDIV1 = 1
* PLLMUL = 9
* Flash Latency(WS) = 2
* @param None
* @retval None
*/
void SystemClock_Config(void)
{
RCC_ClkInitTypeDef clkinitstruct = {0};
RCC_OscInitTypeDef oscinitstruct = {0};
/* Enable HSE Oscillator and activate PLL with HSE as source */
oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
oscinitstruct.HSEState = RCC_HSE_ON;
oscinitstruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
oscinitstruct.PLL.PLLState = RCC_PLL_ON;
oscinitstruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
oscinitstruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
{
/* Initialization Error */
while(1);
}
/* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2
clocks dividers */
clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
{
/* Initialization Error */
while(1);
}
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifndef __BSP_DWT_H
#define __BSP_DWT_H
/* Includes ------------------------------------------------------------------*/
#include "main.h"
// 引用 main.h 是因为需要用到很多的 HAL 库和定义. 所以也建议后续添加的每一个 .h 文件中都要引入 main.h
/*
在 Cortex_M 里面有一个外设叫 DWT (Data Watchpoint and Trace), 该外设有一个32位的寄存器叫 CYCCNT, 它是一个向上的计数器, 记录的使内核时钟运行的个数,
假设内核频率为 72M, 内核跳一次的时间大概为 1/72M = 14ns (最长能记录的时间为: 60s = 2 的 32 次方 / 72 000 000) . 当 CYCCNT 溢出之后, 会清0重新开始向上计数
使能 CYCCNT 计数的操作步骤:
1. 先使能 DWT 外设设备, 这个由另外内核调试寄存器 DEMCR 的 位 24 控制, 写 1 使能
2. 使能 CYCCNT 寄存器之前, 先清0
3. 使能 CYCCNT 计数器, 这个由 DWT_CTRL (代码上宏定义为 DWT_CR) 的位0控制, 写 1 使能
4. CYCCNT 和 us 互相转换: 1 / SystemCoreCLock * CYCCNT (S) = 1000 / SystemCoreClock * CYCCNT (MS) = 1000000 / SystemCoreClock * CYCCNT (US) = (X)US
*/
/* DWT时间戳相关寄存器定义 */
#define DEMCR *(uint32_t *) (0xE000EDFC)
#define DWT_CTRL *(uint32_t *) (0xE0001000)
#define DWT_CYCCNT *(uint32_t *) (0xE0001004)
#define DEMCR_TRCENA (1<<24)
#define DWT_CTRL_CYCCNTENA (1<<0)
void DWT_Init(void);
uint32_t DWT_GetTick(void);
uint32_t DWT_TickToMicrosecond(uint32_t tick, uint32_t frequency);
void DWT_DelayUs(uint32_t time);
void DWT_DelayMs(uint32_t time);
void DWT_DelayS(uint32_t time);
#endif /* __BSP_DWT_H */
/**
* @file bsp_dwt.c
* @brief 使用内核定时器函数接口
*/
#include "dwt/bsp_dwt.h"
/**
* @brief 初始化DWT计数器s
* @param 无
* @retval 无
* @note 使用延时函数前, 必须调用本函数
*/
void DWT_Init(void)
{
/* 使能 DWT 外设 */
DEMCR |= (uint32_t)DEMCR_TRCENA;
/* DWT CYCNT 寄存器计数清0 */
DWT_CYCCNT = (uint32_t)0U; // 使能 CYCCNT 寄存器之前, 先清0
/* 使能 Cortex-M DWT CYCCNT 寄存器 */
DWT_CTRL |= (uint32_t)DWT_CTRL_CYCCNTENA;
}
/**
* @brief 读取当前时间戳
* @param 无
* @retval 当前时间戳, 即 DWT_CYCCNT 寄存器的值
*/
uint32_t DWT_GetTick(void)
{
return ((uint32_t)DWT_CYCCNT);
}
/**
* @brief 节拍数转化时间间隔 (微秒单位)
* @param tick: 需要转换的节拍数
* @param frequency: 内核时钟频率
* @retval 当前时间戳 (微秒单位)
*/
uint32_t DWT_TickToMicrosecond(uint32_t tick, uint32_t frequency)
{
return (uint32_t)(1000000.0 / frequency * tick);
}
/**
* @brief DWT计数器实现精确延时, 32位计数器
* @param time: 延迟长度, 单位: us
* @retval 无
*/
void DWT_DelayUs(uint32_t time)
{
/* 将微秒转化为对应的时钟计数值 */
uint32_t tick_duration = time * (SystemCoreClock / 1000000);
uint32_t tick_start = DWT_GetTick(); /* 刚进入时的计数器值 */
while(DWT_GetTick() - tick_start < tick_duration);
}
/**
* @brief DWT计数器实现精确延时, 32位计数器
* @param time: 延迟长度, 单位: ms
*/
void DWT_DelayMs(uint32_t time)
{
for (uint32_t i = 0; i < time; i++)
{
DWT_DelayUs(1000);
}
}
/**
* @brief DWT计数器实现精确延时, 32位计数器
* @param time: 延迟长度, 单位: s
*/
void DWT_DelayS(uint32_t time)
{
for (uint32_t i = 0; i < time; i++)
{
DWT_DelayMs(1000);
}
}
#ifndef __BSP_LED_H__
#define __BSP_LED_H__
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* ----------------------------- LED 引脚定义 ----------------------------- */
// 红灯
#define LED_R_Pin GPIO_PIN_1
// 绿灯
#define LED_G_Pin GPIO_PIN_2
// 蓝灯
#define LED_B_Pin GPIO_PIN_3
// 所有灯都连接在 GPIOA
#define LED_Port GPIOA
/* ----------------------------- 函数声明 ----------------------------- */
void LED_GPIO_Config(void);
/* ----------------------------- LED控制宏 ----------------------------- */
// 红灯控制
#define LED_R_ON() HAL_GPIO_WritePin(LED_Port, LED_R_Pin, GPIO_PIN_RESET); // 点亮红灯
#define LED_R_OFF() HAL_GPIO_WritePin(LED_Port, LED_R_Pin, GPIO_PIN_SET); // 熄灭红灯
#define LED_R_TOGGLE() HAL_GPIO_TogglePin(LED_Port, LED_R_Pin); // 翻转红灯状态
// 绿灯控制
#define LED_G_ON() HAL_GPIO_WritePin(LED_Port, LED_G_Pin, GPIO_PIN_RESET); // 点亮绿灯
#define LED_G_OFF() HAL_GPIO_WritePin(LED_Port, LED_G_Pin, GPIO_PIN_SET); // 熄灭绿灯
#define LED_G_TOGGLE() HAL_GPIO_TogglePin(LED_Port, LED_G_Pin); // 翻转绿灯状态
// 蓝灯控制
#define LED_B_ON() HAL_GPIO_WritePin(LED_Port, LED_B_Pin, GPIO_PIN_RESET); // 点亮蓝灯
#define LED_B_OFF() HAL_GPIO_WritePin(LED_Port, LED_B_Pin, GPIO_PIN_SET); // 熄灭蓝灯
#define LED_B_TOGGLE() HAL_GPIO_TogglePin(LED_Port, LED_B_Pin); // 翻转蓝灯状态
/* ----------------------------- LED 组合控制宏 ----------------------------- */
// 三色灯全亮
#define LED_RGB_ALL_ON() LED_R_ON(); LED_G_ON(); LED_B_ON()
// 三色灯全灭
#define LED_RGB_ALL_OFF() LED_R_OFF(); LED_G_OFF(); LED_B_OFF()
// 仅亮红灯
#define LED_R_ON_ONLY() LED_R_ON(); LED_G_OFF(); LED_B_OFF()
// 仅亮绿灯
#define LED_G_ON_ONLY() LED_R_OFF(); LED_G_ON(); LED_B_OFF()
// 仅亮蓝灯
#define LED_B_ON_ONLY() LED_R_OFF(); LED_G_OFF(); LED_B_ON()
#endif /* __BSP_LED_H__ */
/**
* @file bsp_led.c
* @brief LED灯函数接口
*/
#include "led/bsp_led.h"
/**
* @brief 初始化控制LED的GPIO引脚
* @note 配置为推挽输出, 默认全灭
*/
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能GPIOA端口时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
GPIO_InitStruct.Pin = LED_R_Pin|LED_G_Pin|LED_B_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉不下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速输出
HAL_GPIO_Init(LED_Port, &GPIO_InitStruct);
/* 初始化时关闭所有LED (低电平点亮, 默认全设为高电平) */
HAL_GPIO_WritePin(LED_Port, LED_R_Pin|LED_G_Pin|LED_B_Pin, GPIO_PIN_SET);
}
#ifndef __BSP_USART_H__
#define __BSP_USART_H__
/* Includes ------------------------------------------------------------------*/
#include "main.h"
extern UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void);
#endif /* __BSP_USART_H__ */
/**
* @file bsp_usart.c
* @brief 初始化串口并重定向printf函数到usart端口
*/
#include "usart/bsp_usart.h"
UART_HandleTypeDef huart1;
/* USART1 init function */
/**
* @brief 初始化 USART1 (波特率 115200, 8N1, 双向通信)
* @note 可用于 printf 重定向输出
* @retval 无
*/
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART1 外设底层初始化 (由 HAL_UART_Init 自动调用)
* @param uartHandle: UART 句柄
* @retval 无
*/
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
/**
* @brief UART MSP 反初始化 (释放资源)
* @param uartHandle: UART 句柄
* @retval 无
*/
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART1)
{
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
}
}
/**
* @brief 重定向 printf 的输出到串口
* @param ch: 要发送的字符
* @param f: 文件指针 (标准库要求的参数, 一般不使用)
* @retval 返回发送的字符
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
#ifndef __BSP_DHT11_H__
#define __BSP_DHT11_H__
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* 定义 DHT11_DATA 连接的 GPIO 端口, 用户只需要修改下面的代码即可改变受控制的 DHT11_DATA 引脚 */
// DHT11_DATA
#define DHT11_DATA_Pin GPIO_PIN_12 /* GPIO 端口 */
#define DHT11_DATA_GPIO_Port GPIOB /* 对应引脚 */
/* DHT11数据结构体 */
typedef struct
{
uint8_t humi_int; // 湿度整数部分
uint8_t humi_deci; // 湿度小数部分
uint8_t temp_int; // 温度整数部分
uint8_t temp_deci; // 温度小数部分
uint8_t check_sum; // 校验和
} DHT11_DATA_TYPEDEF;
/* 函数声明 */
void DHT11_GPIO_Config(void);
void DHT11_SetGPIOMode(uint32_t mode, uint32_t pull);
uint8_t DHT11_ReadByte(void);
HAL_StatusTypeDef DHT11_ReadData(DHT11_DATA_TYPEDEF *data);
#endif /* __BSP_DHT11_H__ */
/**
* @file bsp_dht11.c
* @brief 温湿度传感器底层函数接口
*/
#include "dht11/bsp_dht11.h"
#include "dwt/bsp_dwt.h"
/**
* @brief 初始化 DHT11 的 GPIO 引脚
*/
void DHT11_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能GPIOB端口时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
GPIO_InitStruct.Pin = DHT11_DATA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉不下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速输出
HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct);
/* 初始化 DHT11 数据引脚 */
HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_SET);
}
/**
* @brief 配置 DHT11 数据引脚的工作模式
* @param mode 引脚模式, 如输入, 输出等, 使用 HAL 库中的 GPIO_MODE_XXX 宏
* @param pull 上下拉配置, 使用 HAL 库中的 GPIO_PULL_XXX 宏
*/
void DHT11_SetGPIOMode(uint32_t mode, uint32_t pull)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
GPIO_InitStruct.Pin = DHT11_DATA_Pin;
GPIO_InitStruct.Mode = mode; // 推挽输出
GPIO_InitStruct.Pull = pull; // 不上拉不下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速输出
HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct); // 初始化 GPIO
}
/**
* @brief 读取DHT11一个字节数据
* @retval 返回8位数据(1字节)
*/
uint8_t DHT11_ReadByte(void)
{
uint8_t value = 0;
for (uint8_t i = 0; i < 8; i++)
{
// 等待信号线变高,表示开始传输第i位数据
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_RESET);
DWT_DelayUs(40); // 延时40微秒,判断数据位是0还是1
// 如果40us后线仍然为高,表示该位为1
if (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
{
value |= (1 << (7 - i)); // 把对应位设置为1
// 等待信号线变低,准备接收下一位
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET);
}
// 如果40us后线为低,则该位为0,直接继续下一位
}
return value;
}
/**
* @brief 读取DHT11传感器数据
* @param data 指向存放温湿度数据的结构体指针
* @retval HAL_OK 成功,HAL_ERROR 失败
*/
HAL_StatusTypeDef DHT11_ReadData(DHT11_DATA_TYPEDEF *data)
{
uint8_t retry = 0;
// 1. 主机拉低总线,发送起始信号(至少18ms)
DHT11_SetGPIOMode(GPIO_MODE_OUTPUT_PP, GPIO_NOPULL); // 设置为推挽输出
HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_RESET);
DWT_DelayMs(20); // 保持低电平20ms,通知DHT11开始传输
HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_SET);
DWT_DelayUs(30); // 延时30微秒
// 2. 设置引脚为输入,等待DHT11响应信号
DHT11_SetGPIOMode(GPIO_MODE_INPUT, GPIO_PULLUP);
// 3. 等待DHT11拉低响应信号(最大等待100us)
retry = 0;
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
{
if (++retry > 100)
return HAL_ERROR; // 超时无响应,读取失败
DWT_DelayUs(1);
}
// 等待DHT11拉高信号(最大等待100us)
retry = 0;
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_RESET)
{
if (++retry > 100)
return HAL_ERROR; // 超时,读取失败
DWT_DelayUs(1);
}
// 等待DHT11再次拉低信号(最大等待100us)
retry = 0;
while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
{
if (++retry > 100)
return HAL_ERROR; // 超时,读取失败
DWT_DelayUs(1);
}
// 4. 读取5字节数据(湿度整数、小数,温度整数、小数,校验和)
data->humi_int = DHT11_ReadByte();
data->humi_deci = DHT11_ReadByte();
data->temp_int = DHT11_ReadByte();
data->temp_deci = DHT11_ReadByte();
data->check_sum = DHT11_ReadByte();
// 5. 校验数据是否正确
uint8_t sum = data->humi_int + data->humi_deci + data->temp_int + data->temp_deci;
return (sum == data->check_sum) ? HAL_OK : HAL_ERROR;
}
硬件连接

程序现象

使用串口工具打开串口, 查看输出的数据, 可以看到温湿度数据读取成功并且正确按照指定格式进行输出.
向温湿度传感器模块吹气或用手握住, 可以看到其温湿度发生明显变化.