STM32 进阶封神之路(七):中断核心原理 + NVIC 深度解析 —— 从概念到寄存器配置(面试重点)
上一篇我们掌握了库函数的深度应用与工程优化,这一篇将进入 STM32 的核心进阶知识点 ——中断系统。中断是嵌入式开发中实现 “异步事件响应” 的关键,比如按键触发、串口接收数据、定时器溢出等场景,都离不开中断的支持。而 NVIC(嵌套向量中断控制器)作为 STM32 中断系统的 “指挥官”,直接决定了中断的响应优先级和执行逻辑。
本文基于实战资料,从中断概念、NVIC 架构,到优先级分组、寄存器配置,手把手带你吃透中断系统的底层逻辑,为下一篇外部中断实战(按键检测)打下坚实基础,同时覆盖面试高频考点,让你不仅 “会用中断”,更能 “讲清原理”!
一、中断核心认知:为什么它是嵌入式的 “必备技能”?
在学习 NVIC 之前,必须先明确 “中断” 的核心价值 —— 解决 “轮询占用 CPU” 的痛点,让系统能高效响应异步事件。
1. 中断的本质与核心作用
(1)什么是中断?
中断是 CPU 在执行正常程序时,被外部或内部事件触发后,暂停当前程序,转而去执行该事件对应的 “中断服务函数(ISR)”,执行完成后再回到原程序断点继续执行的机制。
简单类比:你正在看书(正常程序),门铃响了(中断事件),你放下书去开门(执行中断服务函数),开门后回到书中的页码继续阅读(返回原程序)—— 这个过程就是中断。
(2)中断的核心价值
- 提升 CPU 效率:无需轮询检测事件(如按键是否按下),CPU 可专注执行主程序,事件触发时再响应;
- 实时性强:异步事件(如传感器数据就绪)可被即时响应,避免延迟;
- 简化代码:将不同事件的处理逻辑拆分到独立的中断服务函数,代码结构更清晰。
(3)STM32 中断的典型应用场景
- 外部事件:按键按下(外部中断)、传感器触发;
- 通信事件:串口接收数据(USART 中断)、I2C/SPI 数据传输完成;
- 定时事件:定时器溢出(TIM 中断)、PWM 周期结束;
- 异常事件:内存访问错误、总线故障(系统异常)。
2. 中断的执行过程(必掌握)
STM32 的中断执行遵循严格的流程,共 6 个步骤,缺一不可:
- 中断触发:外部 / 内部事件发生(如按键按下→GPIO 电平变化),触发对应的中断请求;
- 请求响应:NVIC 检测到中断请求,判断该中断是否被使能、优先级是否足够;
- 断点保存:CPU 暂停当前程序,保存断点信息(PC 指针、寄存器值)到栈中;
- 跳转执行:根据中断向量表,跳转到对应的中断服务函数(ISR);
- 服务执行:执行中断服务函数的核心逻辑(如读取按键状态、处理串口数据);
- 断点恢复:执行完成后,从栈中恢复断点信息,回到原程序继续执行。
关键注意:中断服务函数执行时间应尽量短(避免阻塞主程序),禁止在 ISR 中调用延时函数、复杂运算或不可重入函数(如printf)。
3. 中断相关专业术语(面试高频)
- 中断源:产生中断请求的设备 / 事件(如 EXTI0、USART1、TIM1);
- 中断向量:每个中断源对应的唯一标识,用于 CPU 识别中断类型;
- 中断向量表:存储所有中断向量的地址表,CPU 通过该表找到中断服务函数入口;
- 中断优先级:多个中断同时请求时,CPU 优先响应的顺序;
- 中断使能 / 失能:允许 / 禁止某个中断请求被 CPU 响应;
- 中断挂起 / 解挂:暂停 / 恢复某个中断的响应(即使中断使能,挂起后也不会响应)。
二、NVIC 深度解析:STM32 中断的 “指挥官”
NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)是 Cortex-M3 内核的核心组件,集成在 STM32 芯片中,负责管理所有中断请求,核心功能是 “中断优先级判断” 和 “中断嵌套控制”。
1. NVIC 的核心作用与位置
(1)核心作用
- 管理中断请求:接收来自外设的中断请求,判断请求的有效性;
- 优先级裁决:多个中断同时请求时,按优先级高低排序,优先响应高优先级中断;
- 中断嵌套控制:高优先级中断可打断低优先级中断的执行,执行完成后回到低优先级中断;
- 中断使能 / 失能:控制单个中断的开启或关闭;
- 中断挂起 / 解挂:临时暂停或恢复中断响应。
(2)NVIC 在 STM32 中的位置
STM32 的硬件架构中,NVIC 位于 Cortex-M3 内核与外设之间,架构关系如下:
plaintext
外设(GPIO/USART/TIM)→ 中断请求线 → NVIC → Cortex-M3 CPU
所有外设的中断请求必须经过 NVIC 裁决后,才能被 CPU 响应。
2. STM32 中断的分类(核心区别)
STM32 的中断分为两大类,核心区别在于是否受 NVIC 管理:
表格
| 中断类型 | 包含内容 | 管理方式 | 典型示例 |
|---|---|---|---|
| 系统异常 | 复位、NMI、硬 fault、内存管理 fault 等 | 部分受 NVIC 管理,优先级固定或可配置 | 复位异常、硬 fault 异常 |
| 外部中断 | 外设中断(GPIO、USART、TIM、EXTI 等) | 完全受 NVIC 管理,优先级可自由配置 | EXTI0 中断、USART1 中断、TIM2 中断 |
关键区别:系统异常优先级通常高于外部中断,部分异常(如复位)不可屏蔽,而外部中断可通过 NVIC 灵活配置使能 / 失能、优先级。
3. NVIC 的核心寄存器(底层配置关键)
NVIC 通过一系列寄存器实现中断管理,新手需重点掌握 4 类核心寄存器,理解其作用即可,无需记忆地址(库函数已封装):
表格
| 寄存器类别 | 核心寄存器 | 作用 |
|---|---|---|
| 优先级配置 | NVIC_IPRx(中断优先级寄存器) | 配置每个中断的抢占优先级和响应优先级 |
| 中断使能 | NVIC_ISERx(中断使能寄存器) | 使能指定中断(置 1 = 使能) |
| 中断失能 | NVIC_ICERx(中断失能寄存器) | 失能指定中断(置 1 = 失能) |
| 中断挂起 | NVIC_ISPRx(中断挂起寄存器) | 挂起指定中断(置 1 = 挂起) |
| 中断解挂 | NVIC_ICPRx(中断解挂寄存器) | 解挂指定中断(置 1 = 解挂) |
(1)NVIC_IPRx:中断优先级配置寄存器
每个外部中断对应一个 NVIC_IPRx 寄存器(8 位),但 STM32 仅使用高 4 位(bit7~bit4)配置优先级,低 4 位保留未使用。
高 4 位分为两部分,用于配置 “抢占优先级” 和 “响应优先级”:
- 抢占优先级(Preemption Priority):决定中断是否能嵌套(高抢占优先级可打断低抢占优先级中断);
- 响应优先级(Sub Priority):抢占优先级相同时,决定中断响应的先后顺序(无嵌套关系)。
示例:若某中断的 NVIC_IPRx 寄存器高 4 位为0100(二进制),则需结合优先级分组判断抢占 / 响应优先级(下文详细讲解)。
(2)NVIC_ISERx/NVIC_ICERx:中断使能 / 失能
- NVIC_ISERx:共 8 个寄存器(ISER0~ISER7),每个寄存器 32 位,对应 32 个中断请求位;
- 使能中断:将对应位设为 1(如使能 EXTI0 中断→ISER0 的 bit6 置 1);
- 失能中断:将 NVIC_ICERx 的对应位设为 1(注意:失能需操作 ICERx,而非 ISERx 清 0)。
4. 优先级分组:抢占与响应优先级的划分规则
STM32 通过优先级分组决定 NVIC_IPRx 的高 4 位如何分配给 “抢占优先级” 和 “响应优先级”,分组由SCB_AIRCR寄存器的 bit10~bit8(PRIGROUP)控制,共 5 种分组模式:
表格
| 优先级分组 | PRIGROUP 位值 | 抢占优先级位数 | 响应优先级位数 | 抢占优先级等级数 | 响应优先级等级数 |
|---|---|---|---|---|---|
| 分组 0 | 000 | 0 | 4 | 1(无抢占) | 16 |
| 分组 1 | 001 | 1 | 3 | 2 | 8 |
| 分组 2 | 010 | 2 | 2 | 4 | 4 |
| 分组 3 | 011 | 3 | 1 | 8 | 2 |
| 分组 4 | 100 | 4 | 0 | 16 | 1(无响应优先级) |
分组规则与示例(必掌握)
- 优先级分组是全局配置,整个系统仅能选择一种分组;
- 抢占优先级等级数 = 2^ 抢占位数(如分组 2→2^2=4 级);
- 响应优先级等级数 = 2^ 响应位数(如分组 2→2^2=4 级);
- 优先级数值越小,优先级越高(如抢占优先级 0 > 抢占优先级 1)。
实战示例:分组 2 下的优先级配置
若系统配置为分组 2(抢占 2 位,响应 2 位),某中断的 NVIC_IPRx 高 4 位为0101(二进制):
- 抢占优先级:高 2 位
01→ 等级 1; - 响应优先级:低 2 位
01→ 等级 1; - 优先级解读:该中断的抢占优先级为 1,响应优先级为 1,可被抢占优先级 0 的中断打断。
5. 中断嵌套原理(分组 3 示例)
假设系统配置为分组 3(抢占 3 位,响应 1 位),存在 3 个中断:
- 中断 A:抢占优先级 0,响应优先级 0;
- 中断 B:抢占优先级 1,响应优先级 0;
- 中断 C:抢占优先级 1,响应优先级 1。
中断嵌套执行流程:
- 主程序执行中,中断 B 触发→CPU 响应中断 B,执行 ISR_B;
- 执行 ISR_B 时,中断 A 触发→中断 A 抢占优先级更高,打断 ISR_B,执行 ISR_A;
- ISR_A 执行完成→恢复执行 ISR_B 剩余逻辑;
- ISR_B 执行完成→回到主程序;
- 主程序执行中,中断 B 和中断 C 同时触发→抢占优先级相同,响应优先级 0 的中断 B 先执行。
关键结论:仅抢占优先级不同时,才会发生中断嵌套;响应优先级仅影响同抢占优先级中断的响应顺序,无嵌套关系。
三、NVIC 配置流程:从分组到中断使能(库函数 + 寄存器)
NVIC 的配置是中断使用的前提,核心流程为 “配置优先级分组→配置中断优先级→使能中断”,以下以 “使能 EXTI0 中断” 为例,拆解库函数和寄存器两种配置方式。
1. 优先级分组配置(全局配置)
优先级分组需在主程序初始化时配置,且仅配置一次(全局生效)。
(1)库函数配置
STM32 标准外设库提供NVIC_PriorityGroupConfig函数,直接指定分组模式:
c
运行
// 配置为优先级分组2(抢占2位,响应2位)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
(2)寄存器配置
直接操作SCB_AIRCR寄存器(地址:0xE000ED0C),需注意该寄存器写操作需满足 “密钥校验”(bit31~bit16 必须为 0x05FA):
c
运行
// 配置为分组2(PRIGROUP=010)
SCB->AIRCR = (SCB->AIRCR & ~(0x07 << 8)) | (0x05FA << 16) | (0x02 << 8);
0x05FA << 16:密钥,必须写入该值才能修改分组;0x02 << 8:PRIGROUP=010→分组 2。
2. 中断优先级配置(单个中断)
配置某中断的抢占优先级和响应优先级,需结合优先级分组。
(1)库函数配置
使用NVIC_Init函数,通过NVIC_InitTypeDef结构体配置:
c
运行
NVIC_InitTypeDef NVIC_InitStruct;
// 1. 选择中断源(EXTI0中断)
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
// 2. 配置抢占优先级(分组2下,抢占优先级范围0~3)
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
// 3. 配置响应优先级(分组2下,响应优先级范围0~3)
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
// 4. 使能该中断
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
// 5. 初始化NVIC
NVIC_Init(&NVIC_InitStruct);
(2)寄存器配置(分组 2 下)
EXTI0 中断对应的 NVIC_IPRx 寄存器为NVIC->IP[6](EXTI0 的中断编号为 6),需配置高 4 位为0100(抢占 1,响应 0):
c
运行
// 分组2:抢占2位(bit7~bit6),响应2位(bit5~bit4)
// 抢占1→01,响应0→00,高4位=0100(0x40)
NVIC->IP[6] = 0x40;
// 使能EXTI0中断(NVIC_ISER0的bit6置1)
NVIC->ISER[0] |= (1 << 6);
3. NVIC 配置关键注意事项
- 优先级分组全局唯一:一旦配置,所有中断都遵循该分组规则,禁止中途修改;
- 中断编号对应:每个中断源有唯一的中断编号(如 EXTI0=6、USART1=37),需参考 STM32 手册的 “中断向量表”;
- 优先级数值越小越高:抢占优先级 0 是最高优先级,响应优先级同理;
- 未使用中断的配置:无需配置未使用的中断,保持默认状态即可。
四、中断向量表:CPU 找到 ISR 的 “地图”
中断向量表是 STM32 中断系统的核心,存储了所有中断服务函数的入口地址,CPU 通过中断向量表快速定位 ISR。
1. 中断向量表的本质与位置
- 本质:一段连续的内存空间,每个中断源对应一个 4 字节的入口地址(32 位 CPU);
- 默认位置:STM32 复位后,中断向量表默认位于 0x08000000(Flash 起始地址);
- 向量表偏移:可通过
SCB->VTOR寄存器修改向量表地址(如 bootloader 跳转后重新配置)。
2. 中断向量表的结构(简化版)
STM32F103 的中断向量表前 16 项为系统异常,后续为外部中断,简化结构如下:
表格
| 地址偏移 | 中断向量 | 中断服务函数 |
|---|---|---|
| 0x00 | 栈顶指针 | - |
| 0x04 | 复位向量 | Reset_Handler |
| 0x08 | NMI 向量 | NMI_Handler |
| 0x0C | 硬 fault 向量 | HardFault_Handler |
| ... | ... | ... |
| 0x18 | EXTI0 向量 | EXTI0_IRQHandler |
| 0x1C | EXTI1 向量 | EXTI1_IRQHandler |
| ... | ... | ... |
| 0x90 | USART1 向量 | USART1_IRQHandler |
关键逻辑:中断触发后,NVIC 会告知 CPU 中断对应的向量编号,CPU 根据向量表偏移 + 编号 ×4,找到 ISR 入口地址,跳转执行。
3. 中断服务函数的命名规则
中断服务函数的名称必须与中断向量表中的名称一致(如 EXTI0 中断→EXTI0_IRQHandler),否则 CPU 无法找到入口地址,导致程序跑飞。
标准外设库的启动文件(如startup_stm32f10x_md.s)中,已声明所有中断服务函数的弱定义(WEAK),用户只需在代码中重新实现即可:
asm
; 启动文件中的弱定义
WEAK EXTI0_IRQHandler
EXPORT EXTI0_IRQHandler
EXTI0_IRQHandler
B . ; 弱定义默认跳转至自身(死循环)
用户实现 ISR 时,需覆盖弱定义:
c
运行
// 用户实现EXTI0中断服务函数
void EXTI0_IRQHandler(void) {
// 中断处理逻辑
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
// 处理按键按下事件
EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志位
}
}
五、NVIC 相关面试高频题(附标准答案)
1. 问题 1:STM32 的 NVIC 是什么?核心作用是什么?
标准答案:
NVIC 是 Cortex-M3 内核的嵌套向量中断控制器,集成在 STM32 芯片中,核心作用是:
- 管理所有外部中断请求,判断中断的有效性;
- 实现中断优先级裁决(多个中断同时请求时,优先响应高优先级中断);
- 支持中断嵌套(高抢占优先级中断可打断低抢占优先级中断);
- 控制中断的使能 / 失能、挂起 / 解挂。
2. 问题 2:STM32 的中断优先级分为哪两种?它们的区别是什么?
标准答案:
分为抢占优先级和响应优先级:
- 抢占优先级:决定中断是否能嵌套,高抢占优先级中断可打断正在执行的低抢占优先级中断;
- 响应优先级:仅当两个中断的抢占优先级相同时,决定中断响应的先后顺序,无嵌套关系;
- 优先级数值越小,优先级越高。
3. 问题 3:STM32 的优先级分组有几种?如何配置?
标准答案:
- 共 5 种分组,由
SCB_AIRCR寄存器的 PRIGROUP 位(bit10~bit8)控制,分组决定抢占优先级和响应优先级的位数分配; - 配置方式:库函数通过
NVIC_PriorityGroupConfig函数(如NVIC_PriorityGroup_2配置分组 2),寄存器方式需操作SCB_AIRCR(需写入密钥 0x05FA); - 分组是全局配置,整个系统仅能选择一种。
4. 问题 4:什么是中断向量表?作用是什么?
标准答案:
- 中断向量表是一段连续的内存空间,存储所有中断服务函数的入口地址;
- 作用:CPU 收到中断请求后,通过中断向量表快速定位对应的中断服务函数,跳转执行;
- 默认位置:0x08000000(Flash 起始地址),可通过
SCB->VTOR修改。
5. 问题 5:中断服务函数的命名有什么要求?为什么?
标准答案:
- 要求:中断服务函数的名称必须与中断向量表中的弱定义名称一致(如 EXTI0 中断→
EXTI0_IRQHandler); - 原因:启动文件中已为每个中断声明了弱定义的 ISR 入口,若用户定义的函数名称不一致,编译器会优先使用弱定义(默认死循环),导致中断无法正常响应。
六、NVIC 配置避坑指南(10 + 高频错误)
-
优先级分组中途修改→中断优先级混乱:
- 现象:部分中断响应顺序异常,嵌套逻辑失效;
- 解决:优先级分组仅在主程序初始化时配置一次,禁止中途修改。
-
中断编号错误→配置无效:
- 现象:中断使能后,触发事件无响应;
- 解决:参考 STM32 手册的 “中断向量表”,确认中断源对应的中断编号(如 EXTI0=6,USART1=37)。
-
优先级数值配置错误→优先级颠倒:
- 现象:低数值优先级中断未优先响应;
- 解决:STM32 中断优先级数值越小,优先级越高(如抢占优先级 0 > 抢占优先级 1)。
-
中断使能时操作 NVIC_ISERx 清 0→失能失败:
- 现象:中断使能后无法关闭;
- 解决:中断失能必须操作
NVIC_ICERx寄存器(置 1 失能),NVIC_ISERx仅用于使能(置 1 使能)。
-
未清除中断标志位→中断重复触发:
- 现象:中断服务函数反复执行,即使事件已处理;
- 解决:中断服务函数中必须清除中断标志位(如
EXTI_ClearITPendingBit),否则 NVIC 会认为中断未处理,持续请求。
-
优先级分组与抢占 / 响应优先级配置不匹配→优先级错误:
- 现象:分组 2 下配置抢占优先级 4(超出 0~3 范围);
- 解决:根据分组确定优先级范围(如分组 2→抢占 0
3,响应 03),避免配置超出范围。
七、总结:NVIC 的核心要点与进阶铺垫
1. 核心要点回顾
- NVIC 是 STM32 中断的 “指挥官”,负责中断优先级裁决和嵌套控制;
- 中断优先级分为抢占和响应优先级,由优先级分组决定位数分配;
- NVIC 配置流程:全局分组→单个中断优先级配置→中断使能;
- 中断向量表是 CPU 找到 ISR 的关键,ISR 名称必须与向量表一致。
2. 下一篇铺垫
掌握 NVIC 的核心原理后,下一篇我们将聚焦外部中断(EXTI)实战—— 结合 GPIO 和 NVIC,实现 “外部中断下的按键检测”,覆盖 EXTI 配置、中断服务函数编写、软件消抖等实战要点,让你真正将中断原理落地到项目中!