STM32定时器中断

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. 运行流程总结

  1. 配置时钟源(内部/外部)。
  2. 配置时基单元(设置 PSCARR)。
  3. 使能定时器中断(NVIC 配置)。
  4. 定时器计数(CNT 从 0 计数到 ARR)。
  5. 触发中断,执行中断服务程序(ISR)。
  6. 清除中断标志,等待下一次中断触发。

1.6. 实际应用

  • 周期性任务(如 1s 触发一次)。
  • PWM 产生(控制 LED 亮度、电机转速)。
  • 输入捕获(测量脉冲信号)。
  • 编码器模式(测量电机位置)。

当然,结合 HAL 库(STM32Cube HAL 库)的使用,我们可以更直观地实现定时器中断。


2. HAL 库定时器中断操作

HAL 库封装了底层寄存器操作,使得 STM32 定时器的配置更加简单。基本流程如下:

2.1. 主要步骤

  1. 初始化定时器(设定时基、预分频等)。
  2. 开启定时器中断
  3. 编写中断回调函数
  4. 清除中断标志,处理中断任务
HAL库的中断服务函数会自动清除中断标志,使用寄存器才需要手动清除。

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. 运行流程

  1. 系统启动时,调用 MX_TIM2_Init() 初始化 TIM2
  2. 调用 HAL_TIM_Base_Start_IT() 开启定时器和中断
  3. TIM2 计数到 ARR 后触发中断,进入 TIM2_IRQHandler()
  4. HAL 处理中断后,调用 HAL_TIM_PeriodElapsedCallback(),执行用户逻辑(如 LED 翻转)

2.4. 结果

  • LED(PC13)每 1s 翻转一次。
  • 定时器精确控制时间间隔,可用于 任务调度、传感器读取、PWM 产生等

总结

  • 定时器核心:基于 PSC 预分频 + CNT 计数 + ARR 自动重装载 实现定时。
  • HAL 代码流程
    1. 初始化 TIMx
    2. 开启中断 HAL_TIM_Base_Start_IT()
    3. 处理中断 HAL_TIM_PeriodElapsedCallback()
  • 适用场景:定时任务、PWM、信号测量、数据采集等。

上一篇
下一篇