2. 硬件架构基础:中断控制器与中断响应
中断,是嵌入式系统的灵魂。没有中断,CPU 就得轮询,效率低得吓人。但中断也常常是 bug 的温床——我见过太多项目因为中断延迟超标导致系统崩溃。所以,搞懂硬件怎么处理中断,是调试实时系统的第一步。
这一章,我们聚焦两个主流架构:ARM Cortex-M 系列的 NVIC,和 Cortex-A/R 系列的 GIC。别被名字吓到,核心逻辑是相通的。
2.1 中断控制器:NVIC 与 GIC
中断控制器是 CPU 和外部中断源之间的“总调度员”。它的职责很简单:收中断、判优先级、发信号给 CPU。
2.1.1 NVIC(Nested Vectored Interrupt Controller)
NVIC 是 Cortex-M 系列的内置中断控制器。它最大的特点是:硬件自动压栈和出栈。这意味着中断响应时,CPU 会自动把 R0-R3、R12、LR、PC、xPSR 压入堆栈,然后从向量表取出中断服务函数地址直接跳转。
我个人习惯把 NVIC 的关键参数记在脑子里:
| 参数 | 说明 | 典型值 |
|---|---|---|
| 中断数量 | 可配置的外部中断线 | 16~240 条 |
| 优先级位数 | 可配置的优先级分组 | 3~8 位(Cortex-M3/M4 通常 4 位) |
| 抢占优先级 | 高优先级可打断低优先级 | 由分组决定 |
| 子优先级 | 同抢占优先级下的响应顺序 | 由分组决定 |
| 尾链技术 | 连续中断免去多余压栈 | 硬件自动支持 |
2.1.2 GIC(Generic Interrupt Controller)
GIC 用在 Cortex-A/R 系列上,比如手机 SoC 或高端 MCU。它比 NVIC 复杂得多,支持多核、消息中断、虚拟化等。
GIC 的架构分两部分:
- 分发器(Distributor):管理所有中断源,配置优先级、使能、触发方式。
- CPU 接口(CPU Interface):每个 CPU 核一个,负责向核发送中断信号。
GIC 支持三种中断类型:
- PPI(Private Peripheral Interrupt):私有外设中断,每个核独享。
- SPI(Shared Peripheral Interrupt):共享外设中断,可路由到任意核。
- SGI(Software Generated Interrupt):软件触发中断,用于核间通信。
2.2 中断向量表
中断向量表,说白了就是一张地址表。CPU 收到中断后,根据中断号查表,找到对应的服务函数地址,然后跳过去执行。
Cortex-M 的向量表固定在地址 0x00000000(或通过 VTOR 寄存器重映射)。表的前 16 个是系统异常,后面是外部中断。举个例子:
; Cortex-M3 向量表示例(部分)
__Vectors DCD __initial_sp ; 0: 栈顶指针
DCD Reset_Handler ; 1: 复位
DCD NMI_Handler ; 2: NMI
DCD HardFault_Handler ; 3: 硬错误
...
DCD TIM2_IRQHandler ; 28: TIM2 中断
DCD TIM3_IRQHandler ; 29: TIM3 中断
嗯,这里要注意:向量表必须 4 字节对齐。我遇到过有人把向量表放在一个未对齐的地址上,结果一进中断就跑飞了。调试了整整一天才发现是 VTOR 配置错了。
2.3 中断优先级与抢占机制
优先级和抢占,是实时系统的命门。搞不清楚这两个概念,中断延迟就无从谈起。
2.3.1 优先级分组
NVIC 支持优先级分组。通过 SCB->AIRCR 寄存器设置分组,把优先级寄存器拆成抢占优先级和子优先级两部分。
举个例子,4 位优先级寄存器,分组为 2 位抢占 + 2 位子优先级:
- 抢占优先级:0~3(数值越小优先级越高)
- 子优先级:0~3(数值越小优先级越高)
抢占优先级高的中断可以打断抢占优先级低的中断。子优先级只在同抢占优先级时决定谁先响应,不能打断。
2.3.2 抢占与嵌套
当一个高优先级中断发生时,如果 CPU 正在执行低优先级中断,硬件会自动触发抢占。流程是这样的:
- 低优先级中断正在执行。
- 高优先级中断到达。
- 硬件比较优先级,发现高优先级更高。
- CPU 暂停低优先级中断,压栈当前上下文。
- 跳转到高优先级中断服务函数。
- 高优先级中断执行完毕,出栈恢复上下文。
- 继续执行低优先级中断。
这个过程,NVIC 是硬件自动完成的。但 GIC 需要软件配合,因为 GIC 不自动压栈——你得在中断入口手动保存上下文。
2.4 CPU 流水线与中断响应
CPU 流水线对中断响应的影响,很多人会忽略。其实,它直接决定了中断延迟的下限。
2.4.1 流水线阶段
以 Cortex-M3 为例,它有一条 3 级流水线:取指、译码、执行。中断发生时,CPU 必须等当前指令执行完才能响应。如果当前指令是多周期指令(比如除法),中断就得等。
更麻烦的是,流水线中的预取指令会被冲刷掉。这意味着中断响应后,CPU 需要重新从向量表取地址,再取中断服务函数的第一条指令。这个过程会额外消耗几个时钟周期。
2.4.2 中断延迟的计算
中断延迟 = 硬件响应时间 + 软件处理时间。硬件响应时间包括:
- 等待当前指令完成(1~12 个周期)
- 压栈(12 个周期,Cortex-M3)
- 取向量表(1 个周期)
- 取中断服务函数第一条指令(1 个周期)
加起来,Cortex-M3 的硬件中断延迟大约是 12~26 个时钟周期。嗯,这个数字很关键——如果你测出来的中断延迟远大于这个值,那问题一定出在软件上。
2.5 知识结构图
下面这张图总结了中断系统的核心流程和关键节点。你可以把它当作调试时的检查清单。
2.6 本章小结
中断系统的硬件基础,说白了就是三件事:中断控制器怎么收中断、向量表怎么查地址、CPU 怎么响应。NVIC 和 GIC 虽然实现不同,但核心逻辑一致。优先级分组和抢占机制决定了中断的嵌套行为,而流水线则影响了中断延迟的下限。
调试中断问题时,我建议你先确认三件事:
- 向量表地址是否正确?
- 优先级分组是否一致?
- 中断服务函数里有没有阻塞操作?
这三步走完,80% 的中断问题都能定位。剩下的 20%,往往藏在硬件时序和流水线交互里——那才是真正考验功力的地方。