—— 进阶篇)
本文是《STM32内核精讲》栏目的第七篇。上一篇我们学习了异常类型、向量表以及 NVIC 的基础寄存器操作使能/禁止、挂起/清除、优先级配置。本篇将继续深入 NVIC 的核心机制优先级分组、晚到与尾链、EXC_RETURN 的奥秘以及异常返回的完整硬件流程。掌握这些你将彻底理解 Cortex‑M 的实时响应能力为何如此出色。 一、引言进阶特性解决什么问题在基础篇中我们已经能够配置中断的优先级并使能它。但你是否想过为什么 NVIC 支持多达 256 级优先级而实际芯片往往只实现 16 级优先级数值与抢占和子优先级是什么关系两个中断同时发生时硬件如何仲裁高优先级中断能否打断正在执行的低优先级中断中断连续触发时硬件做了什么优化来减少延迟尾链与晚到机制异常返回时处理器如何知道应该使用 MSP 还是 PSP如何知道要从哪个地址恢复 PC所有这些问题的答案都藏在本篇的四个主题中。本篇将大量引用SCB系统控制块寄存器它是 Cortex‑M 内核的控制中枢。 二、抢占优先级与子优先级优先级分组2.1 优先级数值的拆分每个中断的优先级由NVIC_IPRx寄存器的一个字节8 位表示。ARM 将这 8 位分成两部分抢占优先级Pre‑emption Priority和子优先级Subpriority。两者通过一个全局的优先级分组Priority Grouping来划分。抢占优先级决定一个中断能否打断另一个正在执行的中断。数值越小优先级越高。如果新中断的抢占优先级高于当前中断就会发生中断嵌套。子优先级当两个中断具有相同的抢占优先级时子优先级决定它们的响应顺序先响应子优先级高的但子优先级不会产生嵌套。注意抢占优先级也称为组优先级或主优先级不同厂商文档叫法不同。2.2 优先级分组寄存器 AIRCR分组配置保存在SCB 的应用程序中断及复位控制寄存器AIRCR的PRIGROUP字段中。AIRCR 地址为0xE000ED0C。AIRCR 关键位位名称功能31:16VECTKEY访问密钥写入时必须为0x05FA否则写操作被忽略15ENDIANNESS只读0为小端1为大端14:11保留—10:8PRIGROUP优先级分组字段决定抢占和子各占几位7:3保留—2SYSRESETREQ系统复位请求1VECTCLRACTIVE仅供调试清除异常活跃状态0VECTRESET系统复位注意从 AIRCR 读出时VECTKEY 字段的值为0xFA05与写入时的0x05FA不同这是正常的。PRIGROUP 值对应分组表以 8 位优先级为例实际芯片只使用高 N 位但原理相同PRIGROUP抢占优先级位数子优先级位数抢占优先级取值范围子优先级取值范围0b000710~1270~10b001620~630~30b010530~310~70b011440~150~150b100350~70~310b101260~30~630b110170~10~1270b1110800~255STM32 系列通常只使用 PRIOGROUP 的低 3 位即PRIGROUP[2:0]而且实际优先级位数多为 4 位高 4 位有效因此上表中的位数需按芯片实现折算。STM32 常用分组以 4 位优先级为例CMSIS 宏HAL含义等效 PRIGROUP 值8 位模型NVIC_PriorityGroup_00 位抢占4 位子优先级7NVIC_PriorityGroup_11 位抢占3 位子优先级6NVIC_PriorityGroup_22 位抢占2 位子优先级5NVIC_PriorityGroup_33 位抢占1 位子优先级4NVIC_PriorityGroup_44 位抢占0 位子优先级32.3 配置优先级分组的代码示例// 直接操作 SCB-AIRCR#defineCORTEX_M_AIRCR_VECTKEY(0x05FAUL16)voidset_priority_grouping(uint32_tprigroup){uint32_ttempSCB-AIRCR;temp~SCB_AIRCR_PRIGROUP_Msk;// 清除分组位temp|(prigroup8)SCB_AIRCR_PRIGROUP_Msk;temp|CORTEX_M_AIRCR_VECTKEY;SCB-AIRCRtemp;__DSB();__ISB();// 确保配置生效}// 例如设置为 2 位抢占、2 位子优先级PRIGROUP 5set_priority_grouping(5);CMSIS 提供了更简洁的函数但需注意其参数是 PRIGROUP 值而非抢占位数NVIC_SetPriorityGrouping(5);// PRIGROUP 5 → 2 位抢占2 位子优先级4 位实现// 或使用 HAL 宏语义更清晰NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);2.4 中断嵌套示例假设已配置分组为 22抢占 4 级子 4 级。两个中断的优先级数值设置如下中断8位优先级值高4位有效位[7:6]抢占位[5:4]子优先级IRQ00x40 (b0100 0000)10IRQ10x80 (b1000 0000)20IRQ0 的抢占优先级 1 IRQ1 的抢占优先级 2因此 IRQ0 可以抢占 IRQ1。如果两个中断同时挂起先响应抢占优先级高的IRQ0。如果抢占相同则子优先级高的先响应。关键结论中断嵌套只取决于抢占优先级子优先级只在非嵌套场景同时到达或挂起队列中决定顺序。 三、晚到Late‑Arriving与尾链Tail‑Chaining机制这两个硬件优化机制是 Cortex‑M 中断延迟远低于传统 MCU 的核心原因。3.1 晚到Late‑Arriving机制场景处理器已经开始响应一个中断 A已开始压栈或压栈完成但尚未执行其服务程序的第一条指令。此时一个更高优先级的中断 B到达。传统处理器会完全放弃中断 A 的处理重新压栈然后响应 B。这样会浪费大量时钟周期。Cortex‑M 的晚到机制处理器立即转向处理中断 B但不需要重新压栈因为压栈的寄存器内容对 A 和 B 是一样的只是将向量表取值改为 B 的入口。当中断 B 返回时不再重复压栈直接回到被抢占的 A 的压栈后状态。结果晚到机制节省了重新压栈的 12 个周期显著降低高优先级中断的响应延迟。晚到发生的前提是中断 B 的抢占优先级高于 A且发生在 A 的压栈过程中或压栈完成后、第一指令执行之前。3.2 尾链Tail‑Chaining机制场景中断服务程序 A 执行完毕在返回之前发现有一个挂起的中断 B优先级等于或低于 A且 B 是当前所有挂起中断中优先级最高的。传统处理器执行 A 的异常返回出栈12 周期然后立即再次压栈12 周期最后开始执行 B。总共浪费大量周期。Cortex‑M 的尾链机制完全跳过A 的出栈和 B 的压栈过程直接跳转到 B 的服务程序。因为出栈和压栈的寄存器内容相同所以可以复用。结果在典型的零等待存储器条件下尾链将原本需要出栈 12 周期 压栈 12 周期的过程缩短为仅 6 个周期的中断延迟效率提升极为明显。尾链在 RTOS 的 PendSV 切换任务时也有体现当某个中断触发 PendSV 时如果 PendSV 优先级最低则会在所有中断处理完后通过尾链直接进入 PendSV无需额外压栈。3.3 实验验证思路虽然很难直接观测到这两个机制但可以通过测量中断延迟的变化来间接验证。比如测量两个连续中断优先级相同的响应间隔比手动出栈压栈的预估时间短。或者查阅 Cortex‑M 技术手册中的中断延迟表其中明确列出了晚到和尾链下的周期数。 四、异常返回值 EXC_RETURN当处理完异常使用BX LR返回时LR 中保存的并不是一个普通地址而是一个特殊值EXC_RETURN。在 ARMv7‑M 架构中EXC_RETURN 值落在0xFFFFFFF0到0xFFFFFFFF范围内其特征是(EXC_RETURN 0xF0000000) 0xF0000000低 4 位携带信息告诉处理器如何恢复现场。4.1 EXC_RETURN 的位含义以 Cortex‑M4 为例位名称含义31:28—固定为 0xF27:5—固定为 0xFFFFFF4SPSEL返回后使用的堆栈指针0MSP1PSP3MODE返回后的模式0处理模式1线程模式2保留通常为 01保留通常为 00ES指示压栈时是否包含了浮点寄存器0无浮点1有浮点常用 EXC_RETURN 值值含义0xFFFFFFF1返回处理模式使用 MSP无浮点0xFFFFFFF9返回线程模式使用 MSP无浮点0xFFFFFFFD返回线程模式使用 PSP无浮点0xFFFFFFE1返回处理模式使用 MSP浮点已压栈0xFFFFFFE9返回线程模式使用 MSP浮点已压栈0xFFFFFFED返回线程模式使用 PSP浮点已压栈当 FPU 使能且异常中使用了浮点指令时硬件压栈会包括 S0‑S15 和 FPSCR此时 EXC_RETURN 的 bit 0 为 1如0xFFFFFFED。4.2 为什么需要 EXC_RETURNLR 是 32 位寄存器普通函数的返回地址范围在0x00000000到0xFFFFFFFF之间。ARM 特意将异常返回地址编码到高地址段 (0xFFFFFFF0以上)这些地址永远不会是普通函数的合法地址因为代码通常放在低地址。因此处理器可以区分是普通返回还是异常返回。EXC_RETURN 值让硬件知道应该使用哪个堆栈指针MSP/PSP、返回到线程还是处理模式、是否要恢复浮点寄存器等。这些信息原本需要额外的寄存器来保存现在全部编码在 LR 中非常巧妙。4.3 在调试器中观察 EXC_RETURN在异常处理函数的第一条指令处设置断点查看 LR 的值。例如voidPendSV_Handler(void){// 设置断点观察 LR__asmvolatile(nop);}由于进入异常时 LR 被自动设置为 EXC_RETURN你可以看到它的值。常见的 PendSV 中默认是0xFFFFFFFD返回到线程模式、使用 PSP、无 FPU。 五、异常返回的硬件序列出栈恢复当处理器执行BX LR且 LR 为 EXC_RETURN 值时会触发异常返回的硬件流程。以下是详细步骤以无 FPU 为例检查 EXC_RETURN解析 bit 0浮点标志、bit 3返回模式、bit 4堆栈选择。确定出栈的堆栈指针如果返回线程模式且 SPSEL1则从PSP出栈否则从 MSP 出栈。出栈操作从上述 SP 地址依次弹出R0, R1, R2, R3, R12, LR, PC, xPSRxPSR 最后弹出恢复到相应寄存器。SP 最终增加 32。注意入栈时硬件从高到低压入 xPSR, PC, LR, R12, R3, R2, R1, R0因此出栈顺序正好相反。弹出的 LR 会被写入 LR 寄存器但它通常是普通地址函数返回地址而不是 EXC_RETURN。关键不弹出 R4~R11。这些寄存器在异常进入时没有被自动压栈所以出栈时也不会恢复。这也是为什么在 PendSV 中必须手工保存/恢复 R4~R11。更新 PSP/MSP将出栈后的 SP 值写回对应的堆栈指针。跳转到 PC处理器从刚弹出的 PC 值处开始取指恢复被中断的任务或线程。完整流程图简版异常响应时硬件自动压栈 8 个寄存器xPSR, PC, LR, R12, R0-R3到当前 SP 异常服务程序可手工压栈 R4-R11如果需要 异常返回BX LR (EXC_RETURN) ↓ 硬件动作 1. 根据 EXC_RETURN 确定出栈 SP 2. 从该 SP 弹出 8 个字 → R0, R1, R2, R3, R12, LR, PC, xPSR 3. 更新 SP 4. 根据 EXC_RETURN 的 SPSEL 位更新 CONTROL 寄存器中 SPSEL 位线程模式下 5. 跳转到 PC注意如果返回线程模式且原本使用的是 PSP则出栈完成后PSP 会更新而 MSP 全程未使用除非在处理模式下嵌套了中断。 六、综合示例配置优先级分组并使用 PendSV 观察 EXC_RETURN以下代码演示了如何设置优先级分组并通过PendSV一个常用来演示的内核异常在中断服务函数中读取 EXC_RETURN。PendSV 不依赖任何外设且默认优先级最低便于观察。#includestm32h7xx.h// 设置优先级分组为 2 位抢占 2 位子优先级voidset_grouping(void){uint32_ttempSCB-AIRCR;temp~SCB_AIRCR_PRIGROUP_Msk;temp|(58)SCB_AIRCR_PRIGROUP_Msk;// PRIGROUP 5temp|(0x05FA16);SCB-AIRCRtemp;__DSB();__ISB();}// PendSV 中断服务函数voidPendSV_Handler(void){uint32_texc_return;// 获取 LR 中保存的 EXC_RETURN__asmvolatile(MOV %0, LR:r(exc_return));// 在此设置断点观察 exc_return应为 0xFFFFFFFD无 FPU返回线程模式使用 PSP(void)exc_return;}intmain(void){set_grouping();// 设置 PendSV 为较低优先级123 仅示例确保不抢占其他中断NVIC_SetPriority(PendSV_IRQn,123);// 软件触发一次 PendSV必须写 SCB 寄存器不能用 NVIC_SetPendingIRQSCB-ICSR|SCB_ICSR_PENDSVSET_Msk;__enable_irq();while(1);}调试时在PendSV_Handler的第一条指令处设置断点查看exc_return的值你会看到0xFFFFFFFD。 七、总结与下篇预告7.1 本篇核心要点优先级分组将 8 位优先级分为抢占和子优先级由 AIRCR 的 PRIGROUP 字段控制。抢占决定嵌套子决定同时到达顺序。晚到与尾链硬件优化机制减少中断延迟。晚到避免重复压栈尾链避免连续出栈压栈将连续中断延迟大幅缩短零等待存储器下典型值为 6 个周期。EXC_RETURN异常返回时 LR 中的特殊值编码了返回模式、堆栈选择、浮点信息。常用值0xFFFFFFF9、0xFFFFFFFD。异常返回硬件序列自动出栈 R0‑R3, R12, LR, PC, xPSR更新 SP跳转到 PC恢复 LR。R4~R11 需手工保存。7.2 下篇预告《SysTick 定时器 —— 内核自带的节拍器》下一篇我们将学习 SysTick —— 一个简单却无比重要的系统定时器。内容包括SysTick 寄存器的详细含义CTRL, LOAD, VAL, CALIB如何用 SysTick 实现精确延时裸机 tickSysTick 在 RTOS 中的角色时间片轮询、系统节拍特殊应用利用 SysTick 校准其他定时器SysTick 是所有 RTOS 的心脏也是裸机程序中最常用的时间基准。 读者问题专栏 · 问题征集本篇我们深入了 NVIC 的优先级分组、晚到/尾链、EXC_RETURN 和异常返回流程。你在使用中断时是否遇到过以下疑惑为什么我设置了中断优先级但嵌套却没有发生两个相同优先级的中断同时触发哪个先执行硬件怎么决定的PendSV 中断的 EXC_RETURN 为什么是 0xFFFFFFFD 而不是别的异常返回时R4~R11 为什么没有被自动恢复欢迎在评论区留言我会在《Cortex‑M 有问必答》专栏中专题解答。如果可以提供调试寄存器截图如SCB-AIRCR的值、LR的值分析会更加精准。 关于作者与更多内容我是BackCatK Chen长期关注嵌入式底层、国产半导体与 AI 算力芯片。如果你对芯片架构、行业趋势感兴趣欢迎关注我的公众号获取更多宏观技术观察。文章标签Cortex-MNVIC优先级分组EXC_RETURN尾链晚到中断嵌套AIRCR