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 需要按照以下步骤操作:

  1. 构造密钥值: 将 0x05FA 左移至位 [31:16]。
  2. 设置分组值: 将目标 PRIGROUP 值写入位 [10:8]。
  3. 保持其他位: 位 [15:11] 和位 [7:0] 保持当前值(通常读-改-写)。
  4. 写入寄存器: 将组合后的 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 位子优先级)时,两个中断的优先级比较与嵌套判定流程:

PRIGROUP=3 时中断优先级比较与嵌套判定 PRIGROUP=3 中断嵌套判定流程 中断 A 优先级值 = 0x30 抢占: 0 (高) / 子: 0 (高) 中断 B 优先级值 = 0x50 抢占: 1 (低) / 子: 0 (高) 抢占比较 A: 0 vs B: 1 A 抢占优先级更高 判定结果 中断 A 可抢占中断 B(嵌套发生) 中断 B 被挂起 中断 A 执行 A 退出 → B 恢复执行

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() 会自动处理移位。