3. 中断嵌套场景:高优先级中断抢占低优先级
中断嵌套,说白了就是高优先级的中断把低优先级的给“踹”了。我在做电机控制项目时,就遇到过这种场景——电流环中断正在处理,位置环中断突然来了,结果电流波形直接崩了。嗯,这就是典型的嵌套没处理好。
3.1 中断嵌套的硬件机制
STM32 的 NVIC(嵌套向量中断控制器)天生支持中断嵌套。当一个高优先级中断请求到来时,如果当前正在执行的中断优先级比它低,CPU 会立即暂停当前中断服务函数,转而执行高优先级中断。等高的处理完了,再回来继续执行低的。
这里有个关键点:抢占优先级决定能否嵌套,子优先级决定同抢占级时的响应顺序。我习惯把抢占优先级理解为“谁有资格插队”,子优先级是“同级别里谁先排队”。
| 优先级类型 | 作用 | 典型配置 |
|---|---|---|
| 抢占优先级 | 决定能否打断当前中断 | 数值越小,优先级越高 |
| 子优先级 | 同抢占级时的响应顺序 | 数值越小,优先级越高 |
核心规则:只有高抢占优先级的中断才能打断低抢占优先级的中断。同抢占优先级的中断之间不会嵌套,它们会按子优先级排队执行。
3.2 中断响应延迟分析
中断响应延迟,就是从硬件拉高中断请求信号,到 CPU 开始执行中断服务函数第一条指令之间的时间。这个时间不是固定的,我把它拆成三部分:
- 硬件延迟:CPU 完成当前指令、压栈、取向量表。这个时间基本固定,大约 12 个 CPU 周期。
- 软件延迟:如果当前正在执行高优先级中断,低优先级中断就得等着。这个时间不确定。
- 总线延迟:如果总线被 DMA 或其他外设占用,取指令会变慢。
为什么会这样?因为 CPU 不是“秒切”的。我记得有一次调试串口丢数据,查了半天才发现是 DMA 占着总线,中断响应被拖慢了 3 微秒。嗯,3 微秒在低速场景不算啥,但在 1MHz 的采样率下,那就是 3 个采样点没了。
3.3 嵌套场景下的优先级配置策略
我个人习惯把中断优先级分成三个等级:
- 实时级(抢占优先级 0):定时器、PWM、电流环。这些中断必须立即响应,延迟不能超过 1 微秒。
- 普通级(抢占优先级 1):UART、SPI、I2C。可以容忍几微秒的延迟。
- 后台级(抢占优先级 2):按键扫描、LED 刷新、低优先级传感器。延迟几十微秒也没事。
我的经验:不要把所有中断都设成同一个抢占优先级。那样做虽然不会嵌套,但一旦某个中断处理时间过长,其他中断都得排队等着,实时性反而更差。
3.4 中断嵌套的避坑指南
我曾经踩过一个坑:在低优先级中断里调用了 HAL_Delay(),结果高优先级中断来了之后,HAL_Delay() 依赖的 SysTick 中断被阻塞,整个系统直接死锁。嗯,这就是典型的“中断嵌套死锁”。
避坑清单:
- 不要在中断里调用阻塞函数:比如
HAL_Delay()、printf()、malloc()。 - 中断服务函数要短:我一般控制在 50 条指令以内。超过这个数,就该考虑用信号量或消息队列把处理逻辑推到任务里。
- 注意临界区保护:如果中断和主循环共享变量,记得用
__disable_irq()和__enable_irq()包裹。 - 优先级分组要统一:整个工程只用一种优先级分组方式,别混用。
警告:中断嵌套层数不要超过 3 层。每嵌套一层,栈空间就多消耗几十个字节。我曾经见过嵌套 5 层导致栈溢出的案例,系统直接跑飞。
3.5 中断响应延迟实测方法
理论分析完了,咱们得动手测。我常用的方法是:
- 在中断服务函数入口处拉高一个 GPIO。
- 在中断服务函数出口处拉低这个 GPIO。
- 用逻辑分析仪或示波器抓这个 GPIO 的波形。
这样就能看到中断的实际响应时间和处理时间。你想想看,如果波形上出现一个“缺口”,那说明有更高优先级的中断插进来了。
// 实测中断延迟的示例代码
void TIM1_IRQHandler(void)
{
// 入口标记:拉高 GPIO
GPIOA->BSRR = GPIO_PIN_0;
// 清除中断标志
TIM1->SR = ~TIM_SR_UIF;
// 中断处理逻辑
// ...
// 出口标记:拉低 GPIO
GPIOA->BSRR = (uint32_t)GPIO_PIN_0 << 16;
}
实测数据:在 STM32F407 上,72MHz 主频,无嵌套时中断响应延迟约 167ns(12 个周期)。嵌套一层时,低优先级中断的响应延迟增加到约 500ns。如果嵌套两层,延迟可能超过 1μs。
3.6 中断嵌套场景下的知识体系
下面这张图是我自己总结的中断嵌套核心逻辑,你一看就明白了:
3.7 中断嵌套的调试技巧
调试中断嵌套问题,我一般用这三板斧:
- 看优先级配置:用调试器读出 NVIC 的优先级寄存器,确认每个中断的抢占优先级和子优先级。
- 看中断标志:如果某个中断一直不响应,检查它的 pending 位是不是被置起来了。如果是,说明有更高优先级的中断在“霸占”CPU。
- 看栈空间:中断嵌套会消耗栈空间。我习惯在调试时观察栈指针的变化,如果栈顶指针接近栈底,那就危险了。
小技巧:在中断服务函数里加一个计数器,每次进入就加 1。如果发现某个中断的计数器增长异常快,那说明它被频繁触发,或者嵌套层数太多导致处理时间变长。
嗯,中断嵌套这块内容就这些。你只要把优先级分好、中断服务函数写短、栈空间留够,基本不会出大问题。如果真出了问题,按我上面说的三板斧去查,很快就能定位到。