STM32 进阶封神之路(七):中断核心原理 + NVIC 深度解析 —— 从概念到寄存器配置(面试重点)

0 阅读17分钟

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 个步骤,缺一不可:

  1. 中断触发:外部 / 内部事件发生(如按键按下→GPIO 电平变化),触发对应的中断请求;
  2. 请求响应:NVIC 检测到中断请求,判断该中断是否被使能、优先级是否足够;
  3. 断点保存:CPU 暂停当前程序,保存断点信息(PC 指针、寄存器值)到栈中;
  4. 跳转执行:根据中断向量表,跳转到对应的中断服务函数(ISR);
  5. 服务执行:执行中断服务函数的核心逻辑(如读取按键状态、处理串口数据);
  6. 断点恢复:执行完成后,从栈中恢复断点信息,回到原程序继续执行。

关键注意:中断服务函数执行时间应尽量短(避免阻塞主程序),禁止在 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 位值抢占优先级位数响应优先级位数抢占优先级等级数响应优先级等级数
分组 0000041(无抢占)16
分组 10011328
分组 20102244
分组 30113182
分组 410040161(无响应优先级)
分组规则与示例(必掌握)
  • 优先级分组是全局配置,整个系统仅能选择一种分组;
  • 抢占优先级等级数 = 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。

中断嵌套执行流程:

  1. 主程序执行中,中断 B 触发→CPU 响应中断 B,执行 ISR_B;
  2. 执行 ISR_B 时,中断 A 触发→中断 A 抢占优先级更高,打断 ISR_B,执行 ISR_A;
  3. ISR_A 执行完成→恢复执行 ISR_B 剩余逻辑;
  4. ISR_B 执行完成→回到主程序;
  5. 主程序执行中,中断 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
0x08NMI 向量NMI_Handler
0x0C硬 fault 向量HardFault_Handler
.........
0x18EXTI0 向量EXTI0_IRQHandler
0x1CEXTI1 向量EXTI1_IRQHandler
.........
0x90USART1 向量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 + 高频错误)

  1. 优先级分组中途修改→中断优先级混乱

    • 现象:部分中断响应顺序异常,嵌套逻辑失效;
    • 解决:优先级分组仅在主程序初始化时配置一次,禁止中途修改。
  2. 中断编号错误→配置无效

    • 现象:中断使能后,触发事件无响应;
    • 解决:参考 STM32 手册的 “中断向量表”,确认中断源对应的中断编号(如 EXTI0=6,USART1=37)。
  3. 优先级数值配置错误→优先级颠倒

    • 现象:低数值优先级中断未优先响应;
    • 解决:STM32 中断优先级数值越小,优先级越高(如抢占优先级 0 > 抢占优先级 1)。
  4. 中断使能时操作 NVIC_ISERx 清 0→失能失败

    • 现象:中断使能后无法关闭;
    • 解决:中断失能必须操作NVIC_ICERx寄存器(置 1 失能),NVIC_ISERx仅用于使能(置 1 使能)。
  5. 未清除中断标志位→中断重复触发

    • 现象:中断服务函数反复执行,即使事件已处理;
    • 解决:中断服务函数中必须清除中断标志位(如EXTI_ClearITPendingBit),否则 NVIC 会认为中断未处理,持续请求。
  6. 优先级分组与抢占 / 响应优先级配置不匹配→优先级错误

    • 现象:分组 2 下配置抢占优先级 4(超出 0~3 范围);
    • 解决:根据分组确定优先级范围(如分组 2→抢占 03,响应 03),避免配置超出范围。

七、总结:NVIC 的核心要点与进阶铺垫

1. 核心要点回顾

  • NVIC 是 STM32 中断的 “指挥官”,负责中断优先级裁决和嵌套控制;
  • 中断优先级分为抢占和响应优先级,由优先级分组决定位数分配;
  • NVIC 配置流程:全局分组→单个中断优先级配置→中断使能;
  • 中断向量表是 CPU 找到 ISR 的关键,ISR 名称必须与向量表一致。

2. 下一篇铺垫

掌握 NVIC 的核心原理后,下一篇我们将聚焦外部中断(EXTI)实战—— 结合 GPIO 和 NVIC,实现 “外部中断下的按键检测”,覆盖 EXTI 配置、中断服务函数编写、软件消抖等实战要点,让你真正将中断原理落地到项目中!