3、中断使能与屏蔽:全局中断使能/屏蔽、局部中断屏蔽、中断挂起与清除
好,咱们接着聊中断管理。前面讲了中断的优先级和嵌套,那怎么控制中断的开关呢?说白了,就是两件事:让不让中断进来,以及进来的中断怎么处理。
我个人习惯把中断使能与屏蔽分成三个层次:全局的、局部的、还有挂起清除。咱们一个一个说。
3.1 全局中断使能与屏蔽
全局中断使能,就是整个CPU的中断总开关。你想想看,就像家里的总电闸。拉下来,所有灯都灭;推上去,灯才能亮。
在ARM Cortex-M内核上,最常用的就是 __enable_irq() 和 __disable_irq() 这两个函数。它们操作的是 PRIMASK 寄存器。
// 全局使能中断
__enable_irq(); // 清除PRIMASK,允许所有可屏蔽中断
// 全局屏蔽中断
__disable_irq(); // 设置PRIMASK,屏蔽所有可屏蔽中断
我在项目中遇到过一个问题:有个同事在中断服务函数里调用了 __disable_irq(),结果退出中断时忘了重新使能。整个系统直接卡死,所有外设都不响应了。嗯,这坑我踩过,所以现在每次用全局屏蔽,我都会反复检查配对。
3.2 局部中断屏蔽
全局屏蔽太粗暴了。有时候我们只想保护一小段代码,不让高优先级中断打断。这时候就需要局部屏蔽。
局部屏蔽的核心思想是:保存当前中断状态 → 关中断 → 执行临界区代码 → 恢复之前的状态。
在Cortex-M上,常用的做法是操作 PRIMASK 或 BASEPRI 寄存器。
// 保存并屏蔽中断
uint32_t primask = __get_PRIMASK(); // 保存当前PRIMASK值
__disable_irq(); // 关中断
// 临界区代码
// ... 这里不允许被中断打断 ...
// 恢复中断状态
__set_PRIMASK(primask); // 恢复到之前的状态
你可能会问:为什么不直接用 __enable_irq() 恢复?因为如果进入临界区之前中断本来就是关的,你一开反而出问题了。所以保存恢复才是正道。
3.3 中断挂起与清除
中断挂起,说白了就是中断来了但没被处理,先记在「小本本」上。这个「小本本」就是挂起状态寄存器。
在Cortex-M中,每个中断都有一个挂起位。当中断条件满足但CPU正在处理更高优先级的中断时,这个挂起位就会被置1。等CPU有空了,再根据优先级去处理。
// 手动挂起一个中断(软件触发)
NVIC_SetPendingIRQ(TIMER0_IRQn);
// 清除挂起状态
NVIC_ClearPendingIRQ(TIMER0_IRQn);
// 检查是否挂起
uint32_t is_pending = NVIC_GetPendingIRQ(TIMER0_IRQn);
我曾经在调试一个TWS耳机的蓝牙连接问题时,发现中断偶尔丢失。查了半天,原来是中断服务函数里没有清除挂起位,导致同一个中断被重复触发。嗯,从那以后我写中断处理,第一件事就是清挂起。
3.4 三种机制的对比
| 机制 | 作用范围 | 典型用法 | 适用场景 |
|---|---|---|---|
| 全局使能/屏蔽 | 所有可屏蔽中断 | __enable_irq() / __disable_irq() |
系统初始化、深度睡眠前后 |
| 局部屏蔽 | 指定优先级以下的中断 | 保存/恢复PRIMASK或BASEPRI | 临界区保护、共享数据访问 |
| 挂起与清除 | 单个中断 | NVIC_SetPendingIRQ() / NVIC_ClearPendingIRQ() |
软件触发中断、中断状态管理 |
3.5 实际应用中的注意事项
在TWS耳机这种实时性要求高的嵌入式系统里,中断管理尤其重要。我总结了几条经验:
- 关中断时间要短:一般不超过几十微秒,否则音频流可能断断续续。
- 嵌套要小心:如果在中断里又调用了关中断函数,一定要用保存恢复的方式,否则嵌套层次一深就乱了。
- 挂起位要清干净:特别是边缘触发的中断,不清除挂起位可能导致中断风暴。
- 调试时多检查:我习惯在关键位置打印PRIMASK的值,看看中断状态是不是预期的。
核心要点:
- 全局屏蔽用
__disable_irq(),但别长时间关着 - 局部屏蔽一定要保存恢复,别直接开/关
- 挂起位是中断的「待办事项」,处理完记得划掉
- TWS耳机里,中断延迟超过100μs就可能出问题
好了,中断使能与屏蔽这块就聊到这儿。下一节咱们讲讲中断优先级配置,那个更讲究,特别是TWS里多个外设抢中断的时候,优先级设不好,系统就乱套了。