抢占优先级数字越小权限越高,设错会导致中断“饿死”;子优先级仅在抢占相同时生效;优先级分组决定数值含义,必须全局一致;C++封装需防优化陷阱。

抢占优先级设错会导致中断“饿死”
抢占优先级数字越小,权限越高;设成一样就完全不能嵌套,设得太高又会卡住低优先级中断。比如把 EXTI0_IRQn 和 TIM2_IRQn 都设成抢占优先级 1,那定时器一进来就拦住所有其他中断,串口数据可能直接溢出丢帧。
常见错误是凭感觉分配:按键和串口都设 2,结果按键按下去系统没反应——其实是因为串口 ISR 正在跑,且两者抢占相同,按键只能排队等,而串口接收函数里又带了 HAL_Delay 这类阻塞调用,等于把整个中断通道堵死了。
- 紧急事件(急停、过流)必须独占最高抢占优先级,例如
0 - 实时通信(USART、SPI)设次高,如
1或2,确保收发不丢字节 - 周期性任务(SysTick、TIMx)设中等,如
3~4,允许被更高优先级打断 - 非关键后台任务(LED刷新、日志缓存)设最低,如
6~7,甚至关掉抢占位只留子优先级
子优先级只在抢占相同时起作用
子优先级不是“第二梯队”,它只在两个中断抢占优先级完全一致、又几乎同时触发时,决定谁先执行。比如两个 UART 都设抢占 2,子优先级分别为 0 和 1,那 0 的那个会先响应;但如果其中一个抢占是 1,另一个是 2,子优先级根本不会参与比较。
滥用子优先级反而增加理解成本。有人给 5 个外设全配不同子优先级但抢占全设 3,结果所有中断都排队执行,毫无嵌套能力,系统响应像灌了铅。
立即学习“C++免费学习笔记(深入)”;
- 除非你真有多个同抢占中断需要微调顺序,否则子优先级统一设
0最安全 - 子优先级位数由分组决定:
NVIC_PRIORITYGROUP_4下没有子优先级位,NVIC_PRIORITYGROUP_2下有 2 位(0~3) - 改分组必须全局一致,HAL 库初始化后调用
HAL_NVIC_SetPriorityGrouping一次就够了,重复调用会覆盖之前设置
优先级分组影响所有中断的数值含义
分组不是“可选项”,而是 NVIC 解析优先级寄存器的“解码规则”。同一行代码 HAL_NVIC_SetPriority(USART1_IRQn, 2, 0),在 NVIC_PRIORITYGROUP_4 下表示抢占=2、子=0;换到 NVIC_PRIORITYGROUP_2 下,实际变成抢占=0、子=2——数值含义全变了,极易配错。
典型误操作:移植别人代码时只抄了 SetPriority 行,漏掉前面的 SetPriorityGrouping,结果所有中断行为反常,调试半天才发现是分组默认值(通常是 _0)和原设计不匹配。
- 资源紧张的小型项目建议用
NVIC_PRIORITYGROUP_4:16 级纯抢占,逻辑最直白 - 需要精细控制同级顺序的大系统才考虑
_2或_3,但必须文档化说明每级含义 - STM32CubeMX 生成的代码默认用
_4,手写 HAL 初始化时别擅自改成别的分组
C++类封装中断优先级配置要避开编译器优化陷阱
C++ 封装 NVIC 配置时,如果把 HAL_NVIC_SetPriority 包进构造函数,又没加 volatile 或内存屏障,编译器可能把优先级写入指令优化掉或重排——尤其在 Release 模式下,看似调用了,实际寄存器没更新。
更隐蔽的问题是:C++ 对象生命周期和中断使能顺序错位。比如一个 UartDriver 对象在 main() 开头构造,但 HAL_NVIC_EnableIRQ 在之后才调,中间若有中断到来,NVIC 会忽略它,导致第一次接收永远丢失。
- 优先级配置和使能必须紧挨着写,中间不穿插其他耗时操作
- 用
static_assert校验分组与优先级数值范围是否匹配,例如抢占位数为 4 时,传入值不能超过15 - 避免在类构造/析构中做 NVIC 操作;统一放在
init()成员函数里,由用户显式调用


















