2. NVIC(嵌套向量中断控制器)详解

NVIC,全称嵌套向量中断控制器。说白了,它就是 Cortex-M 内核里专门管中断的“大脑”。

我刚开始接触 STM32 时,总觉得中断就是“来了就跳进去,执行完就回来”。后来发现,事情远没那么简单。多个中断同时来了怎么办?高优先级的能不能打断低优先级的?这些,都是 NVIC 说了算。

这一节,我们就把它彻底讲透。

2.1 NVIC 寄存器组

NVIC 不是靠魔法工作的。它有一堆寄存器,我们通过配置这些寄存器来控制中断行为。

常用的寄存器有这些:

寄存器 功能
ISER(中断使能寄存器) 使能某个中断
ICER(中断除能寄存器) 除能某个中断
ISPR(中断挂起寄存器) 手动挂起一个中断(软件触发)
ICPR(中断激活寄存器) 清除挂起状态
IABR(中断激活状态寄存器) 只读,查看当前哪个中断正在执行
IPR(中断优先级寄存器) 设置每个中断的优先级

每个寄存器都是 32 位的。但注意,STM32 只用了其中的一部分位。比如 ISER,每个位对应一个中断号。写 1 使能,写 0 无效。

核心要点:NVIC 寄存器操作,本质上就是“位操作”。你不需要记住每个寄存器的地址,用 CMSIS 库函数就行。

2.2 中断优先级分组

这是很多人容易搞混的地方。STM32 的中断优先级,分为抢占优先级子优先级

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

那怎么分配这两种优先级的位数呢?靠的是 NVIC_PriorityGroupConfig() 函数。

STM32 提供了 5 种分组方式:

分组 抢占优先级位数 子优先级位数 说明
Group 0 0 位 4 位 只有子优先级,没有抢占
Group 1 1 位 3 位 2 级抢占,8 级子优先级
Group 2 2 位 2 位 4 级抢占,4 级子优先级
Group 3 3 位 1 位 8 级抢占,2 级子优先级
Group 4 4 位 0 位 16 级抢占,没有子优先级

我的习惯:项目里我一般用 Group 2。4 级抢占够用了,4 级子优先级也能应付大部分场景。你想想看,如果分组太细,反而容易把自己绕晕。

2.3 中断使能与除能

使能中断,就是告诉 NVIC:“这个中断我允许它进来”。

代码很简单:

// 使能 USART1 中断(中断号 37)
NVIC_EnableIRQ(USART1_IRQn);

// 除能 USART1 中断
NVIC_DisableIRQ(USART1_IRQn);

这两个函数背后,操作的就是 ISER 和 ICER 寄存器。

嗯,这里要注意:使能中断之前,一定要先配置好优先级。否则中断来了,优先级是乱的,系统行为不可预测。

我曾经踩过的坑:有一次调试,中断死活不响应。查了半天,发现是 NVIC 使能了,但外设本身的中断没开。比如串口,你不仅要开 NVIC 的 USART1 中断,还要在 USART 的 CR1 寄存器里把 RXNEIE 位置 1。两个开关,缺一不可。

2.4 中断挂起与激活

挂起,就是中断请求已经来了,但还没被 CPU 响应。你可以把它理解为“排队中”。

激活,就是 CPU 正在执行这个中断服务函数。

我们有时候需要手动操作挂起状态。比如:

// 手动挂起 USART1 中断(软件触发)
NVIC_SetPendingIRQ(USART1_IRQn);

// 清除挂起状态
NVIC_ClearPendingIRQ(USART1_IRQn);

// 查看当前是否处于激活状态
uint32_t active = NVIC_GetActive(USART1_IRQn);

为什么要手动挂起?我遇到过一种场景:测试中断响应时间时,用软件触发中断,比用外部信号更可控。你想想看,这样就能精确测量从触发到进入中断函数的延迟。

另外,清除挂起标志也很重要。如果不清除,同一个中断可能会被重复响应。尤其是在中断服务函数里,一定要在退出前清除挂起位。

避坑指南:我曾经在写一个按键中断程序时,忘了在中断里清除挂起标志。结果按一次键,中断进了十几次。后来用示波器一看,原来是 NVIC 一直在重入。加上 NVIC_ClearPendingIRQ() 后,问题解决。

知识体系总览

下面这张图,帮你把 NVIC 的核心逻辑串起来:

NVIC 核心知识体系 NVIC 嵌套向量中断控制器 寄存器组 优先级分组 使能与除能 挂起与激活 ISER / ICER ISPR / ICPR IABR / IPR 抢占优先级 子优先级 Group 0~4 NVIC_EnableIRQ() NVIC_DisableIRQ() 外设中断也要开 SetPendingIRQ() ClearPendingIRQ() GetActive() 核心原则:先配优先级,再使能中断 中断服务函数里记得清挂起标志

NVIC 这部分内容,说白了就是“配置 + 控制”。配置好优先级分组,设置好每个中断的优先级,然后使能它。剩下的,就交给硬件去处理。

我个人觉得,NVIC 是 STM32 中断体系里最基础也最关键的一环。你把它搞明白了,后面学外部中断、定时器中断、串口中断,都会轻松很多。


公众号:蓝海资料掘金营,微信deep3321