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收到中断请求后,它内部会走一套固定的流程。我画了个图帮你理解:
这个流程里,我最想强调的就是「ISR要尽量短」。为什么?因为中断服务函数执行期间,其他同优先级或更低优先级的中断都被屏蔽了。如果你在ISR里搞个10ms的延时,整个系统的实时性就崩了。我一般只在ISR里设置标志位或传递数据,真正的处理逻辑放到主循环里做。
我的习惯:中断服务函数里只做三件事——读数据、清标志、设变量。其他所有复杂计算、协议解析、状态机切换,统统放到主循环或任务调度里。这样既保证了中断的快速响应,又避免了在ISR里出bug。
1.6 中断嵌套——中断里还能有中断?
是的,高优先级的中断可以打断正在执行的低优先级中断。这就是中断嵌套。举个例子:你正在处理定时器中断(优先级2),突然来了个外部中断(优先级1),CPU会暂停定时器ISR,先去处理外部中断,处理完再回来继续执行定时器ISR。
中断嵌套的好处是紧急事件能得到及时响应。但坏处也很明显——嵌套层数太多,栈空间可能不够用。我记得有个同事在项目里嵌套了4层中断,结果栈溢出导致系统随机死机。排查了整整一周才发现是栈大小没配够。
所以我的建议是:中断嵌套尽量控制在2层以内。如果业务逻辑需要多层中断,不如考虑用RTOS的任务优先级来替代。
公众号:蓝海资料掘金营,微信deep3321