一、中断基础概念:什么是中断、中断向量、中断优先级、中断嵌套

大家好,我是你们的老朋友。今天咱们来聊聊中断——这个嵌入式世界里最基础、也最容易被忽视的概念。

说实话,我刚开始做嵌入式那会儿,对中断的理解就是「有个事情来了,CPU 停下手里的活去处理」。嗯,这个说法没错,但太粗糙了。你想想看,如果中断只是这么简单,那为什么还会有中断向量、优先级、嵌套这些乱七八糟的东西?

咱们一个一个来拆。

1.1 什么是中断

中断,说白了就是 CPU 在执行主程序的时候,突然被一个外部或内部的事件打断了。CPU 得先把手头的工作保存好,然后跳去处理这个突发事件,处理完了再回来接着干。

我习惯把中断比作你正在写代码,突然有人敲门——你得先保存当前编辑的文件(保存现场),然后去开门(处理中断),处理完再回来继续写(恢复现场)。

中断的来源有很多:

  • 外部中断:比如按键按下、GPIO 电平变化
  • 内部中断:比如定时器溢出、DMA 传输完成
  • 软件中断:程序自己触发的,比如系统调用
我的经验: 我在项目中遇到过最坑的一次,是 GPIO 中断没做去抖处理,结果按键按一次触发了七八次中断。从那以后,我所有外部中断都老老实实加硬件或软件去抖。

1.2 中断向量

中断向量是什么?其实就是一张表,里面存着每个中断对应的处理函数的地址。

CPU 收到中断信号后,会去查这张表,找到对应的入口地址,然后跳过去执行。这张表就叫「中断向量表」。

举个例子,ARM Cortex-M 系列的中断向量表长这样:

; 中断向量表示例(简化版)
0x00000000: 栈顶指针
0x00000004: Reset_Handler      ; 复位中断
0x00000008: NMI_Handler        ; 不可屏蔽中断
0x0000000C: HardFault_Handler  ; 硬件错误中断
0x00000010: MemManage_Handler  ; 内存管理中断
0x00000014: BusFault_Handler   ; 总线错误中断
0x00000018: UsageFault_Handler ; 用法错误中断
...
0x00000040: TIM2_IRQHandler    ; 定时器2中断
0x00000044: TIM3_IRQHandler    ; 定时器3中断

每个中断都有一个固定的向量号,CPU 根据这个号去查表。比如定时器2中断来了,CPU 就去查向量号对应的位置,拿到 TIM2_IRQHandler 的地址,然后跳过去。

重点: 中断向量表通常放在 Flash 或 RAM 的开头位置。有些芯片支持运行时重映射向量表,这个在 Bootloader 设计中非常有用。

1.3 中断优先级

如果两个中断同时来了怎么办?谁先处理?这就涉及到中断优先级了。

优先级高的中断会先被处理。优先级低的?等着吧。

不同芯片的优先级管理方式不一样:

芯片架构 优先级位数 优先级范围 特点
ARM Cortex-M0/M0+ 2位 0-3 数值越小优先级越高
ARM Cortex-M3/M4 8位(实际使用高4位) 0-15 支持抢占优先级和子优先级
RISC-V 可配置 取决于实现 通常支持抢占式优先级
x86 APIC 8位 0-255 数值越大优先级越高

这里有个容易搞混的地方:有些芯片是数值越小优先级越高(比如 ARM),有些是数值越大优先级越高(比如 x86)。我刚开始做 ARM 开发时,就因为这个搞反过,导致高优先级中断一直抢不过低优先级的,排查了半天。

避坑指南: 我曾经在 STM32 上设置优先级时,把最高优先级设成了 15,结果系统直接卡死。因为 STM32 的优先级数值越小优先级越高,15 是最低优先级。记住:ARM 是「0 最高,越大越低」。

1.4 中断嵌套

中断嵌套,就是中断里面又来了中断。

比如你在处理定时器中断的时候,突然来了一个更高优先级的串口中断。这时候 CPU 会怎么做?

答案是:如果新来的中断优先级更高,CPU 会暂停当前的中断处理,先去处理更高优先级的那个。等处理完了,再回来继续处理原来的中断。

这个过程就叫「中断嵌套」。

但要注意,不是所有中断都能嵌套。能不能嵌套取决于:

  • 优先级设置:只有更高优先级的中断才能抢占当前中断
  • 中断屏蔽位:有些中断处理函数里会关全局中断,这时候任何中断都进不来
  • 硬件设计:有些老旧的 CPU 不支持中断嵌套,处理中断时会自动关中断

我举个例子,假设有三个中断:

中断A:优先级 1(最高)
中断B:优先级 2
中断C:优先级 3(最低)

时间轴:
1. CPU 正在执行主程序
2. 中断C 来了 → CPU 去处理中断C
3. 处理中断C 的过程中,中断B 来了(优先级更高)
   → CPU 暂停中断C,保存现场,去处理中断B
4. 处理中断B 的过程中,中断A 来了(优先级最高)
   → CPU 暂停中断B,保存现场,去处理中断A
5. 中断A 处理完 → 恢复中断B 的现场,继续处理中断B
6. 中断B 处理完 → 恢复中断C 的现场,继续处理中断C
7. 中断C 处理完 → 回到主程序

这就是典型的三层嵌套。你想想看,如果嵌套层数太多,栈空间会急剧增加。我见过一个项目,中断嵌套了 5 层,结果栈溢出导致系统崩溃。嗯,从那以后我设计中断时都会严格控制嵌套深度。

我的建议: 中断处理函数里尽量少做事。把耗时操作放到任务级去处理,中断里只做最必要的操作——比如读数据、清标志、发信号。这样能减少嵌套冲突的概率。

1.5 小结

好了,咱们把中断的基础概念捋了一遍:

  • 中断:CPU 被突发事件打断,处理完再回来
  • 中断向量:一张表,存着每个中断的处理函数地址
  • 中断优先级:决定哪个中断先被处理
  • 中断嵌套:高优先级中断可以打断低优先级中断

这些概念看着简单,但实际项目中踩坑的不少。我建议你动手写个简单的中断程序,比如用定时器中断点个灯,再试试嵌套的效果。纸上得来终觉浅,绝知此事要躬行嘛。

下一章咱们聊聊 USB 的中断处理机制,那又是另一番天地了。