中断管理基础:中断优先级、嵌套与临界区
中断管理,说白了就是让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来减少关中断时间。
中断上下文中安全驱动状态机
状态机在中断里跑,最怕什么?
怕状态被破坏,怕数据不一致,怕死锁。
我总结了几条铁律:
- 状态机变量必须用volatile修饰——编译器不会优化掉中断里的读写
- 状态切换必须原子操作——要么用关中断,要么用原子指令
- 中断里只做状态迁移,不做耗时处理——耗时操作扔到主循环或任务里
- 使用队列传递事件——中断只往队列里放事件,主循环消费队列驱动状态机
举个例子,我做过一个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);
}
// 其他任务
}
}
这样做的好处是:中断服务函数极短,状态机在主循环里安全运行,不会出现嵌套中断破坏状态的问题。
知识结构总览
下面这张图把中断管理、临界区保护和状态机驱动的关系串起来了:
实际项目中的避坑清单
我把这些年踩过的坑整理成了一张表,你写代码时对照着检查:
| 常见问题 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 状态机状态被破坏 | 协议解析错乱,丢包 | 中断嵌套修改了同一状态变量 | 临界区保护 + volatile |
| 栈溢出 | hard fault,系统复位 | 中断嵌套太深,或中断里调用了printf | 中断里只做轻量操作,避免函数调用 |
| 优先级反转 | 低优先级中断阻塞高优先级中断 | 优先级设置不合理 | 通信中断设最高优先级 |
| 关中断时间过长 | 其他中断响应延迟 | 临界区代码太长 | 使用队列缓冲,减少关中断时间 |
核心总结:中断管理不是「配好优先级就完事」的。你需要从系统层面考虑:哪些中断可以嵌套?哪些共享资源需要保护?状态机在中断里怎么安全运行?把这些想清楚,你的协议解析才能稳定可靠。