3. 中断优先级基础:优先级的概念、固定优先级与可编程优先级、优先级对系统实时性的影响

各位同学,咱们今天聊聊中断优先级。这玩意儿,说白了就是给中断排个队——谁更重要,谁先处理。

我记得刚入行那会儿,做一个小家电的控制板。按键中断和定时器中断撞车了,结果按键响应总是慢半拍。客户按下去,灯要等一会儿才亮。后来一查,就是优先级没设对。嗯,从那以后,我对优先级这事儿就特别上心。

3.1 优先级到底是什么?

中断优先级,就是系统给每个中断源分配的一个“紧急程度”标签。

你想想看,CPU同一时间只能干一件事。如果两个中断同时来了,CPU该听谁的?这时候优先级就派上用场了。优先级高的先执行,优先级低的靠边站。

举个例子:

  • 电源掉电检测中断 → 优先级最高(不然数据就丢了)
  • 电机过流保护中断 → 优先级次之(烧了电机可不好玩)
  • 串口接收中断 → 优先级再次(丢几个字节还能重发)
  • 按键扫描中断 → 优先级最低(按慢一点也没事)

这个排序,其实就是优先级设计的基本思路。我在项目中见过有人把所有中断设成同一优先级,结果系统一忙就乱套。所以,优先级不是摆设,是系统稳定性的基石。

3.2 固定优先级 vs 可编程优先级

这里有个重要的分水岭——固定优先级和可编程优先级。

固定优先级

固定优先级,就是芯片出厂时,优先级就焊死了。你改不了。

比如早期的8051单片机,中断优先级是硬件固定的:

  • 外部中断0(INT0)→ 最高
  • 定时器0(T0)→ 第二
  • 外部中断1(INT1)→ 第三
  • 定时器1(T1)→ 第四
  • 串口中断 → 最低

这种设计简单粗暴。但问题也很明显——如果你的应用里,串口数据比定时器更重要,那你就只能干瞪眼。我曾经在一个老项目里就吃过这个亏,串口接收老是丢包,后来发现是定时器中断太频繁,把串口挤得没时间处理。

注意:固定优先级芯片,中断号越小,优先级越高。这是硬件决定的,别想着绕过去。

可编程优先级

可编程优先级,就是你可以通过寄存器,自己给中断排座次。

现在的ARM Cortex-M系列、STM32、ESP32,基本都是可编程的。你可以这样写:

// STM32 设置中断优先级示例
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);   // 串口1,抢占优先级0,最高
HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);     // 定时器2,抢占优先级1
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);    // 外部中断0,抢占优先级2

你看,串口优先级最高,定时器次之,外部中断最低。这样设计,串口数据就不会被其他中断耽误了。

我个人习惯,把实时性要求高的中断(比如电机控制、通信协议)放在高优先级,把非关键的中断(比如按键、LED闪烁)放在低优先级。这样系统响应快,又不会因为低优先级中断卡死高优先级任务。

3.3 优先级对系统实时性的影响

实时性,说白了就是“事情发生时,系统能不能及时响应”。优先级直接影响这个。

咱们分几种情况来看:

情况一:高优先级中断频繁触发

如果高优先级中断来得太频繁,低优先级中断可能永远得不到执行。这叫“中断饿死”。

我曾经在一个电机驱动项目里,把PWM中断设成了最高优先级。结果PWM每50微秒触发一次,串口中断根本插不进去。上位机发来的指令,电机要等好几秒才响应。后来我把PWM中断的优先级降了一级,串口中断提上来,问题就解决了。

核心原则:高优先级中断的执行时间要尽量短。如果高优先级中断里跑了个大循环,那整个系统就废了。

情况二:中断嵌套

当CPU正在处理一个低优先级中断时,来了一个高优先级中断。CPU会暂停当前中断,去处理高优先级中断。处理完再回来继续。

这就是中断嵌套。它提高了实时性,但也带来了风险:

  • 嵌套层数太多,栈空间可能溢出
  • 共享资源(比如全局变量)可能被破坏
  • 中断响应时间变长(因为要保存更多上下文)

我建议,中断嵌套尽量控制在2层以内。超过3层,出bug的概率就指数级上升。

情况三:优先级反转

这是个经典问题。低优先级中断占用了某个资源,高优先级中断想用这个资源,但被低优先级中断卡住了。而低优先级中断又被其他中断打断了,导致高优先级中断迟迟拿不到资源。

嗯,听起来有点绕。我举个实际例子:

  • 中断A(高优先级)想访问共享缓冲区
  • 中断B(低优先级)正在访问这个缓冲区,还没释放
  • 中断C(中优先级)突然来了,打断了中断B
  • 结果:中断A等中断B,中断B等中断C,中断C跑完了,中断B才能继续,中断A才能拿到资源

你看,最高优先级的中断,反而被最低优先级的中断拖累了。这就是优先级反转。

避坑指南:我曾经在一个多中断系统里,就是因为没处理好优先级反转,导致系统偶尔死机。后来我用了“优先级继承”的方法——当低优先级中断持有高优先级需要的资源时,临时提升它的优先级。这样就能避免反转。

3.4 实际设计中的优先级分配策略

说了这么多理论,咱们来点实际的。我一般按这个思路分配优先级:

  1. 第一梯队(最高优先级):系统安全相关中断。比如看门狗、电源监测、硬件故障检测。这些中断必须第一时间响应,否则系统可能崩溃。
  2. 第二梯队:实时控制相关中断。比如电机PWM、传感器采样、通信协议帧同步。这些中断对时间敏感,延迟会导致控制失效。
  3. 第三梯队:数据通信中断。比如UART、SPI、I2C接收。这些中断可以容忍少量延迟,但不能丢数据。
  4. 第四梯队(最低优先级):人机交互中断。比如按键、触摸、LED刷新。这些中断慢一点,用户也感觉不到。

当然,这只是通用策略。具体项目要具体分析。我建议你在设计初期,先画一张中断优先级表格:

中断源 触发频率 实时性要求 建议优先级 备注
看门狗 极高 0(最高) 系统崩溃前必须响应
电机PWM 1 控制周期固定,不能延迟
UART接收 2 缓冲区够大,可容忍少量延迟
按键扫描 3 用户按一下,延迟几十毫秒无感

这张表画好了,你的中断架构就清晰了一半。剩下的就是写代码验证了。

最后提醒一句:优先级不是越高越好。高优先级中断太多,系统会变得“神经质”——频繁切换上下文,CPU大部分时间都在做中断切换,反而干不了正事。我见过一个项目,把所有中断都设成最高优先级,结果CPU利用率不到30%,系统却卡得要死。这就是典型的“优先级滥用”。

好了,这一节就到这里。下一节咱们聊聊中断嵌套的具体实现,以及怎么避免那些坑。记住,优先级设计,不是技术问题,是系统思维问题。想清楚了再动手,比写一万行代码都管用。