3、中断系统深入解析:中断向量表、优先级配置、中断嵌套与抢占、中断延迟分析
中断系统,说白了就是单片机的“紧急事务处理机制”。没有它,CPU就得像个傻子一样轮询每个外设——你想想看,那得多浪费算力?我做了十几年嵌入式,见过太多因为中断配置不当导致的“灵异现象”:按键按下去没反应、串口丢数据、电机突然抽风……十有八九都是中断没玩明白。
这一章,咱们就把中断系统扒开揉碎了讲。从最底层的向量表,到优先级怎么配,再到嵌套抢占的坑,最后聊聊中断延迟这个硬指标。嗯,都是实战里天天要面对的东西。
3.1 中断向量表:CPU的“紧急联络簿”
中断向量表,你可以把它想象成一本电话簿。每个中断源都有一个专属的“电话号码”——也就是中断服务函数(ISR)的入口地址。CPU收到中断信号后,就翻这本电话簿,找到对应的号码,直接跳过去执行。
这张表通常放在Flash或RAM的起始地址。以Cortex-M3为例,向量表从0x00000000开始,前16个是系统异常,后面才是外设中断。
我记得有一次调试一个STM32F103的项目,程序死活不进定时器中断。查了半天,发现是向量表偏移没设置对——默认在0x08000000,但我把代码下载到了0x08010000。嗯,这种低级错误,犯过一次就再也不会忘了。
3.1.1 向量表重定位
现代MCU都支持向量表重定位。什么意思呢?就是你可以把向量表从Flash搬到RAM里,运行时动态修改中断入口。这在Bootloader+App架构里特别常见:Bootloader在Flash里,App也在Flash里,但App启动时得把向量表地址改到自己的起始地址。
// Cortex-M3 向量表偏移寄存器设置示例
#define SCB_VTOR (*(volatile uint32_t *)0xE000ED08)
// 假设App起始地址为0x08010000
SCB_VTOR = 0x08010000 | 0x00000000; // 设置向量表偏移
3.2 优先级配置:谁更重要,谁先处理
中断优先级,就是给中断排个座次。高优先级的中断可以打断低优先级的,低优先级的只能等着。但这里有个坑:不同MCU的优先级模型不一样。
| MCU架构 | 优先级模型 | 特点 |
|---|---|---|
| Cortex-M0/M0+ | 固定4级优先级 | 不可嵌套,简单粗暴 |
| Cortex-M3/M4 | 可配置抢占优先级+子优先级 | 支持嵌套,灵活但容易配错 |
| AVR | 固定优先级(向量表顺序) | 向量号越小优先级越高 |
| PIC | 可配置高低两级 | 简单,但不够灵活 |
我个人习惯用Cortex-M3/M4时,把抢占优先级分成3位,子优先级1位。这样就有8级抢占优先级,每级下面有2个子优先级。够用,而且不容易搞混。
3.2.1 优先级分组
Cortex-M的优先级分组由SCB->AIRCR寄存器控制。分组方式决定了抢占优先级和子优先级各占多少位。
// STM32 HAL库中的优先级分组配置
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
// 分组4:4位抢占优先级,0位子优先级
// 也就是16级抢占优先级,不支持子优先级
// 设置具体中断的优先级
HAL_NVIC_SetPriority(TIM2_IRQn, 2, 0); // 抢占优先级2,子优先级0
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0); // 抢占优先级1,子优先级0
// USART1优先级高于TIM2,可以打断TIM2的中断服务函数
3.3 中断嵌套与抢占:谁可以打断谁
中断嵌套,就是高优先级中断打断低优先级中断的执行。CPU会先保存当前中断的现场(压栈),然后去处理高优先级中断,处理完再回来继续执行低优先级中断。
这个过程,硬件自动完成了大部分工作。但有个关键点:中断服务函数要尽量短。你想想看,如果一个中断ISR执行了100微秒,这期间所有比它优先级低的中断都得等着。要是这个ISR里还调了个延时函数……嗯,系统基本就废了。
我曾经接手过一个项目,UART接收中断的ISR里居然做了协议解析和CRC校验,一次中断处理耗时2毫秒。结果波特率一高,数据就疯狂丢失。后来我把协议解析挪到主循环里做,中断里只做FIFO缓冲,问题立刻解决。
3.3.1 抢占的硬件行为
当高优先级中断抢占时,硬件自动完成以下步骤:
- 保存当前中断的上下文(R0-R3, R12, LR, PC, xPSR)到栈中
- 更新栈指针SP
- 从向量表加载新中断的入口地址
- 跳转到新中断的ISR执行
- 新ISR执行完毕后,执行异常返回指令(EXC_RETURN)
- 硬件自动恢复之前中断的上下文
- 继续执行之前中断的ISR
整个过程,硬件大概需要12个时钟周期。嗯,比你手写任何软件调度都快得多。
3.4 中断延迟分析:从触发到执行到底有多快
中断延迟,就是从中断信号触发到CPU开始执行ISR第一条指令的时间。这个指标直接决定了系统的实时性。
中断延迟由以下几个部分组成:
| 延迟来源 | 典型时间 | 说明 |
|---|---|---|
| 硬件响应延迟 | 3-12个时钟周期 | CPU识别中断、压栈、取向量 |
| 中断屏蔽时间 | 取决于当前代码 | 如果CPU正在执行关中断的代码,必须等开中断后才能响应 |
| 高优先级中断阻塞 | 取决于高优先级ISR长度 | 如果有更高优先级的中断正在执行,必须等它完成 |
| 指令完成延迟 | 1-2个时钟周期 | CPU必须完成当前指令才能响应中断 |
对于Cortex-M3跑在72MHz,硬件响应延迟大概在0.1-0.2微秒。但如果你在某个关中断的临界区里待了10微秒,那中断延迟就至少是10微秒。
3.4.1 测量中断延迟的方法
想知道你的系统中断延迟到底多大?别猜,直接量。我常用的方法:
- 在中断触发引脚上接一个GPIO,中断ISR里立刻翻转另一个GPIO
- 用示波器同时抓这两个信号
- 测量触发信号上升沿到响应信号上升沿的时间差
这个方法虽然粗糙,但足够实用。我见过有人用逻辑分析仪配合DMA来精确测量,但说实话,对于大多数嵌入式项目,示波器量一下就够了。
3.5 中断系统核心逻辑图
下面这张图展示了中断从触发到响应的完整流程,以及优先级、嵌套、延迟之间的关系。我建议你把它存下来,配优先级的时候对照着看。
这张图把中断的整个链路串起来了。你配置优先级的时候,心里要清楚:这个中断的延迟,取决于它前面所有环节的耗时。尤其是那个“中断屏蔽时间”——很多人忽略了这个,结果系统实时性怎么调都上不去。