💡 本文是《STM32内核精讲》栏目第二阶段的开篇之作。前十五篇完成了第一阶段的认知基础——从 Cortex-M 家族图谱、编程模型、存储器模型、指令集、双堆栈、NVIC 基础、SysTick、AAPCS、启动文件、低功耗、调试跟踪、内存屏障到内核选型与性能指标。从本篇开始,我们将进入进阶原理阶段,深入 NVIC 的高级话题、HardFault 故障分析、PendSV 与上下文切换、MPU 内存保护单元等更深入的内核机制。
📌 一、引言:NVIC 不是调度器,是仲裁铁闸
在基础篇(第六、七章)中,我们学习了 NVIC 的基本功能:异常类型、向量表、优先级配置、使能与挂起。但 NVIC 远不止于此。它是一块纯组合逻辑硬件电路,没有状态机,不跑固件,不查表,不排队。它的全部工作,就是在每个时钟沿做三件事:采样所有 IRQ 输入线电平、比对当前活跃中断与所有挂起中断的抢占优先级、若发现更高优先级挂起中断则立即触发硬件嵌套流程。
理解 NVIC 的高级特性,是写出高实时性、高可靠性嵌入式软件的关键。本篇将深入四个主题:
- 中断嵌套与优先级屏蔽:PRIMASK、FAULTMASK、BASEPRI 三个寄存器的语义与使用场景
- 中断响应流程:从硬件压栈到取向量再到执行 ISR 的完整时序
- 软件触发中断:SETPEND 寄存器与 STIR 的使用方法
- 中断延迟分析:最坏情况的计算方法与影响因素
📌 二、中断嵌套与优先级屏蔽寄存器
Cortex‑M 提供了三个特殊功能寄存器用于控制中断和异常的屏蔽,它们的作用范围、灵活性和使用场景各不相同。
2.1 PRIMASK:全局中断“总开关”
PRIMASK 是一个只有 1 个比特的寄存器。当它被置 1 后,除 NMI 和硬故障(HardFault)外,所有可屏蔽的异常和中断都被关闭。它的默认值是 0,表示没有关中断。
语义:PRIMASK = 1 将当前执行优先级提升到 0,只允许优先级为负的异常(NMI 为 -2,HardFault 为 -1)通过。
典型用途:快速进入临界区,保护关键代码段不被中断打断,如 RTOS 任务切换或共享资源访问。
CMSIS 操作方式:
__disable_irq(); // PRIMASK = 1,关中断
__enable_irq(); // PRIMASK = 0,开中断
注意事项:
- 简单粗暴,对系统实时性影响较大。
- 长时间开启 PRIMASK 可能导致高优先级中断无法响应(NMI 和 HardFault 除外)。
- 在 RTOS 中,__disable_irq() 通常用于进入临界区的最外层保护。
2.2 FAULTMASK:更彻底的“静默模式”
FAULTMASK 同样是一个只有 1 个比特的寄存器。当它被置 1 时,只有 NMI 才能响应,所有其它异常——甚至是硬故障——都被屏蔽。默认值也是 0。
语义:FAULTMASK = 1 将当前执行优先级提升到 -1(与硬故障同级),只允许 NMI(优先级 -2)通过。
典型用途:异常处理中的嵌套防护。当系统进入 HardFault_Handler 后,硬件会自动置位 FAULTMASK,防止在故障处理过程中再次发生硬故障导致“双故障”锁死。
CMSIS 操作方式:
__set_FAULTMASK(1); // FAULTMASK = 1
__set_FAULTMASK(0); // FAULTMASK = 0
或使用 CPS 指令:
CPSID F ; FAULTMASK = 1
CPSIE F ; FAULTMASK = 0
注意事项:
- FAULTMASK 比 PRIMASK 更强,连硬故障都能屏蔽。
- 除非你有非常充分的理由(如实现自己的故障处理机制),否则不要在应用程序中主动操作 FAULTMASK。
- 修改 FAULTMASK 通常需要在特权模式下进行。
2.3 BASEPRI:基于优先级的“选择性屏蔽”
BASEPRI 是最灵活的屏蔽寄存器,它是一个 8 位寄存器(BASEPRI[7:0])。其中有效的优先级位数由芯片实现决定(如 STM32 通常使用高 4 位,对应 16 级优先级),未实现的低位读回为零且忽略写入。它定义了一个优先级阈值:
- 当 BASEPRI 被设为某个值后,所有优先级数值大于或等于 BASEPRI 阈值的中断都被屏蔽(ARM 中数值越大优先级越低)。
- 若 BASEPRI = 0,则不关闭任何中断。
- 优先级高于阈值的中断不受影响,可以继续执行。
语义:BASEPRI 允许开发者实现“选择性关中断”——只屏蔽低优先级中断,高优先级中断仍然可以响应。
典型用途:灵活控制中断优先级,允许高优先级任务/中断优先执行,同时屏蔽低优先级中断。
CMSIS 操作方式:
__set_BASEPRI(0x10); // 屏蔽优先级 ≥ 0x10 的中断
__set_BASEPRI(0); // 清除阈值,恢复所有中断
注意事项:
- BASEPRI 不影响 NMI、HardFault 等负优先级异常。
- 需要合理设置优先级阈值,否则可能导致意外屏蔽。
- 优先级数值的计算与优先级分组(PRIGROUP)有关,务必确认实际使用的优先级位数。
2.4 三个寄存器的对比总结
| PRIMASK | 1 bit | 除 NMI 和 HardFault 外的所有中断 | 快速临界区保护 |
| FAULTMASK | 1 bit | 除 NMI 外的所有异常(含 HardFault) | 异常处理嵌套防护 |
| BASEPRI | 8 bit | 优先级 ≥ 阈值的中断 | 选择性屏蔽低优先级中断 |
使用建议:
📌 三、中断响应流程:硬件压栈、取向量、执行 ISR
3.1 中断响应三阶段
当 NVIC 判定一个中断应该被响应时,Cortex‑M 硬件会自动执行以下三个阶段:
第一阶段:硬件压栈(Stacking)
处理器将以下 8 个寄存器 自动压入当前使用的堆栈(MSP 或 PSP):
压栈顺序(内存地址由低到高):
[SP+0] ← R0
[SP+4] ← R1
[SP+8] ← R2
[SP+12] ← R3
[SP+16] ← R12
[SP+20] ← LR(链接寄存器)
[SP+24] ← PC(程序计数器)
[SP+28] ← xPSR(程序状态寄存器)
关于压栈顺序的详细讨论,请参见本专栏第九章 §4(异常入栈:8 个寄存器自动压栈)。
第二阶段:取向量(Vector Fetch)
处理器从向量表中读取对应中断的 ISR 入口地址。向量表的读取与状态保存是并行进行的,这大大提高了中断响应效率。
向量表中每个表项占 4 字节,表项地址的低位(bit[0])必须为 1(表示 Thumb 状态),否则会触发 INVSTATE 故障。
第三阶段:执行服务程序(ISR Execution)
一旦压栈和取向量完成,处理器开始执行中断服务程序的第一条指令。此时:
- 处理器进入处理模式(Handler Mode)
- 强制使用 MSP(主堆栈指针)
- 处于特权级
3.2 尾链(Tail-Chaining)与晚到(Late-Arriving)
这两个硬件优化机制在基础篇(第七章)已经详细讲解,此处简要回顾:
- 尾链(Tail-Chaining):当 ISR A 执行完毕返回时,如果 ISR B 已经挂起,硬件跳过出栈和重新压栈,直接进入 ISR B,节省 6 个周期。
- 晚到(Late-Arriving):在响应 ISR A 的压栈过程中,更高优先级的中断 B 到达,硬件立即转向 B,无需重新压栈。
3.3 中断的挂起(Pending)与活跃(Active)状态
Cortex‑M 为每个中断维护两个状态:
- 挂起状态(Pending):中断已发生,等待响应。可通过 SETPEND 寄存器软件触发。
- 活跃状态(Active):中断正在被处理(ISR 正在执行)。
中断的挂起状态在以下情况下被清除:
对于电平敏感(Level-Sensitive) 的中断,若 ISR 返回时中断信号仍被断言,NVIC 会重新将该中断置为挂起状态。
📌 四、软件触发中断(SETPEND 机制)
4.1 为什么需要软件触发中断?
软件触发中断在以下场景中非常有用:
- RTOS 任务切换:通过 PendSV 实现上下文切换
- 系统调用:使用 SVC 指令从用户态请求内核服务
- 测试与调试:模拟中断场景,验证 ISR 逻辑
- 自测试(BIST) :验证向量表配置是否正确
4.2 两种软件触发方式
方式一:SETPEND 寄存器(NVIC_ISPRn)
每个外部中断都有对应的设置挂起寄存器位。写入 1 即可将该中断置为挂起状态:
NVIC_SetPendingIRQ(EXTI0_IRQn); // 软件触发 EXTI0 中断
方式二:STIR(Software Trigger Interrupt Register)
STIR 位于地址 0xE000_EF00,是一个更专业的软件触发方式:
// 直接写入 STIR 寄存器触发中断(需特权级或已使能 USERSETMPEND)
*((volatile uint32_t *)0xE000EF00) = IRQn;
方式对比:
| SETPEND 寄存器 | NVIC_ISPRn | 特权级 | 按位操作,简单直观 |
| STIR | 0xE000_EF00 | 特权级(默认)/ 用户级(需配置) | 写入即触发,更快捷 |
4.3 用户级访问 STIR
默认情况下,STIR 只能在特权级下访问。如果确实需要在用户级使用 STIR,必须先在 SCB(系统控制块)的 CCR 寄存器(地址 0xE000ED14) 中,将 bit 1(USERSETMPEND)置位。
注意:系统异常(NMI、Faults、PendSV 等)无法通过 STIR 触发。PendSV 的触发需要通过 SCB->ICSR 寄存器操作(参见本专栏第五章)。
📌 五、中断延迟分析:最坏情况计算
5.1 中断延迟的定义
中断延迟是指从中断请求(IRQ)信号置起开始,到内核进入执行该中断 ISR 第一条指令时的时钟周期数。
5.2 影响中断延迟的因素
中断延迟并非固定值,它受多个因素影响:
5.3 各 Cortex‑M 内核的典型中断延迟
在零等待存储器系统中,各内核的典型中断延迟如下:
| M0/M0+ | 15 周期 | 最高优先级中断,零等待系统 |
| M3/M4 | 12 周期 | 零等待系统 |
| M7 | 12 周期(缓存命中时) | 缓存未命中时可能增加 |
5.4 最坏情况中断延迟分析
在零等待内存系统(Zero Wait State Memory System)的理想条件下,Cortex‑M3/M4 处理器的中断延迟为 12 个时钟周期。
然而,在实际工程中,中断延迟的最坏情况需要考虑多种因素:
关于“最坏 14 周期”的来源说明:ARM 社区讨论中曾提及 Cortex-M7 的“典型 12 周期,最坏 14 周期”,但这一数据针对的是 Cortex-M7(缓存未命中场景),并非 Cortex-M4。Cortex-M4 的最坏情况延迟无法用一个固定数值概括——它取决于具体的 Flash 等待周期配置、当前执行的指令类型、总线负载等多种实现因素。
在实测或进行最坏情况分析时,必须综合考虑同步延迟、存储器等待周期、当前指令类型以及中断嵌套状态,不能仅依赖一个固定的公式。
5.5 中断延迟的实测方法
在实际硬件上测量中断延迟,常用方法有:
注意:Cortex‑M 的某些仿真模型(如 FVP)不支持 DWT,此时需要使用 SysTick 或其他计时手段。
📌 六、总结
6.1 本篇核心要点
6.2 下篇预告:《HardFault 故障分析系列》
下一篇我们将进入 HardFault 故障分析的专题。内容包括:
- 可配置故障 vs 硬故障(HFSR)
- 故障状态寄存器组(CFSR、MMFSR、BFSR、UFSR)逐位解析
- 故障地址寄存器(MMFAR、BFAR)
- 通过 PC、LR、xPSR 回溯故障现场
- 常见故障原因案例:未对齐访问、除零、无效指令、栈溢出、总线错误
💬 读者问题专栏 · 问题征集
你在使用 NVIC 高级特性时,是否遇到过:
- PRIMASK、FAULTMASK、BASEPRI 不知道该用哪个?
- 中断响应延迟比预期长,不知道从哪里排查?
- 软件触发中断失败(STIR 写入无效)?
- 中断嵌套行为不符合预期,怀疑 PRIGROUP 配置问题?
欢迎留言,我会在 《Cortex‑M 有问必答》 中专题解答。
📢 关于作者与更多内容
我是 BackCatK Chen,长期关注嵌入式底层、国产半导体与 AI 算力芯片。
如果你对芯片架构、行业趋势感兴趣,欢迎关注我的公众号,获取更多宏观技术观察。
文章标签:Cortex-M NVIC PRIMASK FAULTMASK BASEPRI 中断延迟 软件触发中断 STIR





