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设备头文件
F103 上常见的几条路径
跳转到“F103 上常见的几条路径”| 功能 | 向量入口举例 | HAL分派与应用处理 |
|---|---|---|
| TIM2更新 | TIM2_IRQHandler | HAL_TIM_IRQHandler(&htim2) → PeriodElapsedCallback |
| TIM输入捕获 | 相应TIM向量 | TIM驱动根据通道状态分派IC_CaptureCallback;应用检查Instance与活动通道 |
| UART中断传输 | USART1_IRQHandler等 | HAL_UART_IRQHandler(&huart1) → 已启动接收/发送的完成或错误回调 |
| GPIO EXTI | EXTI0_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_IRQHandler | HAL_RTC_AlarmIRQHandler(&hrtc),处理RTC闹钟及相关EXTI标志 |
| 窗口看门狗 | WWDG_IRQHandler | 配置提前唤醒中断时,可经WWDG驱动到提前唤醒回调 |
| 独立看门狗IWDG | F103没有对应普通IRQ入口 | 超时复位;不能编造IWDG_IRQHandler实现预告 |
| SysTick | SysTick_Handler | 由HAL时基/RTOS集成决定;HAL提供IncTick和SysTick回调入口 |
F103使用DMA Channel向量,不是另一些系列的DMAx_Streamy_IRQHandler。RTC全局中断与RTC闹钟通过EXTI的中断也不是同一个入口。F103xB没有TIM6,示例应选择芯片真实存在的TIM2等实例。
DMA 为什么是一个例外
跳转到“DMA 为什么是一个例外”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”。不要同时编辑驱动源码中的弱函数和另写一个应用强定义来碰运气。
回调没有执行时,按这条链检查
跳转到“回调没有执行时,按这条链检查”- 外设是否真的工作:时钟、GPIO/通道、输入信号、计数器及状态位。
- 是否启动了对应中断模式:Base_Start不等于Base_Start_IT;PWM硬件输出正常也不代表更新中断已启用。
- 外设事件使能与NVIC是否都打开,当前PRIMASK/BASEPRI及优先级是否允许响应。
- 启动文件的准确向量名是否与实现一致;共享向量是否处理了全部已启用来源。
- IRQ入口是否传入正确句柄;HAL驱动检查的标志、通道和模式是否与配置一致。
- 回调签名、链接方式、注册开关及回调内的Instance/Channel判断是否正确。
ISR中一般只读取必要数据、更新状态或用合法的FromISR接口通知任务。长时间打印、轮询等待或调用依赖低优先级tick推进的延时函数,可能拖延其他事件甚至无法返回。ISR调用RTOS接口时还要满足内核对中断优先级的限制,不能仅因为函数名带FromISR就忽略优先级配置。
需要解释更新标志、预装载和启动顺序,见 F1定时器配置;需要评估高频回调的成本,见 H743中断预算与实测。