1. 定时器中断的基本结构
结合这张图,我们可以系统地介绍 STM32 的定时器中断 机制。整体上,定时器中断的结构主要由 时钟输入、时基单元、运行控制和中断输出控制 组成,每个部分的功能如下:
1.1. 时钟输入(Clock Sources)
定时器的计数依赖于时钟源,STM32 定时器可以使用以下几种时钟:
- 内部时钟(RCC):由主时钟(APB1 或 APB2)提供给定时器使用。
- 外部时钟(ETR):可以通过 GPIO 端口引入外部时钟信号。
- 其他定时器的触发(ITRx):定时器之间可以进行触发,形成级联结构,实现更复杂的定时任务。
- 捕获输入通道(Tix):通过 GPIO 端口捕获外部信号,基于输入信号进行计数(如编码器模式)。
1.2. 时基单元(Time Base Unit)
时基单元是定时器的核心,主要由以下组件组成:
- PSC(预分频器):控制定时器的计数速率。例如,如果主时钟频率为 72MHz,而预分频器设置为 71,则定时器的计数频率变为 1MHz。
- CNT(计数器):定时器的核心计数单元,它会从 0 计数到 ARR(自动重装载寄存器) 设定的值。
- ARR(自动重装载寄存器):定义计数器的最大值。当
CNT
计数到ARR
,触发更新事件(Update Event, UE),并可以产生中断。
1.3. 运行控制(Control Logic)
- 负责管理定时器的启停、模式选择以及事件触发。
- 定时器可以配置为 单次模式(计数到 ARR 后停止)或 连续模式(每次计数到 ARR 自动重置)。
- 可选触发模式,如外部信号触发计数。
1.4. 中断输出控制
- 当
CNT
计数到ARR
,定时器触发 更新事件(UE),并通过 NVIC(嵌套向量中断控制器) 处理中断请求。 - 如果使能了定时器中断,则 CPU 执行相应的 中断服务程序(ISR),例如:
- 触发 LED 变换(定时闪烁)
- 控制 PWM 输出
- 采集传感器数据等
1.5. 运行流程总结
- 配置时钟源(内部/外部)。
- 配置时基单元(设置
PSC
和ARR
)。 - 使能定时器中断(NVIC 配置)。
- 定时器计数(CNT 从 0 计数到 ARR)。
- 触发中断,执行中断服务程序(ISR)。
- 清除中断标志,等待下一次中断触发。
1.6. 实际应用
- 周期性任务(如 1s 触发一次)。
- PWM 产生(控制 LED 亮度、电机转速)。
- 输入捕获(测量脉冲信号)。
- 编码器模式(测量电机位置)。
当然,结合 HAL 库(STM32Cube HAL 库)的使用,我们可以更直观地实现定时器中断。
2. HAL 库定时器中断操作
HAL 库封装了底层寄存器操作,使得 STM32 定时器的配置更加简单。基本流程如下:
2.1. 主要步骤
- 初始化定时器(设定时基、预分频等)。
- 开启定时器中断。
- 编写中断回调函数。
- 清除中断标志,处理中断任务。
2.2. HAL 库代码示例
假设我们需要使用 TIM2 定时器,以 1s 触发一次中断,并在中断中翻转 LED。
2.2.1 定时器初始化
在 main.c
里初始化定时器:
TIM_HandleTypeDef htim2; // 定义定时器句柄
void MX_TIM2_Init(void)
{
__HAL_RCC_TIM2_CLK_ENABLE(); // 使能 TIM2 时钟
htim2.Instance = TIM2; // 选择 TIM2
htim2.Init.Prescaler = 7199; // 预分频(72MHz/7200 = 10kHz)
htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式
htim2.Init.Period = 9999; // 自动重装载值(10kHz / 10k = 1s)
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler(); // 初始化失败处理
}
}
- STM32F103 运行在 72MHz,预分频
7199
,使计数频率变为10kHz
。 - 设定
ARR = 9999
,意味着 每 1s 触发一次中断。
2.2.2 定时器时钟计算公式
STM32F103 的定时器时钟计算遵循如下公式: $f_{counter} = \frac{f_{clock}}{(PSC+1)}$
其中:
- $f_{counter}$ 是定时器的计数频率(单位 Hz)。
- $f_{clock}$ 是定时器输入时钟(单位 Hz)。
- $PSC$ 是 预分频系数(Prescaler)。
代入数值
STM32F103 运行在 72MHz($f_{clock} = 72,000,000 Hz$),如果设定预分频 PSC = 7199
,则:
$f_{\text{counter}} = \frac{7199 + 1}{72,000,000} \
= \frac{7200}{72,000,000} \
= 10,000 \, \text{Hz} \
= 10 \, \text{kHz}$
即 定时器每秒计数 10,000 次。
2.2.3 如何选择合适的 ARR 值
ARR(自动重装载值)决定了中断周期,计算公式如下: $T_{interrupt} = \frac{ARR + 1}{f_{counter}}$
如果希望 定时 1s 触发一次中断,则设定:
$T_{interrupt} = 1 \text{ s}$
$ARR + 1 = f_{counter} \times T_{interrupt}$
$ARR + 1 = 10,000 \times 1 = 10,000$
$ARR = 9999$
所以 ARR = 9999
,即 每 1 秒触发一次定时器中断。
2.2.4 开启定时器中断
在 main.c
里启动定时器:
HAL_TIM_Base_Start_IT(&htim2); // 启用 TIM2 并开启中断
2.2.5 配置 NVIC(中断优先级)
在 stm32f1xx_it.c
里启用 NVIC:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0); // 设置 TIM2 中断优先级
HAL_NVIC_EnableIRQ(TIM2_IRQn); // 使能 TIM2 中断
}
}
2.2.6 编写中断回调函数
在 stm32f1xx_it.c
里处理中断:
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2); // 调用 HAL 库的中断处理
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 翻转 LED(PC13)
}
}
TIM2_IRQHandler()
处理 TIM2 中断。HAL_TIM_PeriodElapsedCallback()
是 HAL 库的 定时器溢出回调函数,用于执行用户代码。
2.3. 运行流程
- 系统启动时,调用
MX_TIM2_Init()
初始化 TIM2。 - 调用
HAL_TIM_Base_Start_IT()
开启定时器和中断。 - TIM2 计数到
ARR
后触发中断,进入TIM2_IRQHandler()
。 - HAL 处理中断后,调用
HAL_TIM_PeriodElapsedCallback()
,执行用户逻辑(如 LED 翻转)。
2.4. 结果
- LED(PC13)每 1s 翻转一次。
- 定时器精确控制时间间隔,可用于 任务调度、传感器读取、PWM 产生等。
总结
- 定时器核心:基于 PSC 预分频 + CNT 计数 + ARR 自动重装载 实现定时。
- HAL 代码流程:
- 初始化
TIMx
- 开启中断
HAL_TIM_Base_Start_IT()
- 处理中断
HAL_TIM_PeriodElapsedCallback()
- 初始化
- 适用场景:定时任务、PWM、信号测量、数据采集等。