4、中断优先级配置:抢占优先级与子优先级、优先级分组配置、中断优先级设置函数
好,咱们接着聊中断。上一节我们把中断的开关和挂起讲清楚了,这一节要深入一个更关键的问题——优先级。
你想想看,一个系统里可能同时有十几个中断源。按键按一下、定时器溢出了、蓝牙数据包到了、I2C传输完成了……如果它们同时发生,CPU该先处理谁?
这就涉及到中断优先级的配置。说白了,就是给每个中断排个队,谁更重要谁先上。
4.1 抢占优先级 vs 子优先级
ARM Cortex-M内核(TWS耳机里常用的M0/M4/M33都支持)把优先级分成了两段:抢占优先级和子优先级。
- 抢占优先级:决定一个中断能否打断另一个正在执行的中断。数值越小,优先级越高。
- 子优先级:当两个中断的抢占优先级相同时,子优先级决定谁先执行。但注意,子优先级不能产生抢占。
我打个比方你就明白了。抢占优先级就像公司的部门经理——经理A在开会,经理B来了,如果B的级别更高,可以直接推门进去打断会议。子优先级呢,就像部门里的普通员工——两个员工都想用打印机,谁的工号小谁先用,但谁也不能打断对方正在打印的任务。
核心要点:抢占优先级决定“能不能打断”,子优先级决定“谁先排队”。
我在一个TWS项目里就遇到过这样的坑:音频中断和蓝牙中断同时触发,音频的抢占优先级设得比蓝牙低,结果蓝牙数据包处理到一半,音频流直接卡顿。后来我把音频的抢占优先级调高了一级,问题就解决了。嗯,优先级这个东西,调对了是艺术,调错了是灾难。
4.2 优先级分组配置
那么问题来了:一个32位的优先级寄存器,到底用多少位来表示抢占优先级,多少位来表示子优先级?
这就靠优先级分组来决定了。Cortex-M内核提供了NVIC_SetPriorityGrouping()这个函数,用来配置分组方式。
分组方式一共有5种,我用表格列出来,你一看就明白:
| 分组编号 | 抢占优先级位数 | 子优先级位数 | 抢占优先级等级数 | 子优先级等级数 |
|---|---|---|---|---|
| 0 | 0 | 4 | 1 | 16 |
| 1 | 1 | 3 | 2 | 8 |
| 2 | 2 | 2 | 4 | 4 |
| 3 | 3 | 1 | 8 | 2 |
| 4 | 4 | 0 | 16 | 1 |
我个人习惯用分组2(2位抢占 + 2位子优先级)。为什么?因为TWS耳机里中断源通常不超过10个,4级抢占优先级完全够用,4级子优先级也能满足内部排序需求。分组太细反而容易搞混。
配置代码很简单,一般在系统初始化时调用一次:
// 选择分组2:2位抢占优先级,2位子优先级
NVIC_SetPriorityGrouping(2);
注意:优先级分组必须在系统启动时设置,而且只能设置一次。中途改分组,所有中断的优先级含义都会变,后果很严重。我曾经在调试时手贱改了一次,结果系统直接跑飞……
4.3 中断优先级设置函数
分组配好了,接下来就是给每个中断设置具体的优先级数值。用的函数是NVIC_SetPriority()。
这个函数的原型是:
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);
第一个参数是中断号,第二个参数是优先级数值。注意,这个数值是8位有效的,但实际只用到了高4位(取决于分组配置)。
举个例子,假设我们用了分组2(2位抢占 + 2位子优先级),那么priority寄存器的高4位中,高2位是抢占优先级,低2位是子优先级。
看代码:
// 设置UART中断:抢占优先级1,子优先级2
// 数值 = (抢占优先级 << 子优先级位数) | 子优先级
// 这里子优先级位数 = 2
NVIC_SetPriority(UART_IRQn, (1 << 2) | 2); // 实际数值 = 0b0110
// 设置定时器中断:抢占优先级0,子优先级3
NVIC_SetPriority(TIMER_IRQn, (0 << 2) | 3); // 实际数值 = 0b0011
你可能会问:为什么数值越小优先级越高?这是ARM的设计哲学——数值越小,越“紧急”。就像医院急诊的分诊,1级是危重病人,5级是轻症。习惯就好。
小技巧:我建议你写一个宏或者内联函数,把优先级计算封装起来,这样代码可读性会好很多:
#define PRIORITY(preempt, sub) (((preempt) << 2) | (sub))
NVIC_SetPriority(UART_IRQn, PRIORITY(1, 2));
4.4 实际项目中的配置建议
好了,理论讲完了,咱们聊聊实战。在TWS耳机这种实时性要求高的嵌入式系统里,我一般这样配优先级:
- 最高优先级(抢占0):留给系统滴答定时器Systick和硬件错误HardFault。这些是系统运行的基石,绝对不能被打断。
- 高优先级(抢占1):留给音频I2S中断和蓝牙链路层中断。音频流不能断,蓝牙连接不能丢。
- 中优先级(抢占2):留给定时器PWM、按键扫描、传感器数据采集。这些有实时性要求,但可以容忍短暂延迟。
- 低优先级(抢占3):留给串口打印、I2C/SPI通信、电量检测。这些慢速外设,晚几十微秒处理完全没问题。
我曾经在一个项目里把蓝牙中断的优先级设得太低,结果耳机在播放音乐时频繁断连。后来查了半天才发现,是音频中断把蓝牙中断堵死了。调整优先级后,问题迎刃而解。
所以我的建议是:先画一张中断优先级分配表,把每个中断源、它的抢占优先级和子优先级都列清楚。别偷懒,这张表能帮你省下无数调试时间。
嗯,这一节的内容就到这儿。下一节我们聊聊中断嵌套和优先级反转——这两个概念在TWS这种多中断场景下特别重要。到时候我会分享一个我踩过的坑,保证让你印象深刻。