STM32 定时器中断的完整配置流程(HAL库详解)
👁 4 阅读 · 2026-08-13 · STM32 定时器
定时器是STM32中最常用的外设之一,而定时器中断则让CPU能够在精确的时间点执行任务,无需空耗主循环。本文基于HAL库,从定时器的工作原理出发,详细讲解STM32定时器中断的完整配置流程,包括时钟使能、参数初始化、中断优先级设置以及中断回调函数的编写,并提供可直接移植的代码示例,帮助开发者快速掌握这一核心技能。
## 一、定时器中断原理概述
STM32定时器本质是一个16位/32位的向上/向下计数器,其时钟源来自内部时钟(如APB1 Timer Clock)或外部信号。当计数器计数达到设定的自动重装载值(ARR)时,会产生更新事件(Update Event)。若使能了更新中断,CPU将暂停当前任务,跳转到中断服务函数执行用户逻辑,待处理完毕后再返回。
HAL库将这一过程封装成了三个关键环节:
- 定时器句柄初始化(`HAL_TIM_Base_Init`)
- 时基单元配置(`TIM_MasterConfigTypeDef`)
- 中断使能与回调函数(`HAL_TIM_IRQHandler` / `HAL_TIM_PeriodElapsedCallback`)
理解这三个环节,就能轻松驾驭任何STM32型号的定时器中断。
## 二、HAL库定时器中断配置步骤
### 1. 使能定时器时钟
使用CubeMX或直接操作RCC寄存器,使能对应定时器的时钟。例如TIM2挂在APB1总线上:
```c
__HAL_RCC_TIM2_CLK_ENABLE();
```
若使用CubeMX,勾选TIM2后系统自动生成此句,无需手动添加。
### 2. 初始化时基参数
时基单元决定定时器计数频率和溢出周期。常用参数如下:
- `Prescaler`(预分频):将定时器时钟分频,例如APB1 Timer Clock为84MHz,设Prescaler为8399则计数频率为10kHz。
- `Period`(自动重装载值):计数达到该值产生更新事件,例如设Period为4999则定时周期为500ms。
- `CounterMode`:通常用向上计数模式(`TIM_COUNTERMODE_UP`)。
- `ClockDivision`:一般设为`TIM_CLOCKDIVISION_DIV1`。
使用HAL库编写:
```c
TIM_HandleTypeDef htim2;
void MX_TIM2_Init(void)
{
htim2.Instance = TIM2;
htim2.Init.Prescaler = 8399; // 84MHz / 8400 = 10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 4999; // 10kHz / 5000 = 2Hz,即500ms中断一次
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
}
```
### 3. 使能更新中断并配置NVIC
初始化完成后,需要使能定时器更新中断,并在NVIC中设置中断优先级。注意优先级分组需在主函数中提前配置(`HAL_NVIC_SetPriorityGrouping`)。
```c
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
if (htim_base->Instance == TIM2)
{
__HAL_RCC_TIM2_CLK_ENABLE(); // 若已在外部使能可省略
// 设置中断优先级,抢占优先级1,子优先级0
HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
// 使能TIM2全局中断
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
}
```
在CubeMX生成的代码中,`HAL_TIM_Base_MspInit`会自动被调用,无需手动调用。但需确保其内部已包含NVIC配置。
### 4. 编写中断服务函数(IRQHandler)
每个定时器中断源都有固定的中断函数名,如TIM2对应`TIM2_IRQHandler`。在中断服务函数中必须调用HAL库的`HAL_TIM_IRQHandler`,它会自动判断中断类型并调用对应的回调函数。
```c
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
}
```
### 5. 重写回调函数
HAL库使用弱定义的`HAL_TIM_PeriodElapsedCallback`,用户需要在自己的代码中重新实现该函数。注意此函数对所有使用HAL_TIM_IRQHandler的定时器共用,因此需要通过句柄判断具体是哪个定时器。
```c
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
// 用户代码:翻转LED或执行周期任务
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}
```
### 6. 启动定时器中断
在主函数初始化完成后,调用以下函数启动定时器:
```c
HAL_TIM_Base_Start_IT(&htim2);
```
`_IT`后缀表示中断模式启动,区别于阻塞模式`HAL_TIM_Base_Start`。此后定时器开始计数,每次溢出进入中断。
## 三、完整代码示例(TIM2 产生500ms周期中断)
```c
/* main.c 精简示例 */
TIM_HandleTypeDef htim2;
void Error_Handler(void)
{
while(1) {}
}
void MX_TIM2_Init(void)
{
htim2.Instance = TIM2;
htim2.Init.Prescaler = 8399;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 4999;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
if (htim_base->Instance == TIM2)
{
__HAL_RCC_TIM2_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
}
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 翻转板载LED
}
}
int main(void)
{
HAL_Init();
// 初始化系统时钟(如72MHz或84MHz)
SystemClock_Config();
// 初始化LED引脚(略)
MX_GPIO_Init();
// 初始化定时器
MX_TIM2_Init();
// 启动定时器中断
HAL_TIM_Base_Start_IT(&htim2);
while(1)
{
// 主循环可以执行其他低优先级任务
}
}
```
## 四、注意事项与常见问题
1. **时钟源频率**:不同STM32系列APB1定时器时钟可能不同,务必根据实际主频计算Prescaler和Period。例如F103默认72MHz,F407若APB1分频为2则定时器时钟仍为84MHz。
2. **中断服务函数命名**:一定不要随意修改`TIMx_IRQHandler`函数名,否则中断无法正确链接。
3. **回调函数中避免长时间操作**:中断回调函数应尽量精简,不要使用`HAL_Delay`或阻塞式串口打印,否则会拖慢系统响应。需要大工作量时可置标志位,在主循环中处理。
4. **AutoReloadPreload**:建议使能预装载,这样修改Period时不会立即生效,而是等本次溢出后才更新,避免周期跳动。
5. **MPU与Cache(Cortex-M7)**:若使用F7/H7系列,需确保定时器相关寄存器访问未受MPU限制,并注意D-Cache对DMA等的作用,但普通中断不受影响。
6. **多定时器共用回调**:`HAL_TIM_PeriodElapsedCallback`是所有定时器更新中断的统一回调,务必通过`htim->Instance`区分,否则多个定时器会相互干扰。
7. **NVIC优先级设置**:若系统使用FreeRTOS,建议将定时器中断优先级设置为低于内核最高优级(如5~15),避免破坏临界区。
## 五、总结
使用HAL库配置STM32定时器中断的流程可以浓缩为:时钟使能→时基初始化→NVIC配置→IRQHandler内调用HAL_TIM_IRQHandler→重写PeriodElapsedCallback→启动`_IT`模式。掌握这套流程,就能灵活生成精确的毫秒级、微秒级定时中断,为按键扫描、传感器采样、通信超时处理等场景提供可靠的时基基础。实践时多注意时钟树和中断优先级这两个细节,就能避免大部分低级错误。