4、中断优先级配置:抢占优先级与子优先级、优先级分组、优先级配置实战
好,咱们接着聊中断。上一节我们把中断的开关、挂起、清除这些基本操作捋了一遍。这一节,咱们要啃一块硬骨头——中断优先级配置。
说实话,我刚入行那会儿,觉得中断优先级不就是“谁重要谁先响应”嘛,有啥好讲的?结果第一次做多中断嵌套的项目,就踩了坑。两个中断同时触发,系统直接跑飞了。后来查了半天,才发现是优先级分组没配对。
嗯,咱们今天就把这块彻底讲透。
4.1 抢占优先级 vs 子优先级
先搞清楚两个概念:抢占优先级和子优先级。
- 抢占优先级:决定一个中断能不能打断另一个正在执行的中断。数值越小,优先级越高。
- 子优先级:当两个中断的抢占优先级相同时,谁先执行。数值越小,优先级越高。
说白了,抢占优先级管的是“能不能插队”,子优先级管的是“排队时谁站前面”。
核心规则:
- 高抢占优先级的中断,可以打断低抢占优先级的中断。
- 抢占优先级相同的中断,不会互相打断。谁先来谁先执行,如果同时来,看子优先级。
- 子优先级只在抢占优先级相同时才起作用。
我在项目中遇到过一种情况:两个外设的中断,一个要实时响应(比如定时器),一个数据量大但可以等(比如UART接收)。这时候我就把定时器的抢占优先级设高,UART的设低。这样定时器来了就能打断UART,保证实时性。
4.2 优先级分组
那问题来了:一个32位的中断优先级寄存器,到底用多少位来表示抢占优先级,多少位表示子优先级?
这就是优先级分组要干的事。
以Cortex-M内核为例,它提供了8种分组方式(0~7)。但常用的其实就几种。我一般用分组2或分组3,因为这样分配比较均衡。
| 分组 | 抢占优先级位数 | 子优先级位数 | 抢占优先级取值范围 | 子优先级取值范围 |
|---|---|---|---|---|
| 0 | 0 | 4 | 无 | 0~15 |
| 1 | 1 | 3 | 0~1 | 0~7 |
| 2 | 2 | 2 | 0~3 | 0~3 |
| 3 | 3 | 1 | 0~7 | 0~1 |
| 4 | 4 | 0 | 0~15 | 无 |
我的建议:如果你不确定用哪个分组,先用分组2。4级抢占、4级子优先级,大部分场景都够用。我自己的项目里,80%的情况都用分组2。
4.3 优先级配置实战
光说不练假把式。咱们直接上代码。
以STM32为例,配置中断优先级分三步走:
- 设置优先级分组——系统启动时做一次就行。
- 配置每个中断的抢占优先级和子优先级。
- 使能中断。
4.3.1 设置优先级分组
// 使用分组2:4位抢占优先级,4位子优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
这行代码一般在main函数开头调用。注意,整个系统只能调用一次。如果你中途改了分组,之前配好的优先级全乱套了。
警告:我曾经在一个项目里,因为调试需要,在初始化之后又调了一次PriorityGroupConfig。结果所有中断优先级都变了,系统直接死机。后来花了半天才找到原因。所以记住:分组只配一次,配完就别动了。
4.3.2 配置具体中断的优先级
假设我们有两个中断:定时器中断和UART中断。
// 定时器中断:抢占优先级0,子优先级0(最高优先级)
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// UART中断:抢占优先级1,子优先级0
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
这样配置之后,定时器中断可以打断UART中断。如果两个中断同时来,定时器先执行。
4.3.3 验证配置
配完了怎么验证?我一般用两种方法:
- 读寄存器:直接读NVIC的优先级寄存器,看看值对不对。
- 实际测试:在两个中断服务函数里加GPIO翻转,用示波器看波形。高优先级的中断来了,低优先级的波形是不是被“打断”了。
小技巧:调试的时候,可以在中断服务函数里加个计数器。每次进入中断就加1。如果高优先级中断频繁触发,低优先级中断的计数器增长就会变慢。这能直观地看到优先级的效果。
4.4 优先级分组与中断嵌套的关系
这里有个容易搞混的点:中断嵌套。
只有抢占优先级不同的中断才能嵌套。抢占优先级相同的中断,即使子优先级不同,也不会嵌套。它们会排队,一个一个执行。
举个例子:
- 中断A:抢占0,子优先级0
- 中断B:抢占0,子优先级1
- 中断C:抢占1,子优先级0
如果A正在执行,C来了,C会打断A(因为C的抢占优先级更高)。但如果B来了,B不会打断A(因为抢占优先级相同),B会等A执行完再执行。
嗯,这个逻辑一定要想清楚。我在项目中见过有人把子优先级当成抢占优先级用,结果该打断的没打断,不该打断的反而打断了。
4.5 常见问题与避坑指南
我曾经踩过的坑:
- 坑1:分组没配就配中断优先级。结果优先级值被解释成别的含义,中断行为完全不可控。
- 坑2:所有中断都用同一个抢占优先级。这样就没有嵌套了,高实时性的中断会被堵住。
- 坑3:抢占优先级数值设反了。记住,数值越小优先级越高。0是最高,不是最低。
- 坑4:中断服务函数里做太多事。优先级再高,也架不住你在中断里跑个几百毫秒的循环。高优先级中断的服务函数一定要短小精悍。
好了,中断优先级这块就讲到这里。配置其实不复杂,关键是要理解抢占和子优先级的区别,以及分组的作用。配的时候细心点,测试的时候多验证,基本不会出问题。
可可精品资料库