3. 中断基础:中断向量表、ISR 编写规则与中断嵌套

在 RTOS 环境下,中断是外设与 CPU 交互的最高优先级通道。理解中断向量表的结构、ISR 的编写规范以及中断嵌套与抢占机制,是调试外设驱动的前提。本节从硬件基础出发,逐步深入到 RTOS 对中断的管理策略。

3.1 中断向量表:系统响应的入口地图

中断向量表(Interrupt Vector Table, IVT)是 CPU 在响应中断时查找服务程序入口地址的索引表。每个中断源对应一个固定的表项,表项中存放的是该中断服务程序(ISR)的起始地址。

关键概念: 向量表通常位于 ROM 或 RAM 起始地址(如 ARM Cortex-M 的 0x00000000),复位后由硬件自动加载。在 RTOS 中,向量表可能被重定位到 RAM 以支持动态修改。
中断向量表结构示意图(ARM Cortex-M 示例) 向量表基地址 (0x00000000) 表项 0: 栈顶指针 表项 1: 复位向量 表项 2: NMI 向量 表项 3: HardFault ...... 表项 16: UART0_IRQ 表项 17: SPI1_IRQ 表项 18: TIM2_IRQ 表项 19: I2C0_IRQ ISR 地址 ISR 地址 ISR 地址 ISR 地址 每个表项固定 4 字节(32 位)或 8 字节(64 位),存放对应 ISR 的入口地址
常见中断向量表区域划分
区域表项范围用途RTOS 关注点
异常向量0 – 15系统异常(复位、NMI、HardFault 等)通常不修改,由启动文件固化
外部中断向量16 – N外设中断(UART、SPI、TIM、I2C 等)RTOS 可重定位向量表以支持动态注册
软件触发中断用户定义任务间通信、系统调用(SVC、PendSV)RTOS 内核依赖 PendSV 进行上下文切换

3.2 中断服务程序(ISR)编写规则

ISR 是中断响应的执行体。在 RTOS 环境下,ISR 的编写比裸机更加严格,必须遵循以下核心规则,否则可能导致系统崩溃或任务调度异常。

3.2.1 ISR 编写黄金法则

  1. 短小精悍: ISR 应尽快完成关键操作(如读取数据寄存器、清除中断标志),将耗时处理推迟到任务中。
  2. 避免阻塞: 禁止在 ISR 中调用阻塞型 API(如 vTaskDelay()、vTaskSuspend()),否则会导致系统死锁。
  3. 使用中断安全 API: 必须调用 RTOS 提供的“FromISR”后缀函数(如 xQueueSendFromISR()),这些函数不会触发上下文切换。
  4. 保存/恢复上下文: 编译器或 RTOS 入口/出口宏会自动处理,但手动编写汇编 ISR 时必须显式保存所有通用寄存器。
  5. 清除中断标志: 在 ISR 返回前必须清除外设的中断挂起位,否则会无限触发同一中断。
RTOS 实践提示: 在 FreeRTOS 中,ISR 内调用 xQueueSendFromISR() 后,应检查返回值 pxHigherPriorityTaskWoken。若为 pdTRUE,则在 ISR 退出前调用 portYIELD_FROM_ISR() 触发一次上下文切换,确保高优先级任务及时运行。

3.2.2 ISR 代码模板(FreeRTOS + ARM Cortex-M)

// UART 接收中断服务程序示例
void UART0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint8_t rx_byte;

    // 1. 读取数据并清除中断标志
    rx_byte = UART0->DR;          // 读取数据寄存器自动清除 RX 中断
    UART0->ICR |= (1 << 4);      // 手动清除其他中断标志(如溢出错误)

    // 2. 将数据发送到队列(中断安全版本)
    xQueueSendFromISR(xRxQueue, &rx_byte, &xHigherPriorityTaskWoken);

    // 3. 如果唤醒高优先级任务,请求上下文切换
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

3.3 中断嵌套与抢占:优先级管理的核心

中断嵌套是指一个 ISR 正在执行时,另一个更高优先级的中断到达,CPU 暂停当前 ISR 转而服务更高优先级中断的机制。抢占则是指高优先级中断可以“打断”低优先级中断的执行。

3.3.1 中断优先级分组

ARM Cortex-M 内核支持中断优先级分组,将优先级寄存器的高位划分为抢占优先级和子优先级。抢占优先级决定是否可以嵌套,子优先级用于同抢占优先级下的仲裁。

ARM Cortex-M 优先级分组配置(以 8 位优先级寄存器为例)
分组编号抢占优先级位数子优先级位数典型应用场景
NVIC_PriorityGroup_008无嵌套,仅子优先级仲裁
NVIC_PriorityGroup_1172 级嵌套深度
NVIC_PriorityGroup_2264 级嵌套深度(常用)
NVIC_PriorityGroup_3358 级嵌套深度
NVIC_PriorityGroup_44416 级嵌套深度(RTOS 推荐)
RTOS 配置警告: FreeRTOS 要求将所有中断优先级配置为“抢占优先级”模式(即分组 4),并将 RTOS 管理的最高中断优先级设置为 configMAX_SYSCALL_INTERRUPT_PRIORITY。高于此优先级的中断不会被 RTOS 临界区屏蔽,用于时间关键型外设(如电机控制 PWM)。

3.3.2 中断嵌套流程

下图展示了三级中断嵌套的执行流程,直观体现抢占与返回顺序。

中断嵌套与抢占执行流程 时间 → 主程序 / 任务 低优先级 ISR (IRQ1) 中优先级 ISR (IRQ2) 高优先级 ISR (IRQ3) 返回 返回 返回 IRQ2 到达 IRQ3 到达 执行顺序:主程序 → IRQ1 → IRQ2 → IRQ3 → 返回 IRQ2 → 返回 IRQ1 → 返回主程序

3.3.3 中断嵌套的 RTOS 注意事项

  • 临界区保护: RTOS 的临界区(如 taskENTER_CRITICAL())会屏蔽优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断。高优先级中断(高于该阈值)仍可嵌套,但不得调用任何 RTOS API。
  • 栈深度: 每层嵌套都会消耗额外的栈空间。在嵌套深度较大的系统中,必须为 ISR 栈分配足够的空间,避免栈溢出。
  • 优先级反转: 中断嵌套不会导致优先级反转,但中断与任务之间的资源竞争可能引发类似问题。建议在 ISR 与任务之间使用无锁队列或中断安全信号量。
调试经验: 当系统出现“中断丢失”或“ISR 执行时间异常”时,首先检查中断优先级分组是否与 RTOS 配置一致。其次,使用逻辑分析仪或 GPIO 翻转法测量 ISR 的实际执行时间,确认是否因嵌套导致延迟超出预期。