中断优先级基础:NVIC结构解析、优先级分组规则、抢占优先级与子优先级区别
各位同学,咱们今天聊聊中断优先级。这玩意儿看着基础,但我在项目里见过太多人在这上面翻车。说白了,中断优先级没配好,系统跑起来就跟没头苍蝇似的——该响应的不响应,不该抢的乱抢。咱们从根上把它捋清楚。
NVIC是个什么结构?
NVIC,全称Nested Vectored Interrupt Controller,嵌套向量中断控制器。名字挺长,功能其实就两件事:管中断的使能/除能,管中断的优先级裁决。STM32里NVIC最多支持240个中断,但具体芯片型号不同,数量也不一样。比如F103系列,实际可用的外部中断就60多个。
NVIC内部维护了一张中断向量表,每个中断源对应一个入口地址。当中断触发时,CPU直接从向量表里取出地址跳转执行。这个机制的好处是快,不用软件查表。我个人习惯在工程启动文件里就把向量表安排得明明白白,省得后面出问题。
核心要点:NVIC的优先级寄存器是8位宽的,但STM32只用了高4位。这意味着你最多可以配置16级优先级(0~15),数值越小优先级越高。别搞反了,我见过有人把0当成最低优先级,结果中断一直不响应。
优先级分组规则——你得先选个玩法
STM32的优先级分组,说白了就是把那4位拆成两组:一组管抢占优先级,一组管子优先级。怎么拆?由SCB->AIRCR寄存器的PRIGROUP位段决定。一共5种分组方式,我直接给你列个表:
| 分组编号 | 抢占优先级位数 | 子优先级位数 | 抢占优先级等级数 | 子优先级等级数 |
|---|---|---|---|---|
| NVIC_PriorityGroup_0 | 0 | 4 | 1 | 16 |
| NVIC_PriorityGroup_1 | 1 | 3 | 2 | 8 |
| NVIC_PriorityGroup_2 | 2 | 2 | 4 | 4 |
| NVIC_PriorityGroup_3 | 3 | 1 | 8 | 2 |
| NVIC_PriorityGroup_4 | 4 | 0 | 16 | 1 |
你想想看,这个分组必须在系统启动时就设好,而且整个运行期间不能改。为什么?因为一旦改了,所有中断的优先级解释方式就全乱了。我曾经在一个项目里,同事在main函数中途调了NVIC_PriorityGroupConfig,结果所有中断优先级都错位了,调试了整整两天才发现。
避坑指南:整个系统只调用一次NVIC_PriorityGroupConfig(),放在系统初始化最前面。别在任务里、中断里、或者任何地方再调第二次。我吃过这个亏,现在每次写代码都先检查这个。
抢占优先级 vs 子优先级——到底有啥区别?
这两个概念,很多初学者容易搞混。我换个说法:
- 抢占优先级:决定一个中断能不能打断另一个正在执行的中断。数值越小,越能抢。
- 子优先级:当两个中断的抢占优先级相同时,谁先执行。数值越小,越先执行。但子优先级不能产生嵌套。
举个例子:假设你设了分组2(2位抢占,2位子优先级)。
- 中断A:抢占=1,子优先级=0
- 中断B:抢占=0,子优先级=3
- 中断C:抢占=1,子优先级=2
执行顺序是这样的:B的抢占优先级最低(0),所以它优先级最高,能打断A和C。A和C的抢占优先级相同(都是1),这时候看子优先级,A是0,C是2,所以A先执行。但A和C之间不能互相嵌套,因为抢占优先级一样。
我的经验:大多数项目用分组2(4级抢占,4级子优先级)就够了。抢占优先级用来区分实时性要求高的中断(比如定时器、通信),子优先级用来处理同一类中断的内部顺序。别搞太复杂,否则调试的时候你自己都记不住。
优先级配置的代码示例
咱们直接看代码,怎么配一个USART1中断,抢占优先级1,子优先级2:
// 第一步:设置分组,只调用一次
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 第二步:配置具体中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
嗯,这里要注意:NVIC_IRQChannelPreemptionPriority和NVIC_IRQChannelSubPriority的取值范围,取决于你选的分组。分组2的话,每个都是0~3。你填个5进去,NVIC_Init函数不会报错,但实际效果就乱了。我建议在代码里加个断言检查。
优先级分组与中断嵌套的关系
咱们用一张图把整个逻辑串起来:
这张图把整个决策流程画清楚了。你从触发开始,先看分组配置,然后判断抢占优先级。如果能抢,直接嵌套执行;如果不能抢,再看子优先级排队。记住这个流程,中断优先级这块你就通了。
实际项目中的优先级分配策略
我一般按这个原则分配:
- 最高优先级(抢占0):留给系统滴答定时器、紧急故障处理。这些中断不能被打断。
- 高优先级(抢占1):留给实时通信,比如SPI、I2C、USART的接收中断。数据不能丢。
- 中优先级(抢占2):留给定时器、PWM、ADC等周期性任务。可以容忍少量延迟。
- 低优先级(抢占3):留给按键扫描、LED刷新、非关键外设。这些慢一点没关系。
小技巧:调试阶段把所有中断优先级先设成一样,确保系统能跑起来。然后再逐个调整优先级,观察行为变化。这样定位问题快得多。我每次接手别人的代码,第一件事就是检查优先级分组和每个中断的优先级数值,往往能发现一堆问题。