2. 异常类型与优先级:系统异常与外部中断的区分,优先级分组机制

2.1 异常与中断的本质区别

在 ARM Cortex-M 体系结构中,异常(Exception)是泛指任何打断处理器正常执行流程的事件,而中断(Interrupt)是异常的一个子集,特指由片内外设或外部引脚触发的事件。系统异常则是由内核内部状态或特定指令触发的同步/异步事件。

特征 系统异常 外部中断
触发源 内核内部、总线、指令执行 片内外设、GPIO 引脚
典型编号范围 1 ~ 15(Cortex-M3/4/7) 16 及以上(IRQ 0 ~ IRQ n)
可屏蔽性 Reset/NMI 不可屏蔽;HardFault 不可屏蔽 可通过 PRIMASK/FAULTMASK 或 NVIC 使能位屏蔽
优先级可编程 部分可编程(如 SVCall、PendSV、SysTick) 全部可编程(通过 NVIC_IPR 寄存器)
典型用途 系统调用、任务切换、故障处理 外设数据就绪、电平变化、定时器溢出
关键概念:在 NVIC(嵌套向量中断控制器)中,所有异常和中断共享同一个优先级空间,但系统异常的前 4 个(Reset、NMI、HardFault)具有固定优先级,不受分组机制影响。

2.2 系统异常详解(编号 1~15)

下表列出了 Cortex-M3/4/7 中最重要的系统异常及其向量表偏移、优先级属性:

异常编号 向量表偏移 异常名称 优先级 说明
1 0x0004 Reset -3(最高) 上电或复位后第一条指令入口
2 0x0008 NMI -2 不可屏蔽中断,用于紧急事件
3 0x000C HardFault -1 所有不可恢复故障的最终汇聚点
4 0x0010 MemManage 可编程 MPU 违规或非法访问
5 0x0014 BusFault 可编程 总线返回错误响应
6 0x0018 UsageFault 可编程 未定义指令、对齐错误等
11 0x002C SVCall 可编程 SVC 指令触发的系统调用
14 0x0038 PendSV 可编程 可挂起的系统服务请求
15 0x003C SysTick 可编程 系统滴答定时器中断
硬件强制优先级:Reset > NMI > HardFault 的优先级顺序由 ARM 架构硬编码,任何软件配置都无法改变。即使将 HardFault 的优先级寄存器设为 0,它仍然低于 NMI 和 Reset。

2.3 外部中断(IRQ)的编号与映射

外部中断从异常编号 16 开始,对应 NVIC 的 IRQ 编号 0 ~ n。以 STM32F407 为例:

  • 异常编号 16 → IRQ 0:WWDG(窗口看门狗)
  • 异常编号 17 → IRQ 1:PVD(电源电压检测)
  • 异常编号 40 → IRQ 24:USART1
  • 异常编号 45 → IRQ 29:SPI1
  • 异常编号 81 → IRQ 65:DMA2 数据流 0

每个外部中断的使能、挂起、优先级控制均通过 NVIC 寄存器组(ISER/ICER/ISPR/ICPR/IPR)完成,与系统异常的配置路径分离。

2.4 优先级分组机制

Cortex-M 内核通过 PRIGROUP 字段(位于 SCB->AIRCR 寄存器)将优先级寄存器中的位划分为抢占优先级(Preempt Priority)和子优先级(Subpriority)两部分。分组规则如下:

PRIGROUP 值 分组方式 抢占优先级位数 子优先级位数 抢占优先级等级数 子优先级等级数
0 NVIC_PriorityGroup_0 0 4 1 16
1 NVIC_PriorityGroup_1 1 3 2 8
2 NVIC_PriorityGroup_2 2 2 4 4
3 NVIC_PriorityGroup_3 3 1 8 2
4 NVIC_PriorityGroup_4 4 0 16 1
重要约束:PRIGROUP 必须在系统启动早期(通常在 Reset Handler 中)一次性配置,且在整个运行期间不应修改。修改 PRIGROUP 会导致所有已配置的优先级重新解释,可能引发不可预知的抢占行为。

2.5 优先级分组与抢占行为

优先级分组直接影响中断嵌套的行为:

  • 抢占优先级不同:高抢占优先级的中断可以打断低抢占优先级的中断服务程序。
  • 抢占优先级相同,子优先级不同:不会发生抢占,但若同时到达,硬件优先响应子优先级更高的中断。
  • 抢占优先级和子优先级都相同:按异常编号(向量表顺序)决定响应顺序,编号越小优先级越高。
// 配置优先级分组为 Group 2(2位抢占 + 2位子优先级)
// 对应 PRIGROUP = 2,AIRCR 寄存器值为 0x05FA0400
#define NVIC_PRIORITYGROUP_2  ((uint32_t)0x00000500)  // SCB->AIRCR 的 PRIGROUP 位段

void NVIC_Configuration(void) {
    // 设置优先级分组
    SCB->AIRCR = NVIC_PRIORITYGROUP_2 | (0x5FAUL << SCB_AIRCR_VECTKEY_Pos);
    
    // 配置 USART1 中断优先级:抢占 2,子优先级 1
    // 优先级寄存器值为 (2 << 2) | 1 = 0x09
    NVIC_SetPriority(USART1_IRQn, 0x09);
    
    // 配置 TIM2 中断优先级:抢占 1,子优先级 3
    // 优先级寄存器值为 (1 << 2) | 3 = 0x07
    NVIC_SetPriority(TIM2_IRQn, 0x07);
    
    // 使能中断
    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_EnableIRQ(TIM2_IRQn);
}

2.6 异常优先级架构总览

下图展示了从最高优先级到最低优先级的完整异常层级结构,以及系统异常与外部中断在优先级空间中的位置关系:

ARM Cortex-M 异常优先级层级架构 Reset(优先级 -3) NMI(优先级 -2) HardFault(优先级 -1) 可编程系统异常(MemManage / BusFault / UsageFault / SVCall / PendSV / SysTick) 优先级范围:0 ~ 15(取决于 PRIGROUP 分组) 外部中断(IRQ 0 ~ IRQ n) 优先级范围:0 ~ 15(与系统异常共享优先级空间) 优先级分组(PRIGROUP)的影响 PRIGROUP 决定抢占优先级与子优先级的位数分配,影响中断嵌套深度 固定优先级区域(不受 PRIGROUP 影响) Reset / NMI / HardFault 的优先级由架构硬编码,软件不可修改

2.7 优先级配置的典型陷阱

  • 陷阱 1:在使能中断后才修改 PRIGROUP,导致已使能的中断优先级被重新解释,可能产生意外抢占。
  • 陷阱 2:将系统异常(如 SVCall)的优先级配置为 0,但未考虑 HardFault 的固定优先级 -1,导致故障处理流程异常。
  • 陷阱 3:在中断服务程序中调用 NVIC_SetPriority() 修改自身或其他中断的优先级,可能造成优先级反转或死锁。
  • 陷阱 4:混淆异常编号与 IRQ 编号,例如将异常编号 16 当作 IRQ 16 写入 NVIC_IPR 寄存器,实际应使用 IRQ 0。
最佳实践:在系统启动阶段的 Reset Handler 中,首先调用 NVIC_SetPriorityGrouping() 确定分组方案,然后依次配置所有用到的系统异常和外部中断的优先级,最后才全局使能中断(CPSIE I)。整个过程中保持优先级配置的单调性,避免运行时动态调整。