ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32N6低功耗LRUN模式下NOR Flash XIP访问异常排查与解决

STM32N6低功耗LRUN模式下NOR Flash XIP访问异常排查与解决 LMU之前那篇我写过同类型问题的排查思路这次遇到的是 STM32N657L0 在低功耗模式下的 NOR flash XIP 访问问题折腾了两天才定位到根因把整个过程整理出来希望能帮到同样被 LRUN、XIP 折磨的兄弟。先说结论这颗芯片的 NOR flash 在 XIP 模式下不是“电”的问题而是“时序”和“顺序”的问题。LRUN 模式切换时时钟源、电压档位和 flash 等待状态三者必须按严格顺序协同更新任何一环脱节跑着跑着就 HardFault而且这问题在调试器下往往复现不出来隐蔽性极高。这篇文章适合正在用 STM32N6 系列做低功耗产品的嵌入式工程师尤其是涉及内部 flash 直接执行代码、需要跑 LRUN 模式的场景。我会从原理讲到排查步骤最后给一张症状对照表你可以直接对照抄作业。1. 问题现象LRUN 模式下 XIP 执行的崩溃现场1.1 项目背景和芯片选型我负责的是一款工业数据采集终端主控选的是 STM32N657L0。这颗料最大的优势是 Cortex-M7 内核跑到 800MHz内部带大容量 NOR flash代码可以直接映射到地址空间执行也就是 XIPExecute In Place不需要像 Cortex-M0 系列那样非得把代码搬运到 SRAM 才能跑。选它的原因很直接一是单芯片方案可以省掉外部 NOR flash 的成本和布线二是 800MHz 主频做 FFT、特征提取这类运算有余量三是片内 flash 的 XIP 性能比 QSPI 外部 flash 的 XIP 要稳得多毕竟是芯片内部总线延时可预测。对于需要低功耗待机、定时唤醒采集的工业场景LRUN 模式必不可少这颗料也支持在低功耗运行模式下继续从 flash 取指。1.2 现象描述与复现条件问题出现在我们做完低功耗策略测试的第三天。白天跑正常 RUN 模式连续 72 小时一点问题没有。晚上进入 LRUN 模式后整机电流确实降下来了但运行时间从几分钟到十几分钟不等系统就会随机死机。通过看门狗抓到的复位原因来看不是引脚复位也不是电源复位而是内核锁死触发的 HardFault。最让人头疼的是它的复现规律接上 ST-LINK 用 IDE 在线调试时怎么跑都不死一旦拔掉调试器、脱离调试环境问题必然出现。这种“调试器下正常、脱机必现”的毛病我在之前几个低功耗项目里也遇到过基本都是时序问题不是逻辑问题。后来我拉长观察窗口同时在关键位置翻转 GPIO用逻辑分析仪记录死机前的状态发现 HardFault 之前总会先出现一段时间异常的长周期取指停滞方向指向了代码取指路径上的 flash 控制器。2. 为什么 LRUN 和 XIP 会闹矛盾原理拆解2.1 从 NOR flash 为什么能 XIP 说起和 NAND flash 的区别要理解这个问题得先明白 NOR flash 凭什么能做 XIP。NOR flash 的存储单元结构决定了它有完全随机的字节级读取能力也就是说 CPU 发一个地址、给一个读时序NOR flash 能在确定时间内把对应地址的指令取出来不需要先读一整块到缓冲区再慢慢挑。这就让芯片可以把 flash 映射到统一地址空间代码直接在 flash 地址上跑。NAND flash 就不一样了。它虽然容量大、成本低但只能按页读取CPU 想取一条指令还得先把整个页搬进 SRAM 缓冲再从中找目标指令。这中间的时间是不确定的、不可预测的所以 NAND 不可能直接 XIP必须搭配外部 RAM 做 shadow 映射。这也是为什么业内常说 NOR flash 适合存代码、NAND flash 适合存数据。搞懂这个区别你就能理解芯片原厂为什么把内部 flash 做成 NOR 结构也就能理解为什么 XIP 对 flash 的访问时序极其敏感。2.2 LRUN 模式到底动了哪些“定时炸弹”LRUNLow Power Run模式是 STM32 低功耗策略里的关键一环。它跟停机模式不一样CPU 内核没有停止时钟还在跑代码还在执行只是系统时钟从 800MHz 的 PLL 输出降到几 MHz 级别的 MSI多速内部振荡器。电压档位也会从最高性能档降到低功耗档片内外设时钟大多被门控只有必要的定时器和唤醒源在工作。听起来很简单跑着跑着把主频降下来就行了。但问题就藏在“降频”这两个字里。CPU 时钟降了flash 控制器的访问时序参数如果不跟着改读 flash 的建立时间、保持时间就不匹配轻则偶尔读错指令重则取指直接超时、内核总线 stall最终 HardFault。这就像你和一个搭档配合搬东西原来约定好每秒搬 3 次结果你突然改成每秒搬 1 次搭档却还按原来的节奏伸手不摔跤才怪。2.3 等待状态、电压档位、时钟切换三者的耦合这里要引入一个关键参数flash 等待状态Latency / Wait States。NOR flash 内部存储阵列读取需要一段固定的物理时间而 CPU 的时钟周期是随工作频率变化的。主频越高一个时钟周期越短flash 单次读可能赶不上 CPU 一个周期内完成取指所以必须在两者之间插入若干等待周期让 CPU 多等几个周期等 flash 把数据稳定送出。等待状态怎么定芯片参考手册里会有一张表横轴是主频范围纵轴是电压档位交叉点就是你应该配置的等待状态值。STM32N657L0 在 800MHz 高频下等待状态通常要配到十几甚至更多而在 LRUN 模式几 MHz 主频下等待状态甚至可以是 0。问题就出在切换过程中如果先降频、再改等待状态中间会有一段窗口期CPU 以低频率工作却仍按高频率的等待状态去读 flash读取时间会比需要的更长一般不至于出错但如果顺序反过来先调低了等待状态、主频还没降下来CPU 就会在 flash 还没准备好的情况下强行取指必崩。电压档位同样是变量。主频 800MHz 时芯片肯定工作在最高电压档比如 VOS1降到几 MHz 的 LRUN 模式时为了省电会切到低电压档。电压降低后flash 存储单元的读取裕量变小同样需要更长的时间来稳定读出数据。如果你在电压档位降低之后还沿用高压下的等待状态配置等于让 flash 在更严苛的电压条件下以更快的节奏工作大概率翻车。3. 核心细节与实操排查一步一步定位问题3.1 先说结论最容易翻车的三个环节我这次定位到的问题最终可以收敛到三件事一是进入 LRUN 时MSI 时钟源切换后没有重新配置 flash 等待状态二是电压档位切换后没有等待电压稳定标志就被下面的代码继续执行flash 访问发生在电压跌落的中途三是进入低功耗前没有把 Cortex-M7 的 D-Cache 数据写回导致唤醒后读完脏数据直接当指令执行。这三个问题单独拎出来都不难理解但放在 LRUN 切换序列里它们互相嵌套任何一个环节顺序错了都会引起随机性极强的故障。下面我按排查顺序展开你可以对照自己的代码逐步检查。3.2 排查步骤从 HardFault 到根因的五个关键动作第一步抓 HardFault 现场。我在 HardFault_Handler 里把栈上的返回地址 LR、PC、PSR 全部转存到一块不被刷新的 SRAM 区域然后通过一个调试串口在唤醒后把数据打出来。跑了几轮之后发现 PC 指向的地址范围居然不在代码段内说明取指已经在 flash 映射区域之外飞了。再结合异常的 CPSR 值我判断问题不是普通逻辑错误而是取指路径上的机械性故障。第二步核对实际时钟配置。我在进入 LRUN 前后把 RCC-CFGR、RCC-MSISR 等寄存器的值抓出来对比。正常做法是进入 LRUN 前应该先把系统时钟切到 MSI并把 SYSCLK 分频到目标低频率。但我发现自己的代码在切换时钟源时只是简单调用了库函数并没有确认 MSI 已经稳定输出就继续往下跑。MSI 从启动到稳定需要一段时间如果在这个窗口期访问 flash时序毫无保证。第三步对照等待状态配置表。我把当前工作电压档位、当前系统主频、当前 FLASH-ACR 寄存器的 LATENCY 值放在一起逐个查参考手册的等待状态表发现一个铁证进入 LRUN 后我的代码把 LATENCY 从高主频值只调低了一档但实际主频已经降到几 MHz按表格应该直接配 0 等待状态。配置和物理需求差了整整好几拍这种长时间的过等待倒不会立刻崩但会导致 CPU 访问 flash 的时序毛刺叠加累积到一定程度就触发取指异常。第四步核查电压档位切换时序。STM32 的电压档位切换在 PWR 模块里有标志位指示。我在切入低电压档后继续执行下面的代码之前没有等待电压切换完成标志置位。读寄存器、配置外设这些操作本身就要从 flash 取指如果我第一脚踩进去的时候电压还在下探那次取指就已经埋下了隐患。这一条用逻辑分析仪配合内核事件输出才能看出来普通单步调试根本捕捉不到。第五步把可疑函数搬到 SRAM 里跑对比。为了验证取指路径问题我把 LRUN 入口函数、LowPower 处理函数全部重新定位到 SRAM 执行flash 访问在低功耗切换窗口期被彻底避开。改完这一版之后脱机跑了 12 个小时一次 HardFault 都没出现。这就反向证明了取指路径上的 flash 时序问题就是罪魁祸首。到此问题定位完成剩下的就是梳理正确的进入序列。3.3 进入 LRUN 的推荐配置序列问题定位之后我把进入 LRUN 的流程重新梳理了一遍顺序固定为先切时钟源并等稳定再降主频分频然后切换电压档位并等待稳定最后重新配置 flash 等待状态并且关闭预取缓冲或调整其策略。下面是精简后的配置片段基于寄存器操作展开方便你理解每一步在做什么void enter_lrun_mode(void) { // 1. 先切到 MSI并等 MSI ready RCC-CR | RCC_CR_MSION; while (!(RCC-CR RCC_CR_MSIRDY)); // 2. 配置 MSI 输出频率为目标低频例如 4MHz RCC-CR ~RCC_CR_MSIRANGE_Msk; RCC-CR | RCC_CR_MSIRANGE_4MHz; // 重新触发 MSI 频率切换等待 ready while (!(RCC-CR RCC_CR_MSIRDY)); // 3. 切换系统时钟源到 MSI RCC-CFGR ~RCC_CFGR_SW_Msk; RCC-CFGR | RCC_CFGR_SW_MSI; while ((RCC-CFGR RCC_CFGR_SWS_Msk) ! RCC_CFGR_SWS_MSI); // 4. 降主频分频HCLK 也需要同步调整 RCC-CFGR (RCC-CFGR ~RCC_CFGR_HPRE_Msk) | RCC_CFGR_HPRE_DIV1; // 5. 切电压档位等待电压切换完成标志 PWR-CR1 (PWR-CR1 ~PWR_CR1_VOS_Msk) | PWR_CR1_VOS_LOW; while (!(PWR-SR1 PWR_SR1_VOSF)); // 6. 关键一步根据当前实际主频和电压档位重新配置 flash 等待状态 // LRUN 低频下通常可配 0x0具体以参考手册等待状态表为准 FLASH-ACR ~FLASH_ACR_LATENCY_Msk; FLASH-ACR | FLASH_ACR_LATENCY_0WS; // 7. 最后才进入 LRUN 模式 PWR-CR1 | PWR_CR1_LPRUN; }注意第六步的配置顺序必须放在电压切换完成之后。因为电压档位变了flash 的读取物理特性就变了等待状态必须基于最终的电压状态来算。如果按旧电压下的读取速度来配新的低电压下同样会存在时序风险。这一步也是我最初代码里缺失的关键环节。3.4 缓存、中断、调试器等容易干扰判断的因素Cortex-M7 带有 I-Cache 和 D-Cache这对 XIP 性能至关重要。I-Cache 能缓存 flash 上的指令但它在硬件上对代码修改是透明的一般不需要软件干预。D-Cache 就不一样了如果 LRUN 切换过程中有 DMA 写 flash 或外设写内存的操作必须保证数据一致性否则唤醒后 D-Cache 里可能是陈旧数据。调试器干扰这个坑我必须单独提一句。很多工程师遇到“调试器下正常、脱机必现”的问题会怀疑是不是硬件接触不良其实绝大多数时候是调试器改变了时序环境。调试器在线时内核时钟经常被冻结或者调试器访问总线时会触发额外的总线周期这些操作会拉长 flash 访问的实际周期意外地掩盖了时序缺陷。所以排查这类问题一定要以脱机运行为准逻辑分析仪、GPIO 电平标记才是可信的观测手段。4. 常见问题速查与避坑经验4.1 症状、根因与对策对照表这一节我把同类问题的高频症状整理了表格你可以直接按图索骥症状最可能的根因快速检查方法解决方案LRUN 下随机 HardFaultPC 飞到代码段以外时钟切换后 flash 等待状态未更新打印 FLASH-ACR 与参考手册对照重新按最终主频配置 LATENCY脱机必现、调试器下正常总线访问时序被调试器意外补偿脱机GPIO标记验证以脱机运行为验证标准从 LRUN 唤醒后死机电压切换未完成就访问 flash检查 PWR 电压稳定标志等待 VOSF 后再配 flashD-Cache 开启后运行一段时间异常LRUN 前脏数据未写回检查是否有 DMA 写入内存进入 LRUN 前执行 D-Cache Clean中断触发后瞬间 HardFault中断入口取指发生在 flash 不稳定期把中断回调放到 SRAM 对比延长电压稳定等待或推迟开中断低主频下反而出错高主频正常等待状态配得过高导致时序毛刺实际频率与 LATENCY 按表格核对降至对应 0WS 或最小等待状态4.2 几个高价值排查技巧和工具分享几个这次排查中真正提升效率的技巧。第一在进入 LRUN 的关键节点翻转 GPIO用逻辑分析仪观测整个切换序列的时长。如果某一步骤的间隔异常长或短能快速定位是哪一步没有按预期执行。第二用 DWT-CYCCNT 测量时钟源切换、电压切换等步骤消耗的实际周期数把结果通过调试串口打印出来能直接发现库函数内部哪些地方存在死等。第三把所有中断处理函数临时重定位到 SRAM 执行如果问题消失基本可以确定是 flash 取指路径的问题这个手段隔离性极好。调这种低功耗 XIP 问题我的习惯是准备一个小工具函数能把当前 RCC 时钟源、HCLK 频率、VOS 档位、FLASH-ACR 这些关键参数一次性打包成结构体在串口里用易读格式输出。遇到问题先打一针“状态快照”能省掉一半的猜谜时间。4.3 针对 STM32N657L0 的避坑经验总结最后分享几条针对 STM32N657L0 这颗芯片的经验。一是它内部 flash 容量大、XIP 执行性能好但等待状态配置对电压档位的敏感度比老款 M4 芯片更高别拿老经验套新芯片。二是片上的 ART 加速器或预取缓冲如果使能切换时钟源后有机会读到预取的旧指令必要时建议在切频前后刷新或关闭预取。三是硬件上如果还有外部 NOR flash 通过 FMC 接口 XIP它的时序参数表和片内 flash 是两套体系不要用同一套配置去套。还有一点值得强调LRUN 模式不是“跑着跑着降个频”这么简单它和 flash 控制器、电源管理、缓存策略、中断窗口是绑在一起的。任何一个环节顺序错了结果就是这种随机性极强、排查成本极高的故障。我这次花了两天才定位到根因本质上就是一开始没把“顺序”当作核心约束来审查。我在实际排查中还发现一个很容易被忽略的点进入 LRUN 之前如果开启了全局中断而某个中断恰好在电压切换标志位等待期间触发中断入口的取指会直接踩在 flash 最不稳定的窗口上。建议进入低功耗切换序列的代码段在切换完成之前把中断关掉或者确保所有中断服务函数都放在 SRAM 里执行这是成本最低的保险手段。这个内容后续如果再遇到类似问题我建议你先去看 ST 针对该系列整理的低功耗应用笔记里面关于时钟切换顺序和 flash 等待状态的表格比任何网上的例程都准确。拿到新芯片第一件事就是把 clock tree、FLASH-ACR、PWR 相关寄存器全部打印出来确认每一个配置和实际物理条件匹配能省掉后面大量定位时间。
返回列表