4. 定时器抽象层设计
任务目标
在嵌入式系统中,不同MCU的硬件定时器寄存器操作差异大。本案例将硬件定时器封装为通用定时器接口,支持周期/单次模式配置与回调注册,使上层业务代码不感知具体芯片寄存器,直接复用定时服务。
核心接口设计
/* timer_hal.h */
#ifndef TIMER_HAL_H
#define TIMER_HAL_H
#include <stdint.h>
typedef enum {
TIMER_MODE_ONESHOT = 0,
TIMER_MODE_PERIODIC
} timer_mode_t;
typedef void (*timer_callback_t)(void *arg);
typedef struct {
uint32_t period_us; /* 定时周期,单位微秒 */
timer_mode_t mode;
timer_callback_t callback;
void *cb_arg;
} timer_cfg_t;
/* 初始化定时器硬件,返回0成功 */
int timer_hal_init(void);
/* 启动定时器,timer_id为0~N-1 */
int timer_hal_start(uint8_t timer_id, const timer_cfg_t *cfg);
/* 停止定时器 */
int timer_hal_stop(uint8_t timer_id);
#endif /* TIMER_HAL_H */
关键完整实现(以STM32 HAL库为底层)
/* timer_hal.c */
#include "timer_hal.h"
#include "stm32f1xx_hal.h"
#define MAX_TIMER_NUM 4
static TIM_HandleTypeDef htim[MAX_TIMER_NUM];
static timer_callback_t cb_table[MAX_TIMER_NUM];
static void *cb_arg_table[MAX_TIMER_NUM];
static uint8_t timer_busy[MAX_TIMER_NUM];
/* 底层定时器时钟使能与GPIO配置(此处仅列出TIM2~TIM5使能) */
static void hw_timer_clock_enable(uint8_t idx)
{
switch (idx) {
case 0: __HAL_RCC_TIM2_CLK_ENABLE(); break;
case 1: __HAL_RCC_TIM3_CLK_ENABLE(); break;
case 2: __HAL_RCC_TIM4_CLK_ENABLE(); break;
case 3: __HAL_RCC_TIM5_CLK_ENABLE(); break;
default: break;
}
}
static void hw_timer_base_init(uint8_t idx, uint32_t period_us)
{
/* 假设系统时钟72MHz,预分频72-1,则计数频率1MHz,1 tick = 1us */
htim[idx].Instance = (TIM_TypeDef *)(TIM2_BASE + idx * 0x400);
htim[idx].Init.Prescaler = 72 - 1;
htim[idx].Init.CounterMode = TIM_COUNTERMODE_UP;
htim[idx].Init.Period = period_us - 1;
htim[idx].Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim[idx]);
}
int timer_hal_init(void)
{
for (int i = 0; i < MAX_TIMER_NUM; i++) {
timer_busy[i] = 0;
cb_table[i] = 0;
cb_arg_table[i] = 0;
}
return 0;
}
int timer_hal_start(uint8_t timer_id, const timer_cfg_t *cfg)
{
if (timer_id >= MAX_TIMER_NUM || cfg == 0 || cfg->callback == 0)
return -1;
if (timer_busy[timer_id])
return -2;
hw_timer_clock_enable(timer_id);
hw_timer_base_init(timer_id, cfg->period_us);
cb_table[timer_id] = cfg->callback;
cb_arg_table[timer_id] = cfg->cb_arg;
timer_busy[timer_id] = 1;
if (cfg->mode == TIMER_MODE_ONESHOT)
htim[timer_id].Init.Period = cfg->period_us - 1;
HAL_TIM_Base_Start_IT(&htim[timer_id]);
return 0;
}
int timer_hal_stop(uint8_t timer_id)
{
if (timer_id >= MAX_TIMER_NUM)
return -1;
if (!timer_busy[timer_id])
return -2;
HAL_TIM_Base_Stop_IT(&htim[timer_id]);
timer_busy[timer_id] = 0;
cb_table[timer_id] = 0;
return 0;
}
/* 中断回调:由HAL库在中断中调用 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim_ptr)
{
for (int i = 0; i < MAX_TIMER_NUM; i++) {
if (htim_ptr->Instance == htim[i].Instance && timer_busy[i]) {
if (cb_table[i])
cb_table[i](cb_arg_table[i]);
/* 单次模式:触发后自动停止 */
if (htim[i].Init.Period && htim[i].State == HAL_TIM_STATE_READY) {
/* 此处由HAL库自动处理单次模式停止 */
}
break;
}
}
}
上层调用示例
/* app_timer_demo.c */
#include "timer_hal.h"
#include <stdio.h>
static void led_blink_cb(void *arg)
{
(void)arg;
printf("timer tick\r\n");
}
void app_timer_demo(void)
{
timer_cfg_t cfg;
timer_hal_init();
cfg.period_us = 1000000; /* 1秒 */
cfg.mode = TIMER_MODE_PERIODIC;
cfg.callback = led_blink_cb;
cfg.cb_arg = 0;
if (timer_hal_start(0, &cfg) == 0)
printf("timer0 started\r\n");
}
运行与验证
- 依赖:STM32F1系列HAL库,系统时钟配置为72MHz(若时钟不同,需调整
Prescaler使计数频率为1MHz)。 - 编译:将
timer_hal.c加入工程,包含timer_hal.h头文件路径。 - 调用:在main函数中调用
app_timer_demo(),并确保HAL定时器中断已使能(NVIC配置在HAL_TIM_Base_Start_IT内部完成)。 - 通过条件:串口每秒输出一次
timer tick,且系统不进入HardFault。将cfg.mode改为TIMER_MODE_ONESHOT后重新编译运行,应只输出一次timer tick,之后定时器停止。
常见问题
- 定时不准确:检查
Prescaler是否与系统时钟匹配。若系统时钟为72MHz,预分频72-1得到1MHz计数时钟,Period = period_us - 1即得到微秒级定时。 - 中断未触发:确认在
HAL_TIM_Base_Start_IT前已调用HAL_NVIC_EnableIRQ(HAL库在Start_IT中自动完成),且定时器全局中断已在stm32f1xx_hal_msp.c中正确配置。 - 多定时器冲突:本实现固定映射TIM2~TIM5,若芯片资源不足可减少
MAX_TIMER_NUM或改用其他定时器实例。