STM32U5G9外挂Octal NOR Flash烧录失败排查:从External Loader到OCTOSPI

STM32U5G9外挂Octal NOR Flash烧录失败排查:从External Loader到OCTOSPI 前段时间接手了一块板子主控是 STM32U5G9NJH6Q外挂一片 256Mbit 的 Octal NOR Flash用来放 UI 资源和字库打算直接用 XIP 方式内存映射执行。硬件回来当天板子能通过 STM32CubeProgrammer 正常识别内部 Flash 烧写也没问题但一旦勾上 External Loader 去操作 OCTOSPI问题就来了要么 JEDEC ID 读回来全是 0xFF要么烧写中途校验失败要么直接报 Cannot load external loader。这类问题在 STM32U5 的高配型号上特别容易遇到因为 U5G9 的定位就是大存储、高集成度很多人都会给它挂一片大型 Octal Flash/PSRAM而工程里最容易被忽略的恰恰是烧录工具怎么访问外部存储器这一段链路。这篇文章我把自己排查过程中踩过的坑、验证过的步骤以及最终沉淀下来的一套可复现流程完整写出来方向包括工具版本与加载器匹配、OCTOSPI 初始化时序、物理层接线、以及常见的启动/调试配置问题。做 U5 系列外挂存储方案的工程师或者只是刚拿到 U5G9 开发板想把外部 Flash 烧录跑通的入门用户都可以按这个思路少走弯路。1. 先弄清楚 STM32CubeProgrammer 和 OCTOSPI 之间的真实协作关系1.1 为什么偏偏是 U5G9 上这个问题最让人头疼STM32U5G9NJH6Q 这颗料在 U5 家族里属于性能比较高的配置Cortex-M33 内核内部集成了大容量 Flash 和大块 SRAM还带了多个 OCTOSPI 接口。很多人选这颗料就是看中它能外挂大容量 NOR Flash 或者 PSRAM把图片、字库、音频资源甚至代码放到外部存储器里通过内存映射直接执行。这个用法本身没问题但它给烧录环节增加了一个非常容易被忽略的依赖烧录工具必须先把外部存储器初始化好才能往里写数据。问题就出在这里。STM32CubeProgrammer 对内部 Flash 的烧录是内置支持的工具通过 SWD 接口把烧录算法加载进芯片 SRAM 执行一套流程全自动完成。但是外部 OCTOSPI Flash 不存在这种默认支持工具并不知道你的板子上挂了哪颗 Flash、用哪几个 GPIO、支持什么指令集、Dummy Cycle 怎么配。它必须要一个针对具体板卡的外部加载器才知道怎么操作这颗 Flash这个加载器就是 .stldr 文件。一旦加载器缺失、不兼容、或者初始化时序和实际硬件不匹配你看到的就是各种各样的烧录异常。1.2 烧录工具的执行边界内部 Flash 能烧不代表外部 Flash 也能烧我一开始也犯过这个认知错误觉得STM32CubeProgrammer 能连上芯片外部 Flash 烧不进肯定是工具 bug。后来把原理理清楚才好办STM32CubeProgrammer 本质上是一个上位机它通过 ST-LINK 等调试器访问芯片的调试端口然后利用芯片自身执行的代码去操作外设。内部 Flash 烧录时工具会向芯片 SRAM 中加载一段由 ST 提供的内部烧录算法CPU 执行这段算法去操作 FMC/Flash 控制器工具只负责传输数据和反馈状态。外部 OCTOSPI Flash 是同样的逻辑但烧录算法换成了你的板级初始化代码也就是 External Loader。工具先把 .stldr 加载到 RAM让 CPU 执行其中的初始化函数把 OCTOSPI 外设、GPIO、时钟、Flash 的读/写/擦除指令全部配置好之后才通过调试接口持续访问内存映射区域完成数据的写入和校验。可以这么理解工具是一个发令员真正的搬运工是那段跑在芯片里的 loader 代码。所以外部 Flash 烧不进八成是搬运工出了问题而不一定是发令员的问题。排查方向应该优先放在 loader 本身、loader 与硬件的匹配、以及 loader 运行所需的基础环境上而不是一味重装工具。1.3 我遇到的高频症状一览下面这个表格是我在实际项目和各种论坛帖子里见过最多的几类现象后续所有排查思路都是围绕这些表象展开的。症状典型现象最可能的根因外部 Flash 识别失败Memory 窗口读外部地址全 0xFF 或全 0x00GPIO 复用错误、Dummy Cycle 配置错误、Flash 供电/控制脚异常Loader 加载报错Error: cannot load external loader.stldr 与芯片不兼容、路径指向错误、工具版本过旧烧写中断/校验失败写入一部分后报 verify error或擦除后仍读回旧数据时钟分频太高、DTR/SDR 模式不匹配、Flash 指令配置错误连接阶段失败ST-LINK 能识别但 Target 无响应TrustZone/RDP 配置、启动引脚状态、复位电路异常工具识别型号错误U5G9 被识别成其他芯片或 unknownSTM32CubeProgrammer 版本太老、ST-LINK 固件没升级看到内部 Flash 正常、外部 Flash 异常时可以先排除 SWD 链路和工具本身的问题把注意力放到外部存储器子系统上。如果连型号识别都有问题那就要先解决连接和版本层面的问题再回头看外部 Flash。2. 问题一外部 Flash 挂死读 ID 全 0xFF 全 0x00 的排查链路2.1 先别改代码物理层三件事必须确认我遇到读回全 0xFF 这种情况第一反应是去翻 loader 里的初始化代码结果翻了半天发现问题根本不在代码层面而在最基础的物理连接上。建议所有人先按下面三个顺序排查再动软件。首先是供电。U5G9 的 OCTOSPI 端口可以工作在 1.8V 或 3.3V具体取决于 VDDIO2 或者 IO 口所在的电源域。外部 Flash 的工作电压必须和 MCU 的 IO 电平匹配。我曾经遇到过一块板子Flash 供电是 1.8V但 MCU 的 IO 域被配置成 3.3V结果通信极不稳定读 ID 时好时坏。这个用万用表量一下就能发现不要想当然认为原理图上画的一定对。其次是控制脚的默认状态。Octal NOR Flash 一般都有 WP#、HOLD#、RESET# 这些控制脚如果它们在初始化过程中没有被正确拉高或释放Flash 可能会处于写保护或者挂起状态导致所有读写命令都没有响应。很多最小系统图里只画了数据线和时钟线控制脚的处理容易忽略。实际排查时可以先把这些脚手动强制到正确电平比如把 WP# 和 HOLD# 都拉高看读 ID 是否恢复。最后是接线和测量。OSPI 跑起来之后频率不低哪怕初始化阶段跑得慢IO 线上的干扰也会导致命令字出错。如果 Flash 和 MCU 之间用了杜邦线或者很长的排线先把连线缩短到 5cm 以内再试。有条件的话用示波器或者逻辑分析仪看 CS、CLK、IO0 这几个关键信号确认时钟有没有出来、片选有没有正常拉低、数据线上有没有波形这一步能极大缩小排查范围。2.2 区分命令没到达 Flash和Flash 有响应但返回错误物理层确认没问题后可以通过现象判断故障点在链路的哪一段。读外部内存映射区域如果返回全 0xFF通常意味着 Flash 认为当前没有数据输出可能原因是命令没有到达、或者 CS/CLK 时序不对、或者 Flash 处于深度掉电模式。如果返回全 0x00往往说明 Flash 已经在响应但读到的数据是 0可能是误触发了写操作、地址线配置错、或者读命令不对。还有一个很实用的技巧直接把 OCTOSPI 的时钟分频调到最低把速率降下来。很多 flash 型号对高速模式有要求比如 DTR 模式下的 tV 时序参数如果 loader 里配置的 dummy cycles 和实际设备不匹配读操作就会失败。把频率降到几十 MHz 以下用 SDR 模式、单线模式去读 ID往往能绕过大部分时序问题先确认物理链路是否通畅。等能稳定读到 ID 了再逐步提高速率、切换模式就能定位到具体是哪个参数导致的问题。2.3 Loader 中的 GPIO 复用和 Dummy Cycle 才是重灾区排除了物理层之后剩下的基本都在 loader 的配置参数里。我见过最多的问题集中在 GPIO 复用和 Dummy Cycles 这两个地方。GPIO 复用问题非常隐蔽。很多板卡参考设计里 OCTOSPI 的引脚会被复用为其他功能比如调试串口、LCD 接口、或者普通的 GPIO 控制脚。如果 loader 初始化时把引脚设置成了 AF 模式但 AF 号选错那通信肯定不通。还有一个细节是 U5G9 的不同封装、不同引脚映射可能对应不同的 AF 值同样一个功能在 F4 上是一个 AF 号在 U5 上可能是另一个不能凭经验照搬。最可靠的做法是在 STM32CubeMX 里根据实际原理图的引脚分配生成初始化代码再把它移植到 loader 工程中而不是手写 GPIO 配置。Dummy Cycles 是另一个高频坑。Octal Flash 在 Fast Read、Dual/Quad/Octal 模式下通常都需要插入若干 dummy 周期具体数量由 SFDP 表或者数据手册决定。如果 loader 里配置的 dummy 周期比 Flash 实际需要的少读数据时 Flash 还没把数据准备好总线上读到的就是垃圾数据如果配多了读取速度会下降但不至于完全失败。这里最容易踩坑的是读 JEDEC ID 成功但读数据失败的现象因为读 ID 命令通常不要求 dummy cycle而读数据命令要求所以有些人会误以为初始化和接线没问题实际上数据读取路径上的 dummy 配置是错的。3. 问题二External Loader 加载失败或烧录校验不过3.1 .stldr 文件的本质和兼容性.stldr 文件不是一个配置文件而是一段可执行的固件它面向的是目标芯片而不是 PC。STM32CubeProgrammer 加载它时会把这段代码下载到芯片 RAM 中特定地址然后通过约定的函数指针调用其中的初始化、读、写、擦除等接口。这就带来一个关键约束.stldr 的构建目标必须和实际使用的芯片匹配。我之前遇到过一个案例同事从别的项目里拷贝了一个加载 H7 系列外部 Flash 的 .stldr 文件想着都是 STM32可能通用结果在 U5G9 上怎么都加载不了。原因很简单不同系列的内核、外设寄存器地址、甚至 RAM 布局都不同这段 loader 在 U5G9 上要么无法运行要么运行后访问的是错误的外设地址。正确做法是去 STM32CubeProgrammer 安装目录下的 ExternalLoader 文件夹里找有没有针对 U5 系列的 .stldr如果没有就需要自己用 STM32CubeMX 生成一个 external loader 工程编译出来。还有一种兼容性问题表现得更隐蔽loader 能加载工具也不报错但烧写时校验不过。这时要检查 loader 的接口版本是否和 STM32CubeProgrammer 匹配。ST 在升级工具时偶尔会调整 loader 的接口结构老的 loader 用新工具加载可能某些功能字段缺省导致读操作正常、写操作异常。遇到这种问题优先从官方渠道获取与工具版本配套的 loader 文件。3.2 CubeProgrammer 版本和 ST-LINK 固件都可能是元凶很多人遇到 U5G9 识别异常第一反应是硬件坏了但我见过更多情况只是工具版本问题。STM32U5G9 属于较新的型号它的内核 ID、DBGMCU IDCODE、以及调试访问接口都有特定的标识。早期的 STM32CubeProgrammer 版本内置的设备数据库里没有这个型号工具要么识别成未知设备要么连接后 SVD 映射错误导致操作外部外设时行为怪异。解决办法很直接先把 STM32CubeProgrammer 升级到最新版本。另外 ST-LINK 调试器本身也有固件如果固件版本太老它对新款芯片 SWD 协议的支持可能不完整。在 STM32CubeProgrammer 的 Help 菜单里找到 Firmware upgrades把 ST-LINK 固件也一并升到最新很多时候问题就消失了大半。这里提醒一下升级前最好记录一下当前版本。因为有一次我升级完 STM32CubeProgrammer 之后发现团队里其他人还在用旧版本两个版本的工程配置格式有差异结果我生成的烧录脚本在他们环境里跑不了又折腾了一阵子。版本统一这件事在团队协作里比个人技巧更重要后面我会专门说。3.3 下载渠道和工具完整度搜索热词里很多人问STM32CubeProgrammer 除了官网还有其他下载地址我的建议是别折腾直接用官方渠道。一个原因是第三方下载站经常滞后你明明搜索到的是最新版点进去可能还是几个月甚至一两年前的版本对新芯片支持不完整另一个更现实的原因是有安全风险烧录工具要下载固件到芯片里如果有人篡改过安装包后果不是蓝屏那么简单。STM32CubeProgrammer 的官方获取方式有几种去 ST 官网产品页面按提示注册后下载或者如果你的电脑装了 STM32CubeMX在 Help 菜单里可以检查并安装工具链更新也可以从 STM32CubeIDE 的安装目录里找到它自带的版本。官方安装包还包括驱动程序和完整的 ExternalLoader 目录第三方精简版经常把 ExternalLoader 目录、固件升级包、甚至 USB 驱动裁掉这可能正是你外部 Flash 操作失败的深层原因。4. 问题三连接阶段就失败或识别不准确4.1 连接方式选择Hot Plug 与 Under Reset如果 STM32CubeProgrammer 连 ST-LINK 都连接不上或者偶尔能连上、第二次就失败先检查连接方式。GUI 的连接设置里有 Hot Plug 和 Under Reset 两种模式本质区别在于连接时是否控制目标芯片的复位引脚。Hot Plug 模式适合目标芯片已经正常运行、调试接口没有冲突的场景它直接通过 SWD 协议访问内核不需要复位芯片。Under Reset 模式则是连接前先拉低复位让芯片保持在复位状态再初始化调试访问等调试器接管后再释放复位。对于 U5G9 这种内置 TrustZone 的芯片如果安全配置或者选项字节已经被改过Hot Plug 模式可能因为调试访问权限问题导致连接失败而 Under Reset 模式往往能绕过去。我自己的习惯是新板子第一次连接优先用 Under Reset 模式连接成功后再切回 Hot Plug 验证。如果 Under Reset 也连不上那基本可以判断是硬件复位电路或供电问题而不是软件问题。另外SWD 的复位脚如果被外部电容拉得太死或者复位电路设计不当也可能导致 Under Reset 模式失败这时可以试一下把 resetHWrst 换成软件复位。4.2 复位、供电、TrustZone 与 RDP 等级U5 系列比之前的 STM32 多了一个容易踩的坑TrustZone 和读保护等级 RDP 会直接影响调试连接。如果芯片之前被设置成 RDP Level 1 或更高等级调试端口就不会像默认状态那样开放STM32CubeProgrammer 连接时会报错或者只能执行受限操作。对于工厂新拿到的芯片默认状态一般没问题但如果板子被尝试过加密、或者升级过选项字节就需要先通过全擦除等方式把保护等级降回 Level 0 才能正常调试。TrustZone 开启后调试访问还需要区分安全和非安全世界。如果 loader 代码被放在了非安全区域但 OCTOSPI 外设被配置成仅安全访问loader 初始化时就会触发 fault表现就是加载 loader 后没有反应、读出来的数据异常。这个问题在只有裸机程序、没有启用 TrustZone 的应用里不常见但如果你的工程使用了带 TrustZone 的软件包就要特别注意了。供电方面U5G9 有大电流需求的引脚和多个电源域供电不稳时最典型的表现不是完全连不上而是连接后跑一段时间就断或者烧录到一半失败。这种情况用示波器看 VDD 在烧录瞬间是否有跌落比怀疑工具配置更有效。还有一个容易忽略的点是外部 Flash 的 VCC 是否和 MCU 的电源域同步上电如果 Flash 上电比 MCU 慢loader 初始化时 Flash 还没准备好读 ID 也会失败。4.3 用命令行复现和定位连接问题GUI 界面虽然直观但连接类问题我更喜欢用命令行来复现因为命令行输出更明确而且方便把完整命令贴给同事分析。比如STM32_Programmer_CLI -c portSWD modeUR resetHWrst如果这条命令能正常列出 target 信息说明 SWD 链路、ST-LINK 固件、工具版本都没问题。如果报错错误信息里通常会有详细原因比如 No STM32 target found 或 Error: Activating device failed。命令行还有一个好处是可以在同一条命令里把外部 loader 和操作都串起来方便做最小复现STM32_Programmer_CLI -c portSWD modeUR -el C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\ExternalLoader\xxx.stldr -w app.bin 0x70000000 -v这里的地址 0x70000000 是 U5 系列 OCTOSPI 内存映射区域的常见起始地址具体要以目标芯片参考手册中的 memory map 为准。如果命令行能完成烧写而 GUI 不行那就要检查 GUI 里 External loader 的配置路径是不是和命令行不一致如果命令行也报错那错误信息能直接把问题定位到 loader 还是连接。5. 一套可以直接抄的排查与恢复流程5.1 环境准备清单下面这几件事不做完我不会开始排查任何 U5G9 的烧录问题先列出来给你参考STM32CubeProgrammer 升级到最新版本记录当前版本号。ST-LINK 固件通过 Help Firmware upgrades 升级到最新确认指示灯状态正常。安装官方版本工具确认安装目录下有 ExternalLoader 文件夹且能看到至少一个 U5 系列相关的 .stldr。用万用表确认目标板 VDD、VDDIO 供电正常外部 Flash 的 VCC 符合规格。确认 SWDIO、SWCLK、NRST 三根线连接正确GND 共地排线尽量短。查看芯片选项字节状态确认 RDP 等级为 Level 0TrustZone 配置符合调试需求。5.2 GUI 操作步骤环境确认后GUI 操作按这个顺序来打开 STM32CubeProgrammer在右侧选择 ST-LINK 接口连接方式选 Under Reset点击 Connect。连接成功后先确认右下角识别出的芯片型号是 STM32U5G9xx不是 Unknown。打开 External programming configuration勾选 External loader浏览选择你板卡对应的 .stldr 文件。下载一个测试用的空 bin 文件到外部 Flash 地址或者直接在 Memory 窗口输入外部映射地址读取内容验证是否能读到有效数据。如果读到的内容仍旧全 0xFF 或 0x00切到命令行模式执行同样的操作对比输出。这里有一个细节External loader 勾选后工具会自动关联外部存储器的访问区域。如果你在 GUI 里看不到外部 Flash 对应的地址空间先检查是否是因为连接时没有加载 loader而不仅仅是配置选项的问题。GUI 的 Memory 窗口如果显示的是芯片内部映射那说明 loader 没有被正确挂载。5.3 命令行操作与脚本示例命令行比 GUI 更适合做脚本化量产也更容易复现问题。下面是我常用的几条命令先验证目标连接STM32_Programmer_CLI -c portSWD modeUR resetHWrst -ob displ这条命令可以读取并显示选项字节状态确认保护等级和启动配置。再加载外部 loader 并读取外部 Flash 内容-r 读取到本地文件STM32_Programmer_CLI -c portSWD modeUR -el xxx.stldr -r ext_flash.bin 0x70000000 0x1000如果读取成功说明 loader 和外部 Flash 之间的读写链路基本没问题可以尝试擦除和烧写STM32_Programmer_CLI -c portSWD modeUR -el xxx.stldr -e 0x70000000 0x100000 -w app.bin 0x70000000 -v注意 -e 擦除的地址和长度都要和实际 Flash 容量匹配不要把长度设置超过 Flash 末尾地址否则操作会失败。烧写后加 -v 参数做校验如果校验失败大概率是 loader 配置的擦除/写入指令与时序不匹配。5.4 还是没有解决回归最小系统验证如果你执行到这一步问题还在我最强烈的一个建议是不要继续在完整板卡上反复试做一个最小系统出来。把 U5G9 最小启动电路和一颗已知型号、有官方参考驱动的 Octal NOR Flash 接在一起用 ST 官方评估板的 loader 文件试试。我见过太多问题其实是完整板卡的某个干扰源比如其他外设的 GPIO 冲突、LCD 数据线占了 OCTOSPI 引脚、甚至 PCB 布局导致信号完整性问题这些在最小系统里都会被放大出来更容易定位。最小系统上如果还是读不到 ID那就耐心地用示波器抓一次 CS、CLK、IO0 的时序。注意读 JEDEC ID 命令 0x9F 发出后Flash 应该在第八个时钟之后开始输出 ID 字节。如果 CLK 有波形但 IO0 一直是高电平说明命令没被 Flash 正确解析多见于命令编码或者引脚映射错误如果 IO0 有波形但读出的数值明显不符合这颗 Flash 的 ID那多半是时序参数问题比如 dummy cycle 或时钟极性/相位。5.5 我常用的几个技巧分享几个不在官方文档里写得特别明显的经验。第一加载外部 loader 时优先用绝对路径不要用相对路径。我曾经在命令行里用相对路径结果在不同工作目录下执行一个成功一个失败排查了好久才发现是路径问题。第二遇到烧写中途失败先把擦除和烧写分开执行不要一步到位。很多问题其实是擦除没成功导致烧写时 Flash 里的数据不是全 0xFF校验一比对就报错。先单独执行擦除读回确认全 0xFF 后再烧写能省掉大量时间。第三如果 ST-LINK 连线较长板子供电又偏弱可以在 SWD 时钟频率上降速。GUI 里连接设置一般有频率选项默认自动协商但长线环境下手动降低到 1MHz 以下反而更稳定。这个和 OCTOSPI 的频率不是一个概念但经常被忽略。6. 一些值得长期记住的工程经验6.1 版本统一比个人技巧更重要排查完 U5G9 这个问题后我第一个反思是团队版本一致性。STM32CubeProgrammer、STM32CubeMX、ST-LINK 固件、甚至外部 loader 文件任何一项版本不一致都会复现出不同的现象。一个人的经验如果建立在特定版本上换一个环境就不适用了这是嵌入式开发的常态。建议在工程文档里固定一套版本组合并写明兼容的 .stldr 来源。新成员加入时先按照文档安装工具而不是让每个人自己找最新版。尤其对外部 Flash 烧录这种强依赖 loader 的流程一个团队最好共用同一个 ExternalLoader 目录不要各存各的。6.2 硬件设计时给 OSPI Flash 留条后路这次排查最耗时间的物理层问题如果在原理图设计阶段就规避掉后面能省很多事。我的建议是WP# 和 HOLD# 默认接上拉到 VCC调试时可以通过跳线帽临时断开。Flash 的 RESET# 用 MCU 的 GPIO 控制便于软件复位 Flash而不是强制断电。给 Flash 的供电单独加磁珠或去耦电容减少其他外设带来的电源噪声。在原理图中标注清楚 OCTOSPI 数据线用的 AF 号和实际封装引脚方便写 loader 时对照。这些不算复杂但能极大降低量产阶段烧录不良的返工成本。6.3 量产烧录建议走 CLI 脚本量产阶段不要用 GUI 手工烧录一是效率低二是容易误操作。CLI 脚本的好处是把加载 loader、擦除、烧写、校验、读回都固化成标准流程换任何人都能一键执行。脚本里最好加上前置检查比如先读取目标型号不等于 STM32U5G9xx 就直接退出避免烧错板卡。我的一个量产脚本大致长这样STM32_Programmer_CLI -c portSWD modeUR resetHWrst -ob displ if errorlevel 1 exit /b 1 STM32_Programmer_CLI -c portSWD modeUR -el loader.stldr -e 0x70000000 0x800000 STM32_Programmer_CLI -c portSWD modeUR -el loader.stldr -w firmware.bin 0x70000000 -v每次烧录完成后把日志保存下来万一出问题可以追溯是哪一步失败的。这套流程跑通之后真的能少接很多售后电话。6.4 关于工具下载再啰嗦一句回到很多人搜索的STM32CubeProgrammer 下载地址问题。如果你在某个第三方下载站看到了所谓绿色版完整版我的建议仍然是避而远之。烧录工具直接操作芯片固件安全性要求很高一次非官方包导致的驱动缺失、loader 目录不全、甚至捆绑恶意软件损失的时间远超省下来的那点下载步骤。官方渠道需要注册但也就是几分钟的事换来的是可靠和安心。如果实在无法访问官网也可以通过 STM32CubeMX 的嵌入式软件包管理器、或者已安装的 STM32CubeIDE 自带版本获取这些都是 ST 官方发布的渠道。回到 OCTOSPI 问题本身我最后想强调一点U5G9 的外挂 Octal Flash 烧录本质是对芯片 板级 loader 工具版本这三者匹配关系的验证。工具报错只是表面现象根因通常藏在更底层。把链路逐段打通比盲目重装工具、换电脑、换调试器有效得多。希望这篇记录能帮你少走几个弯路。