2. NVIC配置实战:STM32 NVIC控制器详解、中断优先级分组、中断使能与除能

2.1 NVIC是个什么玩意儿?

NVIC,全称Nested Vectored Interrupt Controller,嵌套向量中断控制器。名字挺长,说白了就是ARM Cortex-M内核里专门管中断的那个硬件模块。

我刚开始用STM32的时候,总觉得NVIC就是个开关——打开中断,关掉中断,完事。后来踩了坑才发现,这玩意儿远比我想象的复杂。它不光管中断的开关,还管优先级、抢占、响应顺序,甚至能决定你的系统实时性能不能达标。

STM32的NVIC支持最多240个中断(具体看芯片型号),每个中断都可以独立配置优先级。嗯,这里要注意:不是所有中断都能同时用,具体看你的芯片型号和参考手册。

2.2 中断优先级分组——你必须搞懂的核心概念

中断优先级分组,这是NVIC配置里最容易让人迷糊的地方。我见过不少新手,甚至有些老手,在这里翻车。

STM32把优先级分成了两部分:抢占优先级和响应优先级(也叫子优先级)。

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

你想想看,如果两个中断的抢占优先级不同,高抢占优先级的中断可以打断低抢占优先级的中断。但如果抢占优先级相同,那就看响应优先级了,而且它们之间不能互相打断。

STM32用NVIC_PriorityGroupConfig()这个函数来配置分组。一共有5种分组方式:

分组 抢占优先级位数 响应优先级位数 说明
NVIC_PriorityGroup_0 0位 4位 只有响应优先级,没有抢占
NVIC_PriorityGroup_1 1位 3位 2级抢占,8级响应
NVIC_PriorityGroup_2 2位 2位 4级抢占,4级响应
NVIC_PriorityGroup_3 3位 1位 8级抢占,2级响应
NVIC_PriorityGroup_4 4位 0位 16级抢占,没有响应

重要提醒:整个系统只能调用一次NVIC_PriorityGroupConfig()。如果你在多个地方调用,后调用的会覆盖前面的设置,导致优先级混乱。我见过有人因为这个bug排查了整整两天。

2.3 中断使能与除能——别搞反了

中断使能,就是让某个中断源可以响应。除能,就是关掉它。STM32用NVIC_Init()函数来完成这个操作。

我个人习惯在初始化外设之前就把NVIC配置好。为什么呢?因为如果你先初始化外设,外设可能已经产生了中断标志,但NVIC还没配置好,这个中断就丢了。

来看一个实际例子,配置USART1的中断:

// 第一步:配置优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  // 4级抢占,4级响应

// 第二步:填充NVIC结构体
NVIC_InitTypeDef NVIC_InitStructure;

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);

代码很简单,对吧?但有几个坑我得提醒你:

  • 优先级数值越小,优先级越高。这个和直觉是反的。我刚开始总写成大数值,结果中断一直不响应。
  • 使能用ENABLE,除能用DISABLE。别写反了。我曾经在调试时把DISABLE写成了ENABLE,中断死活关不掉。
  • 中断通道名称要查手册。不同型号的STM32,中断通道名可能不一样。比如USART1在F103上是USART1_IRQn,在F4上可能也是,但有些外设就不一样了。

小技巧:如果你不确定中断通道名,可以在STM32的头文件里搜一下。比如stm32f10x.h里定义了所有中断通道的枚举。我每次换芯片型号,第一件事就是去翻这个头文件。

2.4 中断优先级配置的实战经验

在实际项目中,中断优先级的分配是个技术活。我总结了几条经验:

  1. 时间敏感的中断给高优先级。比如定时器中断、DMA传输完成中断,这些需要快速响应。
  2. 数据量大的中断给低优先级。比如串口接收中断,如果数据量很大,频繁打断其他中断会影响系统性能。
  3. 关键安全功能的中断给最高优先级。比如看门狗、电源监测,这些绝对不能被打断。

我曾经在一个电机控制项目里,把PWM中断的优先级设得太低,结果被串口中断频繁打断,电机控制周期不稳定,电机嗡嗡响。后来把PWM中断优先级提到最高,问题就解决了。

警告:不要把多个中断设成完全相同的优先级(抢占和响应都一样)。如果它们同时触发,NVIC会按照硬件向量号来决定谁先执行,这个顺序是不可控的。我建议至少让抢占优先级不同,或者响应优先级不同。

2.5 中断使能与除能的底层原理

NVIC使能中断,本质上是操作两个寄存器:ISER(中断使能设置寄存器)和ICER(中断除能清除寄存器)。

你写1到ISER的对应位,就使能了该中断。写1到ICER的对应位,就除能了该中断。注意,是写1,不是写0。写0没效果。

为什么要设计成写1有效?因为这样可以用一条指令完成操作,不需要读-改-写,避免了中断竞争。嗯,这是硬件设计上的一个小巧思。

看一个手动操作寄存器的例子:

// 使能USART1中断(中断号37)
NVIC->ISER[1] |= (1 << (37 - 32));  // ISER[1]对应中断号32-63

// 除能USART1中断
NVIC->ICER[1] |= (1 << (37 - 32));

当然,实际开发中我们直接用NVIC_Init()就行了,不用自己操作寄存器。但理解底层原理,能帮你更好地理解NVIC的工作方式。

2.6 中断优先级分组实战流程图

下面这张图展示了NVIC配置的完整流程,从分组到使能,每一步都标清楚了:

NVIC配置实战流程图 步骤1:选择优先级分组 NVIC_PriorityGroupConfig() 步骤2:确定抢占优先级和响应优先级 根据系统实时性需求分配 步骤3:填充NVIC_InitTypeDef结构体 指定中断通道、优先级、使能/除能 步骤4:调用NVIC_Init() 将配置写入NVIC寄存器 步骤5:使能外设中断(如USART_ITConfig) 外设级中断使能,与NVIC级配合 ✅ 中断配置完成

2.7 常见问题与避坑指南

问题1:中断配置了,但就是不触发?

检查三件事:第一,NVIC使能了吗?第二,外设中断使能了吗?第三,中断标志位清除了吗?我曾经漏了第三步,中断触发一次后就再也不进了。

问题2:中断优先级混乱,高优先级中断被低优先级打断?

检查分组配置。如果你用了NVIC_PriorityGroup_0,所有中断都没有抢占优先级,自然不能互相打断。我建议大多数项目用NVIC_PriorityGroup_2,4级抢占够用了。

问题3:中断响应太慢?

检查中断服务函数里是不是做了太多事。中断服务函数应该短小精悍,只做必要的事,比如设置标志位、清除中断标志。耗时的操作放到主循环里做。

核心总结:NVIC配置就三步——分组、填结构体、调用初始化。但每一步都有细节要注意。分组只能调一次,优先级数值越小优先级越高,使能和除能别搞反。记住这些,你的中断系统就能稳定运行。


可可精品资料库