番摊机器人 STM32 嵌套向量中断控制器(NVIC)详解
作者:admin | 分类:番摊机器人 | 浏览:4 | 日期:2026年09月12日NVIC(Nested Vectored Interrupt Controller)是 Cortex-M 内核自带的硬件中断管理单元,不属
于 STM32 的外设。它的职责只有一个:决定哪个中断先被 CPU 响应、哪个中断可以打断正在执行的
中断。理解 NVIC,本质是理解「优先级」这套规则。
---
一、NVIC 在系统中的位置
外设事件 ──→ EXTI/定时器/USART… ──→ 产生中断请求 IRQn
│
▼
NVIC(内核内)
│ 比较优先级
▼
CPU 响应/嵌套/挂起
外设中断源:EXTI 线、定时器更新、串口收发、DMA 完成等,各自有固定编号(IRQn)。
NVIC:接收所有 IRQn 请求,按优先级裁决,把最高优先级的交给 CPU。
系统异常(复位、NMI、HardFault、SysTick、PendSV、SVCall)也由 NVIC 管理,但它们的优先级固定
或单独配置。
---
二、中断向量表
NVIC 能「向量化」处理中断——每个中断源在向量表里有一个固定的入口地址,CPU 响应时直接跳过去,
不需要软件逐个查询中断标志(相比传统 51 的轮询查询,这是质的区别)。
// startup_stm32f1xx.s 中的向量表片段
Vectors:
.word _estack // 0x0000 初始 SP
.word Reset_Handler // 0x0004 复位
.word NMI_Handler // 0x0008
.word HardFault_Handler
...
.word EXTI0_IRQHandler // 各外设中断处理函数入口
.word TIM2_IRQHandler
...
每个中断对应一个 IRQn 编号(枚举类型 IRQn_Type),在配置 NVIC 时就是用它来定位的。
---
三、优先级机制(核心中的核心)
STM32 把中断优先级分成两个维度:
| 维度 | 作用 | 关键行为 |
|------|------|----------|
| 抢占优先级(Preemption Priority) | 决定谁能打断谁 | 抢占优先级数值小的中断,可以打断数值大的正在执行的中断(即嵌套) |
| 子优先级(Sub Priority,又称响应优先级) | 抢占优先级相同时,决定谁先响应 | 只能排队,不能打断同抢占优先级的中断 |
裁决规则(按顺序,命中即停):
抢占优先级数值小者先响应;
抢占优先级相同 → 子优先级数值小者先;
两者都相同 → 看中断编号,编号小的(硬件固定优先级)先。
⚠️ 注意:这里的「优先级高」= 数值小。0 是最高优先级。
---
四、优先级分组(Priority Grouping)
STM32 主流系列(F1/F4/F7/H7)提供 4 位优先级寄存器,共 16 级。这 4 位需要按「分组」在抢占优先级和
子优先级之间分配,通过 AIRCR 寄存器设置。共 5 种分组:
| 分组 | 抢占优先级位 | 子优先级位 | 抢占级别数 | 子优先级级数 | 适用场景 |
|------|:---:|:---:|:---:|:---:|------|
| Group 0 | 0 | 4 | 1 | 16 | 几乎不需要嵌套时 |
| Group 1 | 1 | 3 | 2 | 8 | |
| Group 2 | 2 | 2 | 4 | 4 | 最常用,兼顾嵌套与排队 |
| Group 3 | 3 | 1 | 8 | 2 | |
| Group 4 | 4 | 0 | 16 | 1 | 需要大量可嵌套中断时 |
系列差异:F0/G0(Cortex-M0+)只有 2 位优先级(4 级);F1/F4/F7/H7 都是 4 位(16 级)。分组设置只能
全局设置一次,通常在 main() 开头完成。
---
五、关键寄存器
| 寄存器 | 名称 | 作用 |
|--------|------|------|
| ISERx | 中断使能寄存器 | 打开某个中断(写 1 使能) |
| ICERx | 中断清除使能 | 关闭某个中断 |
| ISPRx | 中断挂起寄存器 | 软件置起挂起标志 |
| ICPRx | 中断清除挂起 | 清除挂起标志 |
| IABRx | 中断活动状态 | 只读,反映当前正在执行的中断 |
| IPx | 中断优先级寄存器 | 每个中断 8 位,STM32 只用高 4 位 |
| AIRCR | 应用中断与复位控制 | 设置优先级分组 |
---
六、配置方法(三种写法对照)
HAL 库(推荐)
// ① 分组(只设一次)
HALNVICSetPriorityGrouping(NVICPRIORITYGROUP2); // 2 抢占 + 2 子
// ② 设置某中断的优先级
HALNVICSetPriority(EXTI0_IRQn, 0, 1); // 抢占优先级 0,子优先级 1
// ③ 使能该中断
HALNVICEnableIRQ(EXTI0_IRQn);
标准库(老项目常见)
NVICPriorityGroupConfig(NVICPriorityGroup_2);
NVICInitTypeDef NVICInitStructure;
NVICInitStructure.NVICIRQChannel = EXTI0_IRQn;
NVICInitStructure.NVICIRQChannelPreemptionPriority = 0;
NVICInitStructure.NVICIRQChannelSubPriority = 1;
NVICInitStructure.NVICIRQChannelCmd = ENABLE;
NVICInit(&NVICInitStructure);
寄存器级
// 分组:写 AIRCR(需带 VECTKEY 关键字 0x05FA 才能写入)
SCB->AIRCR = 0x05FA0000 | (5 << 8); // 分组 2
// 优先级:IP 寄存器只取高 4 位
NVIC->IP[EXTI0_IRQn] = (0 << 4) | (1 << 6); // 抢占0、子1(分组2时)
// 使能
NVIC->ISER[EXTI0IRQn >> 5] = 1 << (EXTI0IRQn & 0x1F);
---
七、中断响应与嵌套的完整流程
外设产生中断请求 → NVIC 收到 IRQn
NVIC 比较:该中断优先级 是否高于 当前正在执行的优先级?
├─ 高 → 允许嵌套(抢占)
└─ 低或相同 → 挂起,等当前执行完
CPU 响应:
硬件自动压栈(R0-R3、R12、LR、PC、xPSR)→ 无需软件保存现场
读取向量表 → 跳到对应 ISR
ISR 执行完毕 → 硬件自动出栈恢复现场 → 返回被打断处
嵌套示例:抢占优先级 0 的定时器中断,可以打断正在执行的、抢占优先级 2 的串口中断;但两个抢占优先级都
为 2 的中断之间不会互相嵌套,只能按子优先级排队。
---
八、NVIC 的两个硬件优化特性
尾链(Tail-Chaining):一个 ISR 结束、另一个挂起中断立即要执行时,跳过完整的出栈+入栈过程,直接切换,
省 6 个时钟周期。
晚到(Late-Arriving):CPU 正在为低优先级中断压栈时,来了一个更高优先级中断,则直接改去执行高优先级
中断,等它完成后再回来处理原中断,避免重复压栈。
这两个特性是 Cortex-M 实时性能强的重要原因。
---
九、常见坑与注意事项
子优先级不能嵌套——很多人误以为子优先级也能打断。记住:只有抢占优先级能嵌套,子优先级只在同时到达时排队用。
分组只能设一次:多处调用 SetPriorityGrouping 会覆盖,后设的生效。建议统一在 main() 开头,写注释标明用的是哪组。
中断服务函数名必须和向量表一致:例如 EXTI0IRQHandler,写错函数名不会报编译错误,而是中断一触发就跳进默认的 DefaultHandler(死循环)。
在 ISR 里尽量短:ISR 里做标志位置位,复杂处理放主循环或低优先级任务,避免长时间占用、阻塞更高优先级中断。
优先级别太密集:把所有中断都设为 0 或 1 的相邻优先级,容易出现意外嵌套或难以排查的时序问题,先想清楚「谁最重要、谁可被打断」。
F0 系列只有 4 级:如果代码是从 F1 移植到 F0,优先级数值超过 3 会无效。
---
需要的话,我可以进一步展开某一具体部分,比如:SysTick/PendSV 的优先级设置、FreeRTOS 里 NVIC 优先级
的正确配置(涉及 configMAXSYSCALLINTERRUPT_PRIORITY 那套规则),或者用某个具体外设(如定时器 + EXTI 同
时中断)做一个完整的嵌套演示工程。