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

大家好,我是老张。做嵌入式这行快十五年了,今天咱们聊聊中断。说实话,中断是嵌入式系统里最核心的机制之一。你想想看,没有中断的MCU就像个只会死循环的傻瓜——CPU得不停地轮询检查每个外设,效率低得吓人。

1.1 中断到底是什么?

中断,说白了就是CPU正在干一件事,突然被一个外部或内部事件打断,它得先把手头的活儿暂停,去处理这个突发事件,处理完再回来接着干。嗯,这就像你正在写代码,同事突然拍你肩膀问问题——你记下当前思路,转头回答他,答完再继续写。

我个人习惯把中断比作「硬件级别的紧急电话」。CPU平时在主循环里悠哉游哉,一旦中断信号来了,它必须立刻响应。我在项目中遇到过最典型的例子:一个电机控制项目,如果不靠中断,光靠轮询读取编码器脉冲,CPU根本来不及响应高速旋转时的位置变化,电机直接失步。

中断的核心三要素:

  • 触发条件:某个硬件事件发生(比如引脚电平变化、定时器溢出)
  • 响应动作:CPU暂停当前任务,跳转到中断服务函数
  • 恢复现场:处理完后回到断点继续执行

1.2 中断源——谁在敲门?

中断源就是能产生中断请求的硬件单元。不同的MCU,中断源数量差别很大。拿我常用的STM32F103来说,它有68个可屏蔽中断源,而一些低端8位机可能只有十几个。

常见的几类中断源:

  • 外部中断:GPIO引脚电平变化、按键按下、传感器信号跳变
  • 定时器中断:定时器溢出、捕获/比较事件
  • 通信中断:UART接收完成、SPI传输结束、I2C地址匹配
  • DMA中断:DMA传输完成或半完成
  • 软件中断:由软件指令触发(比如ARM的SWI指令)

我曾经踩过一个坑:在一个多传感器采集项目中,同时用了三个外部中断引脚。结果发现某个传感器频繁触发中断,导致其他两个中断响应延迟严重。后来查手册才发现,这几个外部中断共享同一个优先级组,优先级没配好。嗯,这里要注意——中断源多了,优先级管理就是门学问。

1.3 中断向量表——CPU的紧急通讯录

中断向量表,你可以把它理解成一张「中断服务函数地址表」。CPU收到中断信号后,会根据中断号去这张表里查,找到对应的函数入口地址,然后跳过去执行。

这张表通常放在Flash或RAM的起始地址。以ARM Cortex-M3为例,向量表从0x00000000开始,前16个是系统异常,后面才是外设中断。

// 典型的STM32中断向量表(部分)
__attribute__((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
    &__initial_sp,          // 0x0000: 栈顶指针
    Reset_Handler,          // 0x0004: 复位中断
    NMI_Handler,            // 0x0008: 不可屏蔽中断
    HardFault_Handler,      // 0x000C: 硬件错误中断
    // ... 省略系统异常
    TIM2_IRQHandler,        // 定时器2中断
    TIM3_IRQHandler,        // 定时器3中断
    USART1_IRQHandler,      // 串口1中断
    // ...
};

我个人习惯在项目初期就把所有用到的中断向量确认一遍。为什么?因为有一次我写代码时把USART1的中断函数名写成了USART1_IRQHandler(少了个下划线),结果编译通过但中断死活不触发。查了两天才发现是向量表里函数名和实际定义不匹配——这种低级错误,犯过一次就长记性了。

小技巧:很多MCU支持向量表重定位。比如在Bootloader中,你可以把向量表搬到RAM里,运行时动态修改中断入口。这在做OTA升级时特别有用。

1.4 中断优先级——谁更紧急?

多个中断同时发生时,谁先处理?这就靠优先级来决定了。中断优先级机制,说白了就是给每个中断源排个队,紧急的先处理。

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

架构 优先级层级 特点
ARM Cortex-M3/M4 8~256级(可配置) 支持抢占优先级和子优先级
AVR 8位 2级(高/低) 简单粗暴,高优先级可打断低优先级
PIC 16位 7级 固定优先级,不可配置

以Cortex-M为例,优先级数值越小,优先级越高。比如优先级0比优先级1更紧急。我建议你在设计系统时,把时间敏感的中断(比如电机控制中的PWM周期中断)设为最高优先级,把不那么紧急的(比如按键扫描)设为低优先级。

避坑指南:我曾经在一个项目中,把两个中断都设成了最高优先级。结果高优先级中断A正在执行时,同优先级的中断B来了——按照Cortex-M的规则,同优先级中断不能互相打断。这就导致B必须等A执行完才能响应,如果A执行时间过长,B的实时性就完蛋了。所以,不要把多个中断设成同一个最高优先级,除非你确定它们不会同时触发。

1.5 中断响应流程——CPU到底干了啥?

当CPU收到中断请求后,它内部会走一套固定的流程。我画了个图帮你理解:

中断响应流程图 1. 中断请求到来 2. CPU保存现场(压栈) 3. 查向量表,跳转ISR 4. 执行中断服务函数 关键点 • 压栈:保存R0-R3, R12, LR, PC, xPSR • 向量表地址由VTOR寄存器决定 • ISR执行完后执行BX LR返回 • 出栈恢复现场 • 继续执行主程序 注意: ISR要尽量短! 不要在ISR里做 延时或复杂运算 返回主程序

这个流程里,我最想强调的就是「ISR要尽量短」。为什么?因为中断服务函数执行期间,其他同优先级或更低优先级的中断都被屏蔽了。如果你在ISR里搞个10ms的延时,整个系统的实时性就崩了。我一般只在ISR里设置标志位或传递数据,真正的处理逻辑放到主循环里做。

我的习惯:中断服务函数里只做三件事——读数据、清标志、设变量。其他所有复杂计算、协议解析、状态机切换,统统放到主循环或任务调度里。这样既保证了中断的快速响应,又避免了在ISR里出bug。

1.6 中断嵌套——中断里还能有中断?

是的,高优先级的中断可以打断正在执行的低优先级中断。这就是中断嵌套。举个例子:你正在处理定时器中断(优先级2),突然来了个外部中断(优先级1),CPU会暂停定时器ISR,先去处理外部中断,处理完再回来继续执行定时器ISR。

中断嵌套的好处是紧急事件能得到及时响应。但坏处也很明显——嵌套层数太多,栈空间可能不够用。我记得有个同事在项目里嵌套了4层中断,结果栈溢出导致系统随机死机。排查了整整一周才发现是栈大小没配够。

所以我的建议是:中断嵌套尽量控制在2层以内。如果业务逻辑需要多层中断,不如考虑用RTOS的任务优先级来替代。


公众号:蓝海资料掘金营,微信deep3321