4、中断优先级配置:抢占优先级与子优先级、优先级分组、优先级配置实战

好,咱们接着聊中断。上一节我们把中断的开关、挂起、清除这些基本操作捋了一遍。这一节,咱们要啃一块硬骨头——中断优先级配置。

说实话,我刚入行那会儿,觉得中断优先级不就是“谁重要谁先响应”嘛,有啥好讲的?结果第一次做多中断嵌套的项目,就踩了坑。两个中断同时触发,系统直接跑飞了。后来查了半天,才发现是优先级分组没配对。

嗯,咱们今天就把这块彻底讲透。

4.1 抢占优先级 vs 子优先级

先搞清楚两个概念:抢占优先级和子优先级。

  • 抢占优先级:决定一个中断能不能打断另一个正在执行的中断。数值越小,优先级越高。
  • 子优先级:当两个中断的抢占优先级相同时,谁先执行。数值越小,优先级越高。

说白了,抢占优先级管的是“能不能插队”,子优先级管的是“排队时谁站前面”。

核心规则:

  • 高抢占优先级的中断,可以打断低抢占优先级的中断。
  • 抢占优先级相同的中断,不会互相打断。谁先来谁先执行,如果同时来,看子优先级。
  • 子优先级只在抢占优先级相同时才起作用。

我在项目中遇到过一种情况:两个外设的中断,一个要实时响应(比如定时器),一个数据量大但可以等(比如UART接收)。这时候我就把定时器的抢占优先级设高,UART的设低。这样定时器来了就能打断UART,保证实时性。

4.2 优先级分组

那问题来了:一个32位的中断优先级寄存器,到底用多少位来表示抢占优先级,多少位表示子优先级?

这就是优先级分组要干的事。

以Cortex-M内核为例,它提供了8种分组方式(0~7)。但常用的其实就几种。我一般用分组2或分组3,因为这样分配比较均衡。

分组 抢占优先级位数 子优先级位数 抢占优先级取值范围 子优先级取值范围
0 0 4 无 0~15
1 1 3 0~1 0~7
2 2 2 0~3 0~3
3 3 1 0~7 0~1
4 4 0 0~15 无

我的建议:如果你不确定用哪个分组,先用分组2。4级抢占、4级子优先级,大部分场景都够用。我自己的项目里,80%的情况都用分组2。

4.3 优先级配置实战

光说不练假把式。咱们直接上代码。

以STM32为例,配置中断优先级分三步走:

  1. 设置优先级分组——系统启动时做一次就行。
  2. 配置每个中断的抢占优先级和子优先级。
  3. 使能中断。

4.3.1 设置优先级分组

// 使用分组2:4位抢占优先级,4位子优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

这行代码一般在main函数开头调用。注意,整个系统只能调用一次。如果你中途改了分组,之前配好的优先级全乱套了。

警告:我曾经在一个项目里,因为调试需要,在初始化之后又调了一次PriorityGroupConfig。结果所有中断优先级都变了,系统直接死机。后来花了半天才找到原因。所以记住:分组只配一次,配完就别动了。

4.3.2 配置具体中断的优先级

假设我们有两个中断:定时器中断和UART中断。

// 定时器中断:抢占优先级0,子优先级0(最高优先级)
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

// UART中断:抢占优先级1,子优先级0
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

这样配置之后,定时器中断可以打断UART中断。如果两个中断同时来,定时器先执行。

4.3.3 验证配置

配完了怎么验证?我一般用两种方法:

  • 读寄存器:直接读NVIC的优先级寄存器,看看值对不对。
  • 实际测试:在两个中断服务函数里加GPIO翻转,用示波器看波形。高优先级的中断来了,低优先级的波形是不是被“打断”了。

小技巧:调试的时候,可以在中断服务函数里加个计数器。每次进入中断就加1。如果高优先级中断频繁触发,低优先级中断的计数器增长就会变慢。这能直观地看到优先级的效果。

4.4 优先级分组与中断嵌套的关系

这里有个容易搞混的点:中断嵌套。

只有抢占优先级不同的中断才能嵌套。抢占优先级相同的中断,即使子优先级不同,也不会嵌套。它们会排队,一个一个执行。

举个例子:

  • 中断A:抢占0,子优先级0
  • 中断B:抢占0,子优先级1
  • 中断C:抢占1,子优先级0

如果A正在执行,C来了,C会打断A(因为C的抢占优先级更高)。但如果B来了,B不会打断A(因为抢占优先级相同),B会等A执行完再执行。

嗯,这个逻辑一定要想清楚。我在项目中见过有人把子优先级当成抢占优先级用,结果该打断的没打断,不该打断的反而打断了。

4.5 常见问题与避坑指南

我曾经踩过的坑:

  • 坑1:分组没配就配中断优先级。结果优先级值被解释成别的含义,中断行为完全不可控。
  • 坑2:所有中断都用同一个抢占优先级。这样就没有嵌套了,高实时性的中断会被堵住。
  • 坑3:抢占优先级数值设反了。记住,数值越小优先级越高。0是最高,不是最低。
  • 坑4:中断服务函数里做太多事。优先级再高,也架不住你在中断里跑个几百毫秒的循环。高优先级中断的服务函数一定要短小精悍。

好了,中断优先级这块就讲到这里。配置其实不复杂,关键是要理解抢占和子优先级的区别,以及分组的作用。配的时候细心点,测试的时候多验证,基本不会出问题。

中断优先级配置核心逻辑 中断优先级配置 抢占优先级 子优先级 优先级分组 决定能否打断 数值越小优先级越高 同抢占时决定顺序 不产生嵌套 系统启动时配置一次 配置顺序:分组 → 抢占优先级 → 子优先级 → 使能

可可精品资料库