4. 定时器中断实现精确延时
任务说明
在MCU裸机开发中,HAL_Delay()依赖SysTick且为阻塞式,无法在延时期间响应其他中断或执行任务。本案例利用STM32 HAL库的定时器中断,实现一个非阻塞的毫秒级延时函数:调用延时后立即返回,CPU可继续执行其他代码,延时到达后通过回调通知。
硬件连接与资源分配
- 使用TIM3,时钟源为内部APB1定时器时钟(假设84MHz,若实际不同需调整预分频值)
- 通道无需映射到引脚,仅使用更新中断
- 延时范围:1ms ~ 65535ms(受16位自动重载寄存器限制,可扩展)
实现代码
// timer_delay.h
#ifndef TIMER_DELAY_H
#define TIMER_DELAY_H
#include "main.h"
typedef void (*DelayCallback)(void);
void TimerDelay_Init(TIM_HandleTypeDef *htim);
void TimerDelay_Start(uint16_t ms, DelayCallback cb);
void TimerDelay_Stop(void);
uint8_t TimerDelay_IsRunning(void);
#endif
// timer_delay.c
#include "timer_delay.h"
static TIM_HandleTypeDef *delay_tim;
static DelayCallback delay_cb;
static volatile uint8_t delay_running = 0;
void TimerDelay_Init(TIM_HandleTypeDef *htim) {
delay_tim = htim;
delay_cb = NULL;
delay_running = 0;
HAL_TIM_Base_Start_IT(delay_tim); // 启动定时器中断
}
void TimerDelay_Start(uint16_t ms, DelayCallback cb) {
if (delay_running) return; // 已有延时进行中,拒绝新请求
// 计算重载值:定时器时钟84MHz,预分频84-1 → 1MHz计数频率,即1us/tick
// 需要ms级:重载值 = ms * 1000 - 1
__HAL_TIM_SET_PRESCALER(delay_tim, 84 - 1);
__HAL_TIM_SET_AUTORELOAD(delay_tim, (uint32_t)ms * 1000 - 1);
__HAL_TIM_SET_COUNTER(delay_tim, 0);
delay_cb = cb;
delay_running = 1;
HAL_TIM_Base_Start_IT(delay_tim);
}
void TimerDelay_Stop(void) {
if (delay_running) {
HAL_TIM_Base_Stop_IT(delay_tim);
delay_running = 0;
delay_cb = NULL;
}
}
uint8_t TimerDelay_IsRunning(void) {
return delay_running;
}
// 在stm32f1xx_it.c或stm32f4xx_it.c的中断回调中调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim == delay_tim && delay_running) {
delay_running = 0;
HAL_TIM_Base_Stop_IT(delay_tim);
if (delay_cb) {
DelayCallback cb = delay_cb;
delay_cb = NULL;
cb(); // 执行延时完成回调
}
}
}
// 使用示例(main.c片段)
#include "timer_delay.h"
TIM_HandleTypeDef htim3;
volatile uint8_t led_state = 0;
void DelayDone_Callback(void) {
// 延时完成,翻转LED
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
led_state = !led_state;
}
int main(void) {
HAL_Init();
SystemClock_Config(); // 配置系统时钟84MHz
MX_GPIO_Init();
MX_TIM3_Init(); // 基础初始化,不启动中断
TimerDelay_Init(&htim3);
while (1) {
// 非阻塞延时500ms,期间可执行其他任务
TimerDelay_Start(500, DelayDone_Callback);
// 延时期间CPU可处理其他事务
while (TimerDelay_IsRunning()) {
// 执行其他低优先级任务,如按键扫描、通信处理等
ProcessOtherTasks();
}
// 延时完成后继续主循环
}
}
关键配置说明
- 预分频器设为83(即84-1),将84MHz时钟分频为1MHz,计数周期1μs
- 自动重载值 = 延时毫秒数 × 1000 - 1,实现1ms精度
- 若系统时钟不是84MHz,需按实际时钟调整预分频值,保证计数频率为1MHz
- 中断回调函数
HAL_TIM_PeriodElapsedCallback需在工程的中断管理文件中实现
验证方法
- 编译烧录后,观察LED以约500ms周期翻转(亮500ms灭500ms)
- 在
ProcessOtherTasks()中设置断点或翻转另一个GPIO,确认延时期间主循环仍在执行——若LED翻转的同时另一个测试引脚也在翻转,说明非阻塞生效 - 用逻辑分析仪或示波器测量LED引脚波形,高/低电平持续时间应为500ms±1ms
- 连续调用多次
TimerDelay_Start,确认第二次调用在第一次完成后才生效(delay_running保护)
判定标准:LED周期翻转且主循环在延时期间不被阻塞,即判定实现成功。
代码集成说明
将timer_delay.c/h加入工程,在CubeMX中完成TIM3的基础时钟配置(不使能中断启动),然后在main.c中调用TimerDelay_Init()。中断回调函数HAL_TIM_PeriodElapsedCallback需要放在工程已有的中断回调区域(如stm32f1xx_it.c),确保定时器更新中断能正确路由到该函数。若使用其他STM32系列,仅需调整预分频值以匹配实际定时器时钟频率。