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 实际设计中的优先级分配策略
说了这么多理论,咱们来点实际的。我一般按这个思路分配优先级:
- 第一梯队(最高优先级):系统安全相关中断。比如看门狗、电源监测、硬件故障检测。这些中断必须第一时间响应,否则系统可能崩溃。
- 第二梯队:实时控制相关中断。比如电机PWM、传感器采样、通信协议帧同步。这些中断对时间敏感,延迟会导致控制失效。
- 第三梯队:数据通信中断。比如UART、SPI、I2C接收。这些中断可以容忍少量延迟,但不能丢数据。
- 第四梯队(最低优先级):人机交互中断。比如按键、触摸、LED刷新。这些中断慢一点,用户也感觉不到。
当然,这只是通用策略。具体项目要具体分析。我建议你在设计初期,先画一张中断优先级表格:
| 中断源 | 触发频率 | 实时性要求 | 建议优先级 | 备注 |
|---|---|---|---|---|
| 看门狗 | 低 | 极高 | 0(最高) | 系统崩溃前必须响应 |
| 电机PWM | 高 | 高 | 1 | 控制周期固定,不能延迟 |
| UART接收 | 中 | 中 | 2 | 缓冲区够大,可容忍少量延迟 |
| 按键扫描 | 低 | 低 | 3 | 用户按一下,延迟几十毫秒无感 |
这张表画好了,你的中断架构就清晰了一半。剩下的就是写代码验证了。
好了,这一节就到这里。下一节咱们聊聊中断嵌套的具体实现,以及怎么避免那些坑。记住,优先级设计,不是技术问题,是系统思维问题。想清楚了再动手,比写一万行代码都管用。