置顶

番摊机器人 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 同

时中断)做一个完整的嵌套演示工程。