## 1. 为什么需要优先级分组? STM32 的 NVIC(嵌套向量中断控制器)支持中断嵌套,即高优先级中断可以打断低优先级中断的执行。为了管理这种嵌套关系,Cortex-M3/M4 内核引入了**优先级分组**机制,将每个中断的优先级分成两部分: - **抢占优先级(Preemption Priority)**:决定能否打断其他中断。数值越小,抢占能力越强。 - **子优先级(Sub Priority)**:当两个中断的抢占优先级相同时,子优先级数值越小,先响应(但不能打断彼此)。 注意:**只有抢占优先级不同,才能实现嵌套**;子优先级仅用于排队仲裁。 ## 2. 优先级分组寄存器:SCB->AIRCR 优先级分组通过设置应用程序中断及复位控制寄存器(AIRCR)的 PRIGROUP 位域完成。不同值对应不同的抢占/子优先级位宽,以 STM32F1/F4 为例(使用 4 位优先级): | PRIGROUP 值 | 抢占优先级位数 | 子优先级位数 | 可能的抢占优先级 | 可能的子优先级 | |------------|--------------|------------|----------------|--------------| | 0 (NVIC_PriorityGroup_0) | 0 | 4 | 0(不可嵌套) | 0~15 | | 1 (NVIC_PriorityGroup_1) | 1 | 3 | 0~1 | 0~7 | | 2 (NVIC_PriorityGroup_2) | 2 | 2 | 0~3 | 0~3 | | 3 (NVIC_PriorityGroup_3) | 3 | 1 | 0~7 | 0~1 | | 4 (NVIC_PriorityGroup_4) | 4 | 0 | 0~15 | 0(不支持子优先级) | 配置本质是修改 `SCB->AIRCR` 的 PRIGROUP 位,需使用 `VECTKEY` 解锁。**整个系统只能设置一次分组**,必须在初始化时确定,运行中切勿更改。 ## 3. 配置步骤与代码示例 ### 3.1 步骤概览 1. 在系统初始化时调用分组函数,设定全局分组策略。 2. 对外设中断的抢占优先级和子优先级赋值。 3. 使能对应外设中断(如 USART1_IRQn)和 NVIC 通道。 4. 编写中断服务函数,并确保高优先级中断能正确嵌套(中断函数内可响应更高抢占优先级的中断)。 ### 3.2 标准外设库配置(以 STM32F1 为例) ```c #include "stm32f10x.h" void NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; // 设置分组:第3组,抢占优先级2位,子优先级2位 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置USART1中断:抢占优先级2,子优先级0(抢占优先级数值小于其他中断,可嵌套它们) NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 配置EXTI0中断:抢占优先级3,子优先级1 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); } ``` ### 3.3 HAL 库配置(以 STM32F4 为例) ```c #include "stm32f4xx_hal.h" void HAL_NVIC_Config(void) { // 设置分组:第2组,抢占优先级2位,子优先级2位 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); // 配置USART1中断:抢占优先级2,子优先级0 HAL_NVIC_SetPriority(USART1_IRQn, 2, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); // 配置外部中断0:抢占优先级3,子优先级1 HAL_NVIC_SetPriority(EXTI0_IRQn, 3, 1); HAL_NVIC_EnableIRQ(EXTI0_IRQn); } ``` ### 3.4 中断服务函数与嵌套演示 ```c // USART1中断服务函数(优先级高,可打断EXTI0的处理) void USART1_IRQHandler(void) { // 假设此时在EXTI0_IRQHandler中被高优先级打断进入这里 // 处理串口事务... while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) { // 读取数据 } } // EXTI0中断服务函数(优先级低,执行中可能被USART1打断) void EXTI0_IRQHandler(void) { // 长时间处理... 在此时如果USART1中断到来,将暂停本函数,转入USART1_IRQHandler if (EXTI_GetITStatus(EXTI_Line0) != RESET) { // 处理外部事件 EXTI_ClearITPendingBit(EXTI_Line0); } } ``` ## 4. 优先级数值的误区与陷阱 - **数值越小优先级越高**:很多人习惯用“0最大”,在 STM32 中相反。 - **分组后优先级数值范围受限**:例如分组 2 时,抢占优先级只能写 0~3,子优先级只能写 0~3,超出范围会产生不可预测行为。 - **不要在中断服务函数中修改分组**:可能导致已配置的优先级映射错乱,甚至引发 HardFault。 - **RTOS 用户的警告**:FreeRTOS 等内核通常要求 `NVIC_PriorityGroup_4`(即全部位为抢占优先级),并限制中断优先级不高于 `configMAX_SYSCALL_INTERRUPT_PRIORITY`。如果使用 RTOS,分组配置必须与内核要求一致。 - **关闭中断的优先级限制**:`__disable_irq()` 会关闭所有可屏蔽中断,但 NMI 和 HardFault 不受影响,嵌套设计时不建议长期关闭中断。 ## 5. 实战建议 1. **先确定实时需求**:例如有 DMA 传输、高速通信、按键消抖等,将最紧急的中断(如系统心跳)设为抢占优先级 0,普通外设设为 2~3。 2. **尽量统一预处理优先级与子优先级位宽**:如果所有中断抢占优先级都不同,子优先级就无效了,但分组仍影响可用数值范围。 3. **通过 `NVIC_GetPriorityGrouping()` 和 `NVIC_GetPriority()` 调试检查实际配置**,避免人为配置错误。 4. **在中断嵌套场景下,注意栈空间**:每次嵌套都会消耗更多栈,需合理设置中断栈大小,防止溢出。 ## 6. 总结 STM32 的中断嵌套关键在于正确设置 AIRCR 分组,并理解抢占优先级决定“能否打断”,子优先级决定“谁先执行”。配置时务必遵循“系统分组全局唯一、单次设置”的原则,在标准库与 HAL 库中分别使用 `NVIC_PriorityGroupConfig()` 和 `HAL_NVIC_SetPriorityGrouping()`。结合具体项目的实时性要求,合理分配抢占/子优先级,才能写出健壮且响应及时的中断系统。