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层。太深的嵌套会导致:
- 栈溢出风险增加
- 低优先级中断的延迟变得不可预测
- 调试难度指数级上升
中断嵌套流程示意
- 主程序正在执行
- 低优先级中断IRQ_L到达,CPU保存现场,跳转到ISR_L
- ISR_L执行中,高优先级中断IRQ_H到达
- CPU再次保存现场(嵌套),跳转到ISR_H
- ISR_H执行完毕,恢复现场,返回ISR_L
- ISR_L继续执行,完毕,恢复现场,返回主程序
1.5 知识体系总览
下面这张图把中断的基础概念串起来了。你仔细看看,理解每个模块之间的关系。
1.6 小结
这一章咱们把中断的四个基础概念过了一遍:
- 中断定义:CPU被事件打断,执行完ISR再回来
- 中断向量表:存放ISR入口地址的表格,CPU靠它找到处理函数
- 中断优先级:决定哪个中断先被处理,数值越小优先级越高
- 中断嵌套:高优先级中断可以打断低优先级中断的服务程序
这些概念看着简单,但实际项目中踩坑的人不少。我建议你找个开发板,动手配置一下中断,看看向量表在内存里的布局,试试不同优先级下的嵌套行为。光看书是学不会的。