DHT11实验_定时器实现us延时
143.5.15DHT11实验(定时器实现us延时)
/* main.c */
#include "main.h" // 提供 HAL 类型与错误处理函数
#include "tim.h" // 声明 TIM1 句柄和微秒延时所需的定时器初始化函数
#include "usart.h" // 声明 USART1 初始化函数和课程使用的 printf 宏
#include "gpio.h" // 声明 GPIO 时钟初始化函数
#include "dht11.h" // 声明 DHT11 数据数组与读取接口
void SystemClock_Config(void); // 声明 72 MHz 系统时钟配置函数
int main(void) // 程序入口:初始化外设并每秒读取一次 DHT11
{
HAL_Init(); // 初始化 HAL 库、SysTick 和中断优先级基础配置
SystemClock_Config(); // 使用外部晶振和 PLL 配置系统时钟
MX_GPIO_Init(); // 开启 DHT11 所在 GPIOA 的端口时钟
MX_TIM1_Init(); // 将 TIM1 配成 1 MHz 计数,供 Delay_us() 使用
MX_USART1_UART_Init(); // 初始化 USART1,供温湿度数据输出
while (1) // 周期性读取并显示 DHT11 数据
{
if (DHT_Read()) // 返回 1 时表示课程代码的校验通过
{
printf(" H:%u T:%u \r\n", Data[0], Data[2]); // 输出整数湿度和整数温度
}
HAL_Delay(1000); // 两次读取之间等待 1 s
}
}
void SystemClock_Config(void) // 配置 72 MHz 系统时钟及总线分频
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0}; // 保存 HSE、HSI 和 PLL 振荡器配置
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // 保存系统总线时钟与分频配置
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 选择外部高速晶振
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 开启 HSE
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; // HSE 不预分频
RCC_OscInitStruct.HSIState = RCC_HSI_ON; // 保持内部高速时钟开启
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 开启 PLL
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 输入选择 HSE
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8 MHz 乘 9 得到 72 MHz
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) // 写入振荡器配置
{
Error_Handler(); // 振荡器配置失败
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | // 指定要配置的时钟域
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 同时配置 APB1 与 APB2
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 系统时钟取自 PLL
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // HCLK = 72 MHz
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // PCLK1 = 36 MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // PCLK2 = 72 MHz,TIM1 也取 72 MHz
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) // 设置总线时钟
{
Error_Handler(); // 总线时钟配置失败
}
}
void Error_Handler(void) // HAL 初始化失败时统一进入的错误处理函数
{
__disable_irq(); // 发生不可恢复错误时关闭全局中断
while (1) // 停留在错误状态,便于调试定位
{
}
}
/* dht11.c */
#include "stm32f1xx.h" // 提供 STM32F1 寄存器与基础类型定义
#include "dht11.h" // 提供 DHT11 接口和 TIM1 句柄声明
uint8_t Data[5] = {0x00, 0x00, 0x00, 0x00, 0x00}; // 保存湿度、温度和校验字节
void Delay_us(uint16_t us) // 使用 1 MHz 的 TIM1 产生约 us 微秒阻塞延时
{
uint16_t differ = 0xffff - us; // 计算倒数 us 个计数位置作为起点
__HAL_TIM_SET_COUNTER(&htim1, differ); // 将 TIM1 计数器写入起始值
HAL_TIM_Base_Start(&htim1); // 启动 TIM1 向上计数
while (differ < 0xffff) // 轮询等待计数器到达 0xFFFF
{
differ = __HAL_TIM_GET_COUNTER(&htim1); // 读取当前计数值
}
HAL_TIM_Base_Stop(&htim1); // 到达目标计数后停止 TIM1
}
void DHT_GPIO_SET_OUTPUT(void) // 将 PA5 切换为主机发送起始信号所需的输出模式
{
GPIO_InitTypeDef GPIO_InitStructure; // 保存 PA5 输出配置
GPIO_InitStructure.Pin = GPIO_PIN_5; // DHT11 DATA 连接 PA5
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 主机发送起始信号时使用推挽输出
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; // 使用高速 GPIO 输出
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); // 将输出配置写入 GPIOA
}
void DHT_GPIO_SET_INPUT(void) // 将 PA5 切换为读取传感器应答和数据的输入模式
{
GPIO_InitTypeDef GPIO_InitStructure; // 保存 PA5 输入配置
GPIO_InitStructure.Pin = GPIO_PIN_5; // DHT11 DATA 仍使用 PA5
GPIO_InitStructure.Mode = GPIO_MODE_INPUT; // 释放总线并读取传感器数据
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; // 保留课程工程中的速度配置
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); // 将输入配置写入 GPIOA
}
uint8_t DHT_Read_Byte(void) // 按 DHT11 高位先发的时序读取一个字节
{
uint8_t ReadData = 0; // 累积本次接收的 8 个数据位
uint8_t temp; // 暂存当前位判断结果 0 或 1
uint8_t retry = 0; // 等待电平变化时的超时计数
uint8_t i; // 8 位数据循环下标
for (i = 0; i < 8; i++) // DHT11 每个字节固定发送 8 位
{
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == 0 && retry < 100) // 等待每一位起始低电平结束
{
Delay_us(1); // 每次等待约 1 us
retry++; // 防止信号异常时永久卡在循环中
}
retry = 0; // 为等待本位高电平结束重新计数
Delay_us(40); // 在高电平开始约 40 us 后读取电平,用脉宽区分 0 和 1
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == 1) // 40 us 后仍为高电平则判为 1
{
temp = 1; // 保存逻辑 1
}
else // 40 us 后已经为低电平,当前位为 0
{
temp = 0; // 40 us 前已经变低则判为逻辑 0
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == 1 && retry < 100) // 等待当前位高电平结束
{
Delay_us(1); // 每次等待约 1 us
retry++; // 累加等待时间并限制最大循环次数
}
retry = 0; // 为下一位重新计数
ReadData <<= 1; // 为当前位腾出最低位位置,DHT11 高位先发
ReadData |= temp; // 把当前位写入结果最低位
}
return ReadData; // 返回组合完成的一个字节
}
uint8_t DHT_Read(void) // 发送起始信号、接收五字节并执行课程中的校验
{
uint8_t retry = 0; // 等待 DHT11 应答电平时的超时计数
uint8_t i; // 五个数据字节的循环下标
DHT_GPIO_SET_OUTPUT(); // PA5 切换为输出,准备发送起始信号
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 主机拉低 DATA
HAL_Delay(18); // 低电平保持 18 ms
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // 主机先把 DATA 拉高
Delay_us(20); // 高电平保持约 20 us
DHT_GPIO_SET_INPUT(); // PA5 切换为输入,把总线交给 DHT11
Delay_us(20); // 等待 DHT11 开始应答
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_RESET) // 检测到应答低电平后继续读取
{
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_RESET && retry < 100) // 等应答低电平结束
{
Delay_us(1); // 每次等待约 1 us
retry++; // 限制低电平等待次数
}
retry = 0; // 为应答高电平等待重新计数
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_SET && retry < 100) // 等应答高电平结束
{
Delay_us(1); // 每次等待约 1 us
retry++; // 限制高电平等待次数
}
retry = 0; // 应答完成,清零计数
for (i = 0; i < 5; i++) // 连续读取湿度、温度和校验共 5 字节
{
Data[i] = DHT_Read_Byte(); // 按发送顺序保存当前字节
}
Delay_us(50); // 按课程代码在一帧读取后等待 50 us
}
uint32_t sum = Data[0] + Data[1] + Data[2] + Data[3]; // 计算前四个数据字节之和
if (sum == Data[4]) // 与第五个校验字节比较
{
return 1; // 校验相等,通知主循环打印数据
}
else // 前四字节之和与校验字节不相等
{
return 0; // 校验不相等,本次数据不打印
}
}
/* dht11.h */
#ifndef __DHT11_H // 防止 dht11.h 被重复包含
#define __DHT11_H // 定义 dht11.h 的头文件保护宏
#include "stm32f1xx_hal.h" // 获取 HAL GPIO 和定时器接口
#include "stm32f1xx.h" // 获取 STM32F1 基础定义
extern uint8_t Data[5]; // 引用 dht11.c 中的五字节接收数组
extern TIM_HandleTypeDef htim1; // 引用 tim.c 中用于微秒延时的 TIM1 句柄
void Delay_us(uint16_t us); // 声明微秒级阻塞延时函数
void DHT_GPIO_SET_OUTPUT(void); // 声明 PA5 输出模式切换函数
void DHT_GPIO_SET_INPUT(void); // 声明 PA5 输入模式切换函数
uint8_t DHT_Read_Byte(void); // 声明单字节读取函数
uint8_t DHT_Read(void); // 声明一帧 DHT11 数据读取函数
#endif // dht11.h 头文件保护结束
/* tim.c */
#include "tim.h" // 引入 TIM1 句柄声明和 HAL 定时器接口
TIM_HandleTypeDef htim1; // 保存 TIM1 的实例和初始化参数
void MX_TIM1_Init(void) // 将 TIM1 配成 1 MHz 向上计数的基本定时器
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0}; // 配置 TIM1 的计数时钟源
TIM_MasterConfigTypeDef sMasterConfig = {0}; // 配置主从同步与触发输出
htim1.Instance = TIM1; // 选择高级定时器 TIM1
htim1.Init.Prescaler = 71; // 72 MHz 除以 72,计数周期为 1 us
htim1.Init.CounterMode = TIM_COUNTERMODE_UP; // 使用向上计数
htim1.Init.Period = 65535; // 自动重装值设为 16 位最大值
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 不额外分频定时器采样时钟
htim1.Init.RepetitionCounter = 0; // 不使用高级定时器重复计数
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // 禁用 ARR 预装载
if (HAL_TIM_Base_Init(&htim1) != HAL_OK) // 初始化 TIM1 基本计数单元
{
Error_Handler(); // TIM1 基本初始化失败
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; // 使用内部定时器时钟
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) // 写入时钟源配置
{
Error_Handler(); // TIM1 时钟源配置失败
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; // 不向其他外设输出特殊触发
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; // 不使用定时器主从同步
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) // 写入主模式配置
{
Error_Handler(); // 主模式配置失败
}
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_baseHandle) // 为 TIM1 开启外设时钟
{
if (tim_baseHandle->Instance == TIM1) // 只为 TIM1 执行底层初始化
{
__HAL_RCC_TIM1_CLK_ENABLE(); // 开启 TIM1 外设时钟
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_baseHandle) // 反初始化时关闭 TIM1 时钟
{
if (tim_baseHandle->Instance == TIM1) // 只为 TIM1 执行底层反初始化
{
__HAL_RCC_TIM1_CLK_DISABLE(); // 关闭 TIM1 外设时钟
}
}
/* tim.h */
#ifndef __TIM_H__ // 防止 tim.h 被重复包含
#define __TIM_H__ // 定义 tim.h 的头文件保护宏
#include "main.h" // 获取 HAL 定时器句柄类型
extern TIM_HandleTypeDef htim1; // 引用 tim.c 中唯一的 TIM1 句柄
void MX_TIM1_Init(void); // 声明 TIM1 初始化函数
#endif /* __TIM_H__ */ // tim.h 头文件保护结束
/* usart.c */
#include "usart.h" // 引入 USART1 句柄、缓冲区和初始化声明
uint8_t u_buf[256]; // 保存 printf 格式化后的待发送文本
UART_HandleTypeDef huart1; // 保存 USART1 的实例和配置参数
void MX_USART1_UART_Init(void) // 配置 USART1 为 115200-8-N-1
{
huart1.Instance = USART1; // 选择 USART1 外设
huart1.Init.BaudRate = 115200; // 波特率设为 115200 bit/s
huart1.Init.WordLength = UART_WORDLENGTH_8B; // 每帧 8 位数据
huart1.Init.StopBits = UART_STOPBITS_1; // 使用 1 位停止位
huart1.Init.Parity = UART_PARITY_NONE; // 不使用奇偶校验
huart1.Init.Mode = UART_MODE_TX_RX; // 同时使能发送和接收
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 不使用 RTS/CTS 硬件流控
huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 使用 16 倍过采样
if (HAL_UART_Init(&huart1) != HAL_OK) // 初始化 USART1 并检查返回值
{
Error_Handler(); // USART1 初始化失败
}
}
void HAL_UART_MspInit(UART_HandleTypeDef *uartHandle) // 配置 USART1 时钟与 PA9/PA10
{
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 保存 USART1 引脚配置
if (uartHandle->Instance == USART1) // 只为 USART1 执行底层初始化
{
__HAL_RCC_USART1_CLK_ENABLE(); // 开启 USART1 外设时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 开启 PA9、PA10 所在 GPIOA 时钟
GPIO_InitStruct.Pin = GPIO_PIN_9; // PA9 对应 USART1_TX
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // TX 配置为复用推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 使用高速输出模式
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 应用 PA9 配置
GPIO_InitStruct.Pin = GPIO_PIN_10; // PA10 对应 USART1_RX
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // RX 配置为输入
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不启用内部上下拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 应用 PA10 配置
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef *uartHandle) // 关闭 USART1 时钟并复位引脚
{
if (uartHandle->Instance == USART1) // 只为 USART1 执行底层反初始化
{
__HAL_RCC_USART1_CLK_DISABLE(); // 关闭 USART1 外设时钟
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9 | GPIO_PIN_10); // 复位 PA9 与 PA10 配置
}
}
/* usart.h */
#ifndef __USART_H__ // 防止 usart.h 被重复包含
#define __USART_H__ // 定义 usart.h 的头文件保护宏
#include "main.h" // 获取 HAL 串口句柄类型
extern UART_HandleTypeDef huart1; // 引用 usart.c 中的 USART1 句柄
extern uint8_t u_buf[256]; // 引用 usart.c 中的格式化缓冲区
// 将可变参数先格式化到 u_buf,再通过 USART1 阻塞发送。
#define printf(...) /* 接收与标准 printf 相同的可变参数 */ \
HAL_UART_Transmit(&huart1, (uint8_t *)u_buf, /* 用 USART1 发送 u_buf */ \
sprintf((char *)u_buf, __VA_ARGS__), 0xffff) /* sprintf 返回发送长度 */
void MX_USART1_UART_Init(void); // 声明 USART1 初始化函数
#endif /* __USART_H__ */ // usart.h 头文件保护结束
/* gpio.c */
#include "gpio.h" // 引入 GPIO 初始化声明
void MX_GPIO_Init(void) // 开启 DHT11 与 USART1 所在的 GPIOA 时钟
{
__HAL_RCC_GPIOA_CLK_ENABLE(); // 开启 PA5、PA9 和 PA10 所在 GPIOA 时钟
}
/* gpio.h */
#ifndef __GPIO_H__ // 防止 gpio.h 被重复包含
#define __GPIO_H__ // 定义 gpio.h 的头文件保护宏
#include "main.h" // 获取 HAL 基础类型
void MX_GPIO_Init(void); // 声明 GPIO 时钟初始化函数
#endif /* __GPIO_H__ */ // gpio.h 头文件保护结束
/* main.h */
#ifndef __MAIN_H // 防止 main.h 被重复包含
#define __MAIN_H // 定义 main.h 的头文件保护宏
#include "stm32f1xx_hal.h" // 引入 STM32F1 HAL 库类型与接口
void Error_Handler(void); // 声明统一错误处理函数
#endif /* __MAIN_H */ // main.h 头文件保护结束
一、DHT11温湿度传感器实验
1.1 任务资源图
- 本实验使用 STM32F103ZET6 读取 DHT11 温湿度传感器,并经 USART1 输出温度和湿度。
HAL_Delay()的单位是毫秒,而 DHT11 的应答和数据位判别需要几十微秒的时序,因此使用 TIM1 实现Delay_us()。- TIM1 在本实验中只作为由程序启动、读取和停止的计数器,不使用 PWM、输入捕获、外部通道或定时器中断。
- DHT11 的数据线使用
PA5,源码中的GPIOA与GPIO_PIN_5是实际依据。
1.2 实验演示
- DHT11 三针模块与 STM32 的连接如下:
STM32F103ZET6 DHT11 三针模块
------------- ----------------
3.3V ---------------------> VCC
GND ---------------------> GND
PA5 <--------------------> DATA
DATA是双向数据线。STM32 发起通信时将 PA5 配为推挽输出并拉低;随后将 PA5 切为输入,释放总线,由 DHT11 输出应答和 40 位数据。双方不能同时驱动该线。- 视频画面能确认本课使用的是带小电路板的三针 DHT11 模块,但看不清板上 DATA 是否已接上拉电阻,因此不能直接断言“该模块一定自带上拉”。实际接线前应查看所购模块原理图,或断电后测量
DATA与VCC之间的电阻;若没有上拉,再按模块手册连接外部上拉电阻。 - 课程说明该模块的
VCC可接 3.3 V 或 5 V。供电电压和 DATA 上拉电压是两件事:若模块把 DATA 上拉到 VCC,使用 5 V 供电时还必须核对 STM32 对应引脚的输入电压规格;没有模块原理图时,不应凭外观判断数据线电平。 - 串口连接为
PA9 -> USB 转串口模块 RX、PA10 <- USB 转串口模块 TX、GND -> GND,参数为 115200 bit/s、8 位数据位、1 位停止位、无校验。 - 用手靠近传感器或改变周围空气状态时,串口中的温湿度会缓慢变化。DHT11 的采样间隔应不小于 1 s。
1.3 代码分析
1.3.1 系统时钟配置
- 系统时钟为 72 MHz。TIM1 位于 APB2,APB2 未分频时 TIM1 输入时钟也是 72 MHz。
MX_GPIO_Init()只需要开启 GPIOA 时钟;TIM1 没有配置外部通道,因此不需要为 TIM1 分配输入捕获或 PWM 引脚。MX_TIM1_Init()、MX_USART1_UART_Init()必须在第一次调用DHT_Read()前完成。
1.3.2 定时器一初始化
- TIM1 使用内部时钟,向上计数,
Prescaler=71、Period=65535,不启用中断。 - 预分频器的实际分频系数是
71 + 1 = 72,因此计数频率为:
$ f_{CNT}=\frac{72\ \text{MHz}}{71+1}=1\ \text{MHz} $
- 计数器每增加 1 就经过 $1\ \mu s$。16 位计数器的范围是 0 到 65535,单次延时上限约为 65.535 ms。
1.3.3 DHT11分析
DHT_GPIO_SET_OUTPUT()将 PA5 配置为推挽输出,用于发送开始信号;DHT_GPIO_SET_INPUT()将 PA5 配置为输入,用于接收传感器应答和数据。DHT_Read()的流程是:PA5 拉低至少 18 ms,拉高约 20 us,切换为输入,等待 DHT11 的低电平和高电平应答,再读取 5 个字节。- DHT11 发送的数据依次为湿度整数、湿度小数、温度整数、温度小数和校验和。本实验打印
Data[0]与Data[2],即整数湿度和整数温度。
1.3.4 DHT11_Read_Byte分析
DHT_Read_Byte()连续读取 8 位。每一位先等待约 50 us 的低电平结束,再等待 40 us 后读取电平。- 高电平约 26 到 28 us 表示
0,约 70 us 表示1;在高电平开始 40 us 后采样,短高电平已经结束,长高电平仍然存在。 - 每读取一位执行一次左移并写入当前位,最终按照高位在前的顺序组成一个字节。
1.3.5 Delay_us分析
Delay_us()为 DHT11 的应答等待和位判别提供微秒级延时;毫秒级的开始信号仍使用HAL_Delay(18)。- DHT11 协议规定,校验字节等于前四个数据字节之和的低 8 位:
$ \text{checksum}=(Data[0]+Data[1]+Data[2]+Data[3])\ &\ 0xFF $
- 课程源码实际使用
uint32_t sum,并直接比较sum == Data[4],没有显式截取低 8 位。本课正常温湿度数据之和未超过一个字节时,两种写法结果相同;这里按课程源码呈现,不擅自改写其判断逻辑。 retry < 100的作用是避免等待电平时永久卡死,但读取函数没有单独返回“位超时”状态,最终仍依靠校验结果判断本帧是否可用。
1.4 Delay_us分析
Delay_us(us)先把 TIM1 的计数器写为0xFFFF-us,启动 TIM1,再轮询当前计数值。计数到0xFFFF时停止定时器并返回。
Delay_us(20)
CNT: 65515 -> 65516 -> ... -> 65535
|<--------- 20 个 1 us 计数周期 --------->|
- 该函数是忙等延时,CPU 在等待期间不能处理其他任务,适合 DHT11 所需的几十微秒短时序;长时间延时应使用 SysTick、定时器中断或操作系统延时机制。
- 这种方法的核心是先将定时器配置成已知计数单位,再通过写入计数器起点、启动、读取计数值和停止得到可控短延时。
1.5 配置界面配置定时器
- 在 CubeMX 中将 TIM1 的 Clock Source 设为 Internal Clock,Prescaler 设为 71,Counter Mode 设为 Up,Counter Period 设为 65535。
- 不配置 PWM、输入捕获通道,也不启用 TIM1 中断;Auto-reload preload 可保持 Disable。
- 将
dht11.c、dht11.h加入工程,dht11.h中使用正确的htim1句柄。使用其他定时器时,需要同步修改句柄和初始化函数。
1.6 头文件透析
dht11.h应声明Data[5]、Delay_us()、GPIO 模式切换函数和读取函数;tim.h应声明htim1。- 复用驱动时,将
GPIOA、GPIO_PIN_5改为实际接入的端口和引脚,并保证数据线有可靠上拉、传感器供电与 MCU 数据引脚电平兼容。 - 主循环在每次读取后执行
HAL_Delay(1000)。校验失败时不会输出本次数据;但课程源码在未检测到新应答时不会清空Data[],若数组中仍保存着上一帧有效数据,仍可能再次打印旧值。这是阅读当前代码时需要知道的实际行为,不代表本节还缺少了一段源码。 - 排查顺序:检查 VCC、GND、DATA 与 PA5 的连接;检查上拉与供电;检查 TIM1 的 1 MHz 计数配置;最后检查串口、源文件是否加入工程及初始化顺序。