跳转到内容
新建笔记

STM32 HAL 中断链:向量入口、句柄分派与回调

STM32 HAL 中断通常经过三层:启动文件中的向量入口,驱动的 HAL_*_IRQHandler(),以及应用实现的回调。仅写一个回调函数并不会自动产生中断;外设时钟、事件使能、NVIC、向量名和句柄必须连成完整路径。

本页使用 STM32F103xB 与 STM32F1 HAL。DMA channel/stream、RTC向量、TIM实例和看门狗中断能力随系列变化,不能把不同器件的函数名拼成一张通用表。

从硬件事件走到应用代码

跳转到“从硬件事件走到应用代码”
外设产生事件并置状态位
│ 外设的该事件中断使能有效
▼
NVIC 中断请求挂起
│ NVIC已使能,且当前屏蔽/优先级允许服务
▼
启动文件向量指定的 Xxx_IRQHandler
│ 传入实际属于这个外设的 handle
▼
HAL_Xxx_IRQHandler
│ 检查状态和使能,清相应标志,更新驱动状态
▼
弱回调或已注册的回调函数
│ 保存结果/通知任务
▼
主循环或任务完成较长的业务处理

以TIM为例,当前STM32F1 HAL实现先读取DIER与SR,既检查事件标志,也检查对应中断是否使能,然后分派更新、输入捕获、输出比较等事件。回调注册开关启用时,还可能通过句柄中的函数指针调用,而不是固定调用一个全局弱函数。ST HAL TIM 实现

有些硬件事件使用同一个向量,有些外设按事件类型拆为多个向量;IRQ入口并不总与外设一一对应。读取启动文件和具体设备的 IRQn_Type,比凭名字猜更可靠。STM32F103xB设备头文件

功能向量入口举例HAL分派与应用处理
TIM2更新TIM2_IRQHandlerHAL_TIM_IRQHandler(&htim2) → PeriodElapsedCallback
TIM输入捕获相应TIM向量TIM驱动根据通道状态分派IC_CaptureCallback;应用检查Instance与活动通道
UART中断传输USART1_IRQHandler等HAL_UART_IRQHandler(&huart1) → 已启动接收/发送的完成或错误回调
GPIO EXTIEXTI0_IRQHandler、EXTI9_5_IRQHandler等对相应引脚调用HAL_GPIO_EXTI_IRQHandler(pin),再到GPIO_EXTI_Callback
DMA传输DMA1_Channel1_IRQHandler等HAL_DMA_IRQHandler(&hdma),再调用该DMA句柄的回调函数指针
ADC中断转换ADC1_2_IRQHandler对实际启用的ADC句柄分派;DMA模式的完成路径则经过DMA
RTC闹钟RTC_Alarm_IRQHandlerHAL_RTC_AlarmIRQHandler(&hrtc),处理RTC闹钟及相关EXTI标志
窗口看门狗WWDG_IRQHandler配置提前唤醒中断时,可经WWDG驱动到提前唤醒回调
独立看门狗IWDGF103没有对应普通IRQ入口超时复位;不能编造IWDG_IRQHandler实现预告
SysTickSysTick_Handler由HAL时基/RTOS集成决定;HAL提供IncTick和SysTick回调入口

F103使用DMA Channel向量,不是另一些系列的DMAx_Streamy_IRQHandler。RTC全局中断与RTC闹钟通过EXTI的中断也不是同一个入口。F103xB没有TIM6,示例应选择芯片真实存在的TIM2等实例。

DMA驱动的完成分派使用 hdma->XferCpltCallback 等函数指针。外设驱动启动DMA时,可能先把这些指针连接到内部处理函数,再由内部处理更新UART/ADC/TIM状态并调用应用回调。

因此不能假定每次DMA完成都会调用一个叫 HAL_DMA_XferCpltCallback() 的通用弱函数。独立DMA传输可按驱动API注册自己的回调;由某个外设驱动管理DMA时,则应使用该外设的公开完成/错误回调,避免覆盖其内部连接。ST HAL DMA实现

SysTick 确实有 HAL 回调入口

跳转到“SysTick 确实有 HAL 回调入口”

STM32F1 HAL提供 HAL_SYSTICK_IRQHandler(),其内部调用 HAL_SYSTICK_Callback()。HAL_IncTick()则递增HAL的时间基准,两者负责的事情不同。ST HAL Cortex实现

裸机工程若以SysTick作为HAL毫秒时基,生成代码可能在SysTick入口中调用IncTick,并按需要调用SysTick IRQ分派。RTOS工程还涉及内核tick及HAL时基选择,必须以实际生成的集成代码为准,不能另写一个同名入口把原来的调用覆盖掉。

一个完整的 TIM2 更新中断链

跳转到“一个完整的 TIM2 更新中断链”

以下模块要求应用已执行HAL初始化和时钟配置,确认TIM2内核时钟为72 MHz。它独占TIM2,按1 ms周期产生更新请求。例子使用普通裸机HAL的优先级分组;优先级数值应由工程统一安排,不是所有应用的推荐值。

#include <stdint.h>
#include "stm32f1xx_hal.h"
TIM_HandleTypeDef htim2_tick;
static volatile uint32_t tim2_events;
HAL_StatusTypeDef tim2_tick_start(void)
{
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_TIM2_FORCE_RESET();
__HAL_RCC_TIM2_RELEASE_RESET();
tim2_events = 0u;
htim2_tick.Instance = TIM2;
htim2_tick.Init.Prescaler = 71u;
htim2_tick.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2_tick.Init.Period = 999u;
htim2_tick.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2_tick.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2_tick) != HAL_OK) {
return HAL_ERROR;
}
__HAL_TIM_CLEAR_FLAG(&htim2_tick, TIM_FLAG_UPDATE);
HAL_NVIC_ClearPendingIRQ(TIM2_IRQn);
HAL_NVIC_SetPriority(TIM2_IRQn, 5u, 0u);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
HAL_StatusTypeDef result = HAL_TIM_Base_Start_IT(&htim2_tick);
if (result != HAL_OK) {
HAL_NVIC_DisableIRQ(TIM2_IRQn);
}
return result;
}
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2_tick);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *timer)
{
if (timer->Instance == TIM2) {
tim2_events++;
}
}
uint32_t tim2_take_events(void)
{
uint32_t saved_mask = __get_PRIMASK();
__disable_irq();
uint32_t result = tim2_events;
tim2_events = 0u;
__set_PRIMASK(saved_mask);
return result;
}

主循环调用tim2_take_events()取得自上次读取以来实际处理的回调次数,并在临界区外执行较长操作。短临界区保存和恢复原来的中断屏蔽状态,避免“读后清零”时与ISR增量竞争。

这个数值表示软件处理了多少次更新回调,并不能证明硬件每个周期都没有丢失。若中断被屏蔽很久,多个周期可以合并成同一个挂起状态;还应防止软件计数器长期不读取而回绕。

当前模块给出初始化、向量、回调和前台读取路径,但时钟树、启动向量表、HAL配置与主函数仍属于工程依赖。一个工程内只能有一个相同的强定义回调;如果还有其他TIM,就在同一回调中按Instance分派,不要复制出多个同名函数。

在 USE_HAL_TIM_REGISTER_CALLBACKS=0 的常见配置中,应用使用驱动声明的准确函数签名覆盖弱回调。开关启用后,可以通过注册API和句柄中的回调指针改变分派;注册允许的驱动状态也有限制,应看所用版本的说明。

IRQ入口则由启动文件向量决定。C++工程应使用生成头文件及正确的C链接声明,避免函数名修饰造成“函数存在但向量仍指向Default_Handler”。不要同时编辑驱动源码中的弱函数和另写一个应用强定义来碰运气。

回调没有执行时,按这条链检查

跳转到“回调没有执行时,按这条链检查”
  1. 外设是否真的工作:时钟、GPIO/通道、输入信号、计数器及状态位。
  2. 是否启动了对应中断模式:Base_Start不等于Base_Start_IT;PWM硬件输出正常也不代表更新中断已启用。
  3. 外设事件使能与NVIC是否都打开,当前PRIMASK/BASEPRI及优先级是否允许响应。
  4. 启动文件的准确向量名是否与实现一致;共享向量是否处理了全部已启用来源。
  5. IRQ入口是否传入正确句柄;HAL驱动检查的标志、通道和模式是否与配置一致。
  6. 回调签名、链接方式、注册开关及回调内的Instance/Channel判断是否正确。

ISR中一般只读取必要数据、更新状态或用合法的FromISR接口通知任务。长时间打印、轮询等待或调用依赖低优先级tick推进的延时函数,可能拖延其他事件甚至无法返回。ISR调用RTOS接口时还要满足内核对中断优先级的限制,不能仅因为函数名带FromISR就忽略优先级配置。

需要解释更新标志、预装载和启动顺序,见 F1定时器配置;需要评估高频回调的成本,见 H743中断预算与实测。