3. 中断机制基础:轮询与中断的对比、中断向量表、中断服务程序(ISR)、中断优先级与嵌套
在嵌入式系统中,CPU 需要及时响应外部或内部事件。处理这些事件主要有两种方式:轮询(Polling)和中断(Interrupt)。理解两者的区别以及中断的底层机制,是设计高效热插拔检测系统的前提。
3.1 轮询与中断的对比
轮询是 CPU 主动、周期性地检查事件标志位;中断是外设主动通知 CPU 事件发生。下表从多个维度对比两种机制:
| 对比维度 | 轮询(Polling) | 中断(Interrupt) |
|---|---|---|
| CPU 利用率 | 低效,CPU 被占用反复查询,无法执行其他任务 | 高效,CPU 仅在事件发生时响应,其余时间可处理其他任务 |
| 实时性 | 取决于轮询周期,响应延迟不可控 | 事件触发立即响应,实时性高 |
| 实现复杂度 | 简单,仅需循环读取寄存器 | 较复杂,需配置中断控制器、编写 ISR、处理优先级 |
| 资源消耗 | 持续占用 CPU 时间片,功耗较高 | 事件驱动,空闲时可进入低功耗模式 |
| 适用场景 | 事件频率极高、或对延迟不敏感的场景(如按键消抖后的状态读取) | 事件随机发生、对实时性要求高的场景(如热插拔检测、数据接收) |
设计建议:在热插拔检测中,GPIO 电平变化是随机事件,推荐使用中断触发。轮询仅适用于系统空闲时对状态进行二次确认,避免漏检。
下图展示了轮询与中断在 CPU 时间线上的本质区别:
3.2 中断向量表(IVT)
中断向量表是存储中断服务程序入口地址的表格。当 CPU 响应中断时,硬件自动根据中断号从向量表中取出对应的 ISR 地址并跳转执行。
在 ARM Cortex-M 系列 MCU 中,向量表通常位于存储器的起始地址(如 0x00000000),其结构如下:
| 偏移地址 | 中断号 | 内容 | 说明 |
|---|---|---|---|
| 0x0000_0000 | - | 初始栈指针(MSP) | 复位后栈顶地址 |
| 0x0000_0004 | 1 | 复位中断(Reset_Handler) | 系统启动入口 |
| 0x0000_0008 | 2 | NMI 中断(NMI_Handler) | 不可屏蔽中断 |
| 0x0000_000C | 3 | 硬错误(HardFault_Handler) | 严重错误处理 |
| ... | ... | ... | ... |
| 0x0000_0040 | 16 | EXTI0_IRQHandler | 外部中断线 0(GPIO 中断) |
| 0x0000_0044 | 17 | EXTI1_IRQHandler | 外部中断线 1 |
关键点:在热插拔检测中,GPIO 中断通常映射到 EXTI(外部中断/事件控制器)的中断线。每个 GPIO 引脚可以配置为触发 EXTI 中断,对应的 ISR 入口地址在向量表中固定。
下图展示了中断向量表的结构与跳转流程:
3.3 中断服务程序(ISR)
中断服务程序(ISR)是中断触发后 CPU 执行的特定函数。ISR 的设计直接影响系统的实时性和稳定性,必须遵循以下原则:
- 短小精悍:ISR 应尽快执行完毕,避免阻塞其他中断或任务。通常只做标志位设置、数据拷贝等轻量操作。
- 不可阻塞:ISR 中禁止使用延时、等待循环或可能引起阻塞的函数(如 printf、malloc)。
- 清除中断标志:退出 ISR 前必须清除中断挂起标志,否则会反复触发同一中断。
- 保护共享数据:如果 ISR 与主循环共享变量,需使用
volatile关键字声明,必要时关中断保护。
常见错误:在 ISR 中执行复杂 I2C 通信操作。I2C 通信依赖时钟同步,可能因中断嵌套或优先级反转导致总线锁死。正确做法是在 ISR 中设置事件标志,在主循环或任务中处理 I2C 通信。
以下是一个典型的热插拔检测 ISR 代码示例:
// GPIO 中断服务程序 - 热插拔检测
volatile uint8_t g_hotplug_flag = 0; // 共享标志位,需 volatile
void EXTI0_IRQHandler(void) {
// 1. 检查中断源
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
// 2. 读取当前 GPIO 电平,判断是插入还是拔出
uint8_t pin_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
if (pin_state == 1) {
g_hotplug_flag = 1; // 设备插入
} else {
g_hotplug_flag = 2; // 设备拔出
}
// 3. 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
// 主循环中处理事件
int main(void) {
while (1) {
if (g_hotplug_flag == 1) {
// 处理插入事件:启动 I2C 通信,读取设备信息
I2C_StartCommunication();
g_hotplug_flag = 0;
} else if (g_hotplug_flag == 2) {
// 处理拔出事件:更新系统状态
UpdateDeviceList();
g_hotplug_flag = 0;
}
// 其他任务...
}
}
3.4 中断优先级与嵌套
现代 MCU 支持多级中断优先级,允许高优先级中断抢占低优先级中断的执行,形成中断嵌套。合理配置优先级是系统稳定运行的关键。
以 ARM Cortex-M4 为例,NVIC(嵌套向量中断控制器)支持最多 16 级抢占优先级(0~15,0 为最高)。优先级配置遵循以下规则:
- 抢占优先级:决定中断是否可以打断另一个中断的执行。数值越小,优先级越高。
- 子优先级:当两个中断的抢占优先级相同时,子优先级决定响应顺序,但不支持嵌套。
- 自然优先级:若抢占优先级和子优先级均相同,则中断号小的优先响应(硬件决定)。
| 中断源 | 抢占优先级 | 子优先级 | 说明 |
|---|---|---|---|
| SysTick(系统滴答) | 0(最高) | 0 | 系统时钟,不可被其他中断阻塞 |
| EXTI0(GPIO 中断) | 1 | 0 | 热插拔检测,需快速响应 |
| I2C1_EV(I2C 事件) | 2 | 0 | I2C 通信,可被 GPIO 中断抢占 |
| USART1(串口) | 3 | 0 | 调试输出,优先级最低 |
下图展示了中断嵌套的执行流程:
热插拔优先级配置建议:GPIO 中断(EXTI)应设置为较高优先级(如抢占优先级 1),确保插拔事件能及时响应。I2C 通信中断可设置为较低优先级(如抢占优先级 2),避免在 ISR 中处理复杂通信时被其他事件干扰。
3.5 本章小结
- 轮询 vs 中断:轮询简单但低效,中断高效但需合理配置。热插拔检测推荐使用 GPIO 中断。
- 中断向量表:存储 ISR 入口地址,CPU 根据中断号自动查表跳转。
- ISR 设计:短小、非阻塞、及时清除标志、保护共享数据。
- 优先级与嵌套:合理分配抢占优先级,确保关键事件(如热插拔)优先处理,避免低优先级中断阻塞系统。