ARM Cortex-M内核的单片机,都具有中断抢占优先级(Preemption Priority)和子优先级(Sub Priority,又称“响应优先级”),再加上将这些优先级进行分组,容易让人糊涂。

一、怎么设置中断优先级
执行一条库函数即可:
HAL_NVIC_SetPriority(中断号, 抢占优先级号, 子优先级号)
两个参数都是数值越小,优先级越高,0为最高。
举例:
HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0,0);上面这条指令将DMA2_Stream2中断的抢占优先级和子优先级设为最高。
二、中断优先级分组(NVIC_PriorityGroup)
“中断优先级分组”这个概念听起来很抽象,换个说法就好懂了:其实就是为整个项目,选择一套“中断优先级分配方案”。注意,这是对整个项目的一次性选择,而不是针对某个具体的中断。
绝大多数Cortex‑M3/M4/M7系列内核的单片机,都有5种方案可选,只能选其一:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_0);HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_1);HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_3);HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
一旦选择了某一种中断方式,抢占优先级和子优先级的取值范围就固定下来了:
选择的中断方案 | 抢占优先级范围 | 子优先级范围 |
NVIC_PRIORITYGROUP_0 | 0 | 0~15 |
NVIC_PRIORITYGROUP_1 | 0~1 | 0~7 |
NVIC_PRIORITYGROUP_2 | 0~3 | 0~3 |
NVIC_PRIORITYGROUP_3 | 0~7 | 0~1 |
NVIC_PRIORITYGROUP_4 | 0~15 | 0 |
关键规则:
优先级判断规则:
当多个中断同时触发时,STM32 按以下顺序判断执行顺序:
解读一下:
•配置成NVIC_PRIORITYGROUP_0:
抢占优先级是0,意味着完全没有抢占,所有中断一视同仁,此时中断响应顺序完全由子优先级决定。无论你代码里给抢占优先级参数填什么,实际都被视为0。
HAL_NVIC_SetPriority(USART1_IRQn, 0, 2); //抢占优先级实际为0HAL_NVIC_SetPriority(EXTI0_IRQn,3, 1); // 抢占优先级实际也是0
•配置成NVIC_PRIORITYGROUP_4:
子优先级是0,意味着子优先级被禁用。优先级全部用于抢占,中断系统变成了纯抢占式模式。此时,当两个中断的抢占优先级相同时,不再比较子优先级,如果两个中断同时到来,硬件会根据中断编号(IRQn)顺序决定谁先响应(编号小的先执行)。
•配置成 NVIC_PRIORITYGROUP_1~3
这三种是更均衡的方案,既有抢占优先级,也有子优先级,可以根据项目需要,灵活划分中断响应层次。
三、默认配置
用STM32CubeMX直接生成的代码,默认配置成NVIC_PRIORITYGROUP_4,位于函数HAL_Init(void)中:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);如果要修改,可以在Main函数中显式地声明,例如:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);然后,记得要为你用到的每一个中断设定好抢占优先级和子优先级。
一个可以直接抄的"抢占分级地图"(Group_4 下,数字越小越紧急):越靠硬件越紧急,越靠业务越宽松;SysTick 永远压最底。

四、代码实现(以STM32F103为例)
1. 配置中断分组(全局唯一)
//配置中断分组为Group2(2位抢占 + 2位响应)void NVIC_ConfigGroup(void){//初始化NVIC结构体NVIC_InitTypeDef NVIC_InitStructure;//配置中断分组:Group2NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//示例:配置外部中断EXTI0的优先级NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择EXTI0中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级:1(0~3)NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应优先级:0(0~3)NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该中断通道NVIC_Init(&NVIC_InitStructure);//示例:配置定时器TIM2的优先级NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//选择TIM2中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级:1(与EXTI0相同)NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级:1(比EXTI0低)NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}
2. 优先级效果说明
以上配置中:
EXTI0和TIM2的抢占优先级都是 1,因此无法互相嵌套; 若EXTI0和TIM2同时触发,EXTI0 的响应优先级0高于TIM2的1,因此先执行EXTI0中断服务函数; 若将TIM2的抢占优先级改为0,则TIM2可以打断EXTI0的执行(嵌套)。
五、实际开发注意事项

六、使用FreeRTOS时的注意事项
如果上了FreeRTOS 这类操作系统,情况就不同了:内核占用了0~4优先级,留给用户的外设中断只能用5 ~ 15。千万不能手动将任何中断优先级设置成0~4,否则,可能破坏系统的实时性,导致不可预料的后果。想区分优先级?很简单,在5~15之间分配就行,数值越小级别越高。
七、总结
