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 异常优先级架构总览
下图展示了从最高优先级到最低优先级的完整异常层级结构,以及系统异常与外部中断在优先级空间中的位置关系:
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)。整个过程中保持优先级配置的单调性,避免运行时动态调整。