中断管理基础:中断优先级、嵌套与临界区

中断管理,说白了就是让CPU知道「哪件事更急」。
我刚开始做嵌入式时,总觉得中断优先级随便设设就行。直到有一次,一个低优先级的按键中断把高优先级的数据接收给堵了——整包数据全丢。嗯,从那以后我再也不敢乱设优先级了。

中断优先级:谁先谁后?

大多数MCU支持多级中断优先级。以ARM Cortex-M系列为例,优先级数值越小,优先级越高。

优先级分组 抢占优先级位数 子优先级位数 典型应用场景
组0 0 4 所有中断同级,无嵌套
组1 1 3 简单外设,如GPIO
组2 2 2 通信协议解析常用
组3 3 1 高实时性需求
组4 4 0 纯抢占,无子优先级

我个人习惯:通信接收中断设最高抢占优先级,定时器中断次之,按键等用户交互放最低。这样能保证数据不丢。

中断嵌套:能进不能出?

中断嵌套,就是高优先级中断打断低优先级中断的服务函数。
你想想看,如果嵌套太深,栈空间会爆炸。我在项目中遇到过:一个UART接收中断里调用了printf,printf又触发了DMA中断,DMA中断里又操作了I2C……结果栈溢出,系统直接hard fault。

避坑指南:我曾经在中断嵌套里犯过一个低级错误——在低优先级中断中修改了全局状态机变量,然后被高优先级中断打断,高优先级中断也修改了同一个变量。等回到低优先级中断时,状态已经乱了。后来我加了临界区保护才解决。

临界区保护:锁住共享资源

临界区,就是一段「不允许被中断打断」的代码。通常用于保护全局变量、状态机状态、缓冲区指针等。

常用的保护手段:

  • 关中断:最简单粗暴,但关中断时间不能太长
  • 使用信号量/互斥锁:适合RTOS环境
  • 使用原子操作:Cortex-M支持LDREX/STREX指令
// 关中断保护临界区示例(Cortex-M)
uint32_t primask;
primask = __get_PRIMASK();  // 保存当前中断状态
__disable_irq();            // 关中断

// 临界区:修改状态机状态
g_state_machine.current_state = STATE_RX_DATA;
g_state_machine.rx_count = 0;

__set_PRIMASK(primask);     // 恢复中断状态
小技巧:关中断的时间尽量控制在10微秒以内。如果临界区代码太长,考虑用双缓冲或DMA来减少关中断时间。

中断上下文中安全驱动状态机

状态机在中断里跑,最怕什么?
怕状态被破坏,怕数据不一致,怕死锁。

我总结了几条铁律:

  1. 状态机变量必须用volatile修饰——编译器不会优化掉中断里的读写
  2. 状态切换必须原子操作——要么用关中断,要么用原子指令
  3. 中断里只做状态迁移,不做耗时处理——耗时操作扔到主循环或任务里
  4. 使用队列传递事件——中断只往队列里放事件,主循环消费队列驱动状态机

举个例子,我做过一个Modbus协议解析:

// 中断服务函数:只放事件
void USART1_IRQHandler(void) {
    uint8_t byte = USART1->DR;
    // 临界区保护
    uint32_t primask = __get_PRIMASK();
    __disable_irq();
    if (ring_buffer_write(&rx_buf, byte) == 0) {
        // 缓冲区满,置溢出标志
        g_overflow_flag = 1;
    }
    __set_PRIMASK(primask);
}

// 主循环:消费事件,驱动状态机
void main_loop(void) {
    uint8_t byte;
    while (1) {
        if (ring_buffer_read(&rx_buf, &byte) == 0) {
            modbus_state_machine(&byte);
        }
        // 其他任务
    }
}

这样做的好处是:中断服务函数极短,状态机在主循环里安全运行,不会出现嵌套中断破坏状态的问题。

知识结构总览

下面这张图把中断管理、临界区保护和状态机驱动的关系串起来了:

中断管理与状态机驱动关系图 UART接收中断 定时器中断 GPIO外部中断 中断优先级仲裁 抢占优先级比较 子优先级比较 中断嵌套控制 硬件自动压栈 临界区保护 关中断 原子操作 信号量/互斥锁 volatile修饰 队列缓冲 状态机驱动 关键原则: 1. 中断服务函数只做「放事件」操作,不做状态机迁移 2. 临界区保护必须覆盖所有共享变量的读写 3. 中断优先级按实时性需求分配,通信接收最高优先级 4. 关中断时间控制在10微秒以内,避免影响其他中断

实际项目中的避坑清单

我把这些年踩过的坑整理成了一张表,你写代码时对照着检查:

常见问题 现象 根因 解决方案
状态机状态被破坏 协议解析错乱,丢包 中断嵌套修改了同一状态变量 临界区保护 + volatile
栈溢出 hard fault,系统复位 中断嵌套太深,或中断里调用了printf 中断里只做轻量操作,避免函数调用
优先级反转 低优先级中断阻塞高优先级中断 优先级设置不合理 通信中断设最高优先级
关中断时间过长 其他中断响应延迟 临界区代码太长 使用队列缓冲,减少关中断时间

核心总结:中断管理不是「配好优先级就完事」的。你需要从系统层面考虑:哪些中断可以嵌套?哪些共享资源需要保护?状态机在中断里怎么安全运行?把这些想清楚,你的协议解析才能稳定可靠。