3. 中断机制基础:轮询与中断的对比、中断向量表、中断服务程序(ISR)、中断优先级与嵌套

在嵌入式系统中,CPU 需要及时响应外部或内部事件。处理这些事件主要有两种方式:轮询(Polling)和中断(Interrupt)。理解两者的区别以及中断的底层机制,是设计高效热插拔检测系统的前提。

3.1 轮询与中断的对比

轮询是 CPU 主动、周期性地检查事件标志位;中断是外设主动通知 CPU 事件发生。下表从多个维度对比两种机制:

对比维度 轮询(Polling) 中断(Interrupt)
CPU 利用率 低效,CPU 被占用反复查询,无法执行其他任务 高效,CPU 仅在事件发生时响应,其余时间可处理其他任务
实时性 取决于轮询周期,响应延迟不可控 事件触发立即响应,实时性高
实现复杂度 简单,仅需循环读取寄存器 较复杂,需配置中断控制器、编写 ISR、处理优先级
资源消耗 持续占用 CPU 时间片,功耗较高 事件驱动,空闲时可进入低功耗模式
适用场景 事件频率极高、或对延迟不敏感的场景(如按键消抖后的状态读取) 事件随机发生、对实时性要求高的场景(如热插拔检测、数据接收)
设计建议:在热插拔检测中,GPIO 电平变化是随机事件,推荐使用中断触发。轮询仅适用于系统空闲时对状态进行二次确认,避免漏检。

下图展示了轮询与中断在 CPU 时间线上的本质区别:

轮询 vs 中断 CPU 时间线对比 轮询模式: CPU 持续轮询事件标志,无法执行其他任务 事件1 事件2 中断模式: 任务A 任务B 任务C 任务D 事件1触发中断 ISR

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 入口地址在向量表中固定。

下图展示了中断向量表的结构与跳转流程:

中断向量表结构与跳转流程 中断向量表 0x0000_0000: 初始栈指针 0x0000_0004: Reset_Handler 0x0000_0040: EXTI0_IRQHandler 0x0000_0044: EXTI1_IRQHandler 0x0000_0048: EXTI2_IRQHandler ... 中断号 16 中断服务程序(ISR) void EXTI0_IRQHandler(void) { // 清除中断标志位 // 读取 GPIO 电平 // 判断插拔状态 // 触发 I2C 通信 // 更新设备状态 // 退出中断

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 调试输出,优先级最低

下图展示了中断嵌套的执行流程:

中断嵌套执行流程 时间 主程序(低优先级) 低优先级 ISR 中断1 高优先级 ISR 中断2(抢占) 恢复 继续 中断嵌套规则 高优先级中断(中断2)可以抢占正在执行的低优先级中断(中断1) 低优先级中断必须等待高优先级 ISR 完成后才能恢复执行
热插拔优先级配置建议:GPIO 中断(EXTI)应设置为较高优先级(如抢占优先级 1),确保插拔事件能及时响应。I2C 通信中断可设置为较低优先级(如抢占优先级 2),避免在 ISR 中处理复杂通信时被其他事件干扰。

3.5 本章小结

  • 轮询 vs 中断:轮询简单但低效,中断高效但需合理配置。热插拔检测推荐使用 GPIO 中断。
  • 中断向量表:存储 ISR 入口地址,CPU 根据中断号自动查表跳转。
  • ISR 设计:短小、非阻塞、及时清除标志、保护共享数据。
  • 优先级与嵌套:合理分配抢占优先级,确保关键事件(如热插拔)优先处理,避免低优先级中断阻塞系统。