4. 优先级分组寄存器:SCB->AIRCR 详解与 PRIGROUP 配置
在 ARM Cortex-M 内核中,中断优先级的核心控制寄存器是 应用中断与复位控制寄存器(Application Interrupt and Reset Control Register, AIRCR),它位于系统控制块(SCB)中。该寄存器不仅负责软件复位和异常处理,还通过其 PRIGROUP 位域定义了整个系统的优先级分组规则,决定了优先级寄存器中哪些位用于抢占优先级、哪些位用于子优先级。
4.1 AIRCR 寄存器结构
AIRCR 是一个 32 位寄存器,地址为 0xE000ED0C。其关键位域定义如下表所示:
| 位域 | 名称 | 功能描述 |
|---|---|---|
| [31:16] | VECTKEY | 访问密钥,写入时必须为 0x05FA,否则写入无效。 |
| [15] | ENDIANESS | 只读,指示系统端序(0 = 小端,1 = 大端)。 |
| [10:8] | PRIGROUP | 优先级分组位域,决定抢占优先级与子优先级的位数分配。 |
| [2] | SYSRESETREQ | 系统复位请求,写 1 触发系统复位。 |
| [1] | VECTCLRACTIVE | 清除异常活跃状态(调试用)。 |
| [0] | VECTRESET | 内核复位(不复位外设)。 |
关键提示: 写入 AIRCR 时,
VECTKEY 位域必须同时写入 0x05FA,否则整个写入操作被忽略。这是硬件保护机制,防止意外修改优先级分组。
4.2 PRIGROUP 位域详解
PRIGROUP 位域(位 [10:8])共 3 位,支持 8 种分组模式。对于 Cortex-M3/M4/M7 等内核,优先级寄存器(如 NVIC_IPRx)使用 高 4 位(bit[7:4])存储优先级值,低 4 位固定为 0。PRIGROUP 决定这 4 位中哪些用于抢占优先级、哪些用于子优先级。
下表列出了所有 PRIGROUP 值对应的分组效果:
| PRIGROUP 值 | 抢占优先级位数 | 子优先级位数 | 抢占优先级等级数 | 子优先级等级数 |
|---|---|---|---|---|
| 0 (000) | 4 | 0 | 16 | 1 |
| 1 (001) | 3 | 1 | 8 | 2 |
| 2 (010) | 2 | 2 | 4 | 4 |
| 3 (011) | 1 | 3 | 2 | 8 |
| 4 (100) | 0 | 4 | 1 | 16 |
| 5–7 | 保留(Cortex-M3/M4 中行为未定义,禁止使用) | |||
工程经验: 最常用的分组是 PRIGROUP = 3(1 位抢占 + 3 位子优先级)和 PRIGROUP = 0(4 位抢占 + 0 位子优先级)。前者适合需要精细子优先级管理的场景,后者适合纯抢占式调度。
4.3 PRIGROUP 配置方法
配置 PRIGROUP 需要按照以下步骤操作:
- 构造密钥值: 将
0x05FA左移至位 [31:16]。 - 设置分组值: 将目标 PRIGROUP 值写入位 [10:8]。
- 保持其他位: 位 [15:11] 和位 [7:0] 保持当前值(通常读-改-写)。
- 写入寄存器: 将组合后的 32 位值写入
SCB->AIRCR。
标准 CMSIS 库提供了宏 NVIC_SetPriorityGrouping(),封装了上述操作。推荐直接使用该函数:
// 设置 PRIGROUP = 3:1 位抢占优先级,3 位子优先级
NVIC_SetPriorityGrouping(3);
// 等效的底层寄存器操作(仅供理解原理)
#define AIRCR_VECTKEY (0x05FAUL << 16)
#define AIRCR_PRIGROUP (3UL << 8)
SCB->AIRCR = AIRCR_VECTKEY | AIRCR_PRIGROUP | (SCB->AIRCR & 0x0000F8FF);
警告: 不要在中断服务函数(ISR)中修改 PRIGROUP。优先级分组应在系统初始化阶段、所有外设中断使能之前一次性配置完成。运行时修改分组会导致已配置的优先级含义混乱,引发不可预知的嵌套行为。
4.4 优先级分组与嵌套关系图解
下面的 SVG 图展示了 PRIGROUP = 3(1 位抢占 + 3 位子优先级)时,两个中断的优先级比较与嵌套判定流程:
4.5 配置示例与验证
以下代码演示了完整的 PRIGROUP 配置流程,并验证分组效果:
#include "stm32f4xx.h"
void System_Init(void)
{
// 1. 设置优先级分组:3 位抢占 + 1 位子优先级
NVIC_SetPriorityGrouping(3);
// 2. 验证分组值
uint32_t group = NVIC_GetPriorityGrouping();
if (group != 3) {
// 错误处理:分组配置失败
while(1);
}
// 3. 配置两个中断的优先级
// 中断 A:抢占 0,子优先级 0 → 优先级值 = (0 << 1) | 0 = 0x00
NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(3, 0, 0));
// 中断 B:抢占 0,子优先级 1 → 优先级值 = (0 << 1) | 1 = 0x01
NVIC_SetPriority(TIM3_IRQn, NVIC_EncodePriority(3, 0, 1));
// 中断 C:抢占 1,子优先级 0 → 优先级值 = (1 << 1) | 0 = 0x02
NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(3, 1, 0));
// 4. 使能中断
NVIC_EnableIRQ(TIM2_IRQn);
NVIC_EnableIRQ(TIM3_IRQn);
NVIC_EnableIRQ(USART1_IRQn);
}
优先级编码公式: 当 PRIGROUP = 3 时,优先级寄存器的值 = (抢占优先级 << 子优先级位数) | 子优先级。子优先级位数 = 8 - PRIGROUP - 4 = 1。因此公式为:
prio = (preempt << 1) | sub。
4.6 常见配置错误与排查
- 忘记写入 VECTKEY: 直接写 PRIGROUP 位而不带
0x05FA,寄存器值不变。排查时读取 AIRCR 确认分组值。 - 分组值超出范围: 写入 5–7 在 Cortex-M3/M4 中行为未定义,可能导致优先级比较异常。始终使用 0–4。
- 中断使能后修改分组: 已使能的中断优先级含义瞬间改变,可能造成优先级反转或死锁。务必在初始化阶段一次性配置。
- 优先级值超出有效位: 写入优先级寄存器的值必须左移 4 位(因为低 4 位硬件忽略)。CMSIS 的
NVIC_SetPriority()会自动处理移位。