1. 中断基础概念:中断的定义、中断向量表、中断优先级、中断嵌套

各位同学,咱们今天聊聊中断。说实话,中断是嵌入式系统的灵魂。没有中断,你的CPU就得像个傻子一样轮询,效率低得吓人。

我刚开始做嵌入式那会儿,对中断的理解就是“一个信号来了,程序跳走”。结果呢?项目调试时系统动不动就死机,查了三天才发现是中断没处理好。从那以后,我花了大把时间把中断的每个细节啃透。

这一章,咱们把中断的四个基础概念讲清楚。你把这些搞明白了,后面优化延迟才有底气。

1.1 中断到底是什么?

中断,说白了就是CPU正在干一件事,突然被一个外部或内部事件打断,转去处理这个事件,处理完再回来接着干。

举个例子:你在写代码,电话响了。你放下笔接电话,说完挂掉,继续写。电话就是中断信号,接电话就是中断服务程序。

中断的核心价值在于——让CPU不用一直等着某个事件发生。没有中断,你就得每隔几毫秒看一眼电话有没有响,这叫轮询。轮询浪费CPU,中断则让CPU该干嘛干嘛。

中断 vs 轮询 对比

特性 中断 轮询
CPU利用率 高,无事时休眠或做其他任务 低,一直在检查状态
响应实时性 快,事件触发立即响应 慢,取决于轮询周期
实现复杂度 较高,需配置中断控制器 简单,一个循环搞定
适用场景 实时性要求高、事件稀疏 事件频繁、对延迟不敏感

我记得有个项目做电机控制,一开始用轮询检测霍尔传感器,电机转速一高就丢脉冲。换成中断后,问题立刻解决。嗯,这就是中断的威力。

1.2 中断向量表——CPU的“电话本”

中断来了,CPU怎么知道该跳到哪里去执行?靠的就是中断向量表。

中断向量表,你可以把它理解成一个电话本。每个中断源都有一个编号,这个编号对应电话本里的一个地址。CPU收到中断后,查表找到对应的服务程序入口,直接跳过去。

以ARM Cortex-M系列为例,向量表通常放在Flash的起始地址。第0个向量是栈顶指针,第1个是复位向量,后面依次是各种异常和中断的入口地址。

// 典型的ARM Cortex-M向量表定义
__attribute__((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
    (void (*)(void))(&_estack),          // 0: 栈顶指针
    Reset_Handler,                        // 1: 复位
    NMI_Handler,                          // 2: NMI
    HardFault_Handler,                    // 3: 硬错误
    MemManage_Handler,                    // 4: 内存管理
    BusFault_Handler,                     // 5: 总线错误
    UsageFault_Handler,                   // 6: 用法错误
    0, 0, 0, 0,                          // 保留
    SVC_Handler,                          // 11: SVC
    DebugMon_Handler,                     // 12: 调试监视
    0,                                    // 保留
    PendSV_Handler,                       // 14: PendSV
    SysTick_Handler,                      // 15: SysTick
    // 外部中断从16开始
    TIM2_IRQHandler,                      // 28: TIM2
    TIM3_IRQHandler,                      // 29: TIM3
    // ...
};

避坑指南:我曾经在移植代码时忘记重新映射向量表,结果中断一触发就跑飞。检查了整整一天才发现向量表地址不对。记住,如果你的程序在RAM中运行,一定要把VTOR寄存器指向RAM中的新向量表。

1.3 中断优先级——谁更紧急谁先来

多个中断同时发生怎么办?CPU得有个决策机制。这就是中断优先级。

每个中断源都有一个优先级数值。数值越小,优先级越高(也有反过来的,看具体架构)。高优先级的中断可以打断低优先级的中断服务程序。

不同架构的优先级管理方式不太一样:

架构 优先级位数 优先级分组 特点
ARM Cortex-M3/M4 8位(实际使用高4位) 可配置抢占优先级和子优先级 灵活,最多16级抢占优先级
ARM Cortex-M0/M0+ 2位 固定4级 简单,但灵活性差
RISC-V 可配置 平台相关 不同实现差异大
AVR 1位(使能/禁止) 固定优先级由向量表顺序决定 最简,无嵌套

我个人习惯在项目初期就把所有中断的优先级规划好。比如:

  • 最高优先级:系统时钟、看门狗——这些挂了系统就完了
  • 高优先级:实时控制、通信超时——对延迟敏感
  • 中等优先级:外设数据收发——可以等一等
  • 低优先级:按键、显示刷新——慢点无所谓

注意:优先级不是越高越好。你把所有中断都设成最高优先级,那跟没设一样。而且高优先级中断会阻塞低优先级中断,导致低优先级中断的响应延迟变大。我曾经见过一个项目,把定时器中断设得比串口还高,结果串口收数据时频繁被定时器打断,导致丢字节。这就是优先级分配不合理。

1.4 中断嵌套——中断里还有中断

中断嵌套,就是正在处理一个中断时,另一个更高优先级的中断来了,CPU暂停当前中断服务程序,去处理新的中断。处理完再回来继续。

你想想看,这就像你正在接一个重要电话,突然另一个更紧急的电话打进来。你会说“稍等,我接个更急的”,然后切换过去。

中断嵌套的深度取决于几个因素:

  • 优先级配置:只有更高优先级的中断才能嵌套
  • 中断使能状态:如果在ISR里关了全局中断,那谁也嵌套不了
  • 栈空间:每次嵌套都要压栈,栈不够就崩了

我建议你在设计时控制嵌套深度,一般不超过3层。太深的嵌套会导致:

  1. 栈溢出风险增加
  2. 低优先级中断的延迟变得不可预测
  3. 调试难度指数级上升

中断嵌套流程示意

  1. 主程序正在执行
  2. 低优先级中断IRQ_L到达,CPU保存现场,跳转到ISR_L
  3. ISR_L执行中,高优先级中断IRQ_H到达
  4. CPU再次保存现场(嵌套),跳转到ISR_H
  5. ISR_H执行完毕,恢复现场,返回ISR_L
  6. ISR_L继续执行,完毕,恢复现场,返回主程序

1.5 知识体系总览

下面这张图把中断的基础概念串起来了。你仔细看看,理解每个模块之间的关系。

中断基础概念知识体系 中断系统 中断定义 中断向量表 中断优先级 中断嵌套 异步事件 CPU暂停当前任务 执行ISR 恢复现场 存储ISR入口地址 中断号索引 VTOR寄存器定位 启动时初始化 数值越小优先级越高 抢占优先级 子优先级 优先级分组配置 高优先级打断低优先级 多层压栈 栈空间消耗 嵌套深度控制 理解这四个概念,是优化中断延迟的基础。后面每一章都会用到。

1.6 小结

这一章咱们把中断的四个基础概念过了一遍:

  • 中断定义:CPU被事件打断,执行完ISR再回来
  • 中断向量表:存放ISR入口地址的表格,CPU靠它找到处理函数
  • 中断优先级:决定哪个中断先被处理,数值越小优先级越高
  • 中断嵌套:高优先级中断可以打断低优先级中断的服务程序

这些概念看着简单,但实际项目中踩坑的人不少。我建议你找个开发板,动手配置一下中断,看看向量表在内存里的布局,试试不同优先级下的嵌套行为。光看书是学不会的。