资讯动态

STM32N6启动链路拆解:BootROM校验、FSBL签名与Flash烧录避坑

发布时间:2026/11/6 8:06:47 来源:尧图企业网站定制
简介《LAT1491 STM32N6的FSBL基本流程介绍》是一份面向ARM架构MCU开发者与嵌入式技术爱好者的PDF技术文档聚焦STM32N6这颗带NPU的MCU在BootROM之后如何进入FSBL帮助读者理解Dev boot、Flash boot/Serial boot等启动模式及工程落地方式。文档系统梳理仅启动FSBL、FSBL加载并运行应用、FSBL配合XiP三种流程并说明用ST Signing Tool为二进制镜像签名、生成合法Header的要点末尾以IAR与STM32N6 DK板示例项目串起创建、部署和注意事项便于对照实操与排错。资源包共1个PDF文件大小约2.07MB图示围绕启动流程展开适合作为查阅型笔记。目前已有162人学习可供希望掌握STM32N6新启动体系的工程师快速建立整体认知。1. 少数把 NPU 塞进 MCU 的芯片偏偏没有片内用户 FlashSTM32N6 把 NPU 集成进了 MCU 封装但它同时做了一件在传统单片机开发里相当反直觉的事片内没有给用户代码留 Flash。上电后 CPU 执行的第一条指令来自一小块出厂固化的 ROM也就是 BootROM。它能做的事情很有限读几个 Boot 引脚、校验外部 Flash 里镜像开头那 1024 字节 Header、把通过校验的 First Stage Boot LoaderFSBL搬进内部 SRAM2 的 0x34180000然后 PC 跳过去。也就是说从 CPU 启动模式的引脚电平到 Binary Signing 打出来的 Header再到 Flash Programming 时写进去的地址整条链路上横着一个必须签名、必须放对位置、必须小于 512KB 的中间层。搞不清这个中间层你会遇到的现象非常统一编译成功、烧录成功、复位之后什么都没发生。下面就把这条链路拆开。2. 启动模式与外部 FlashBootROM 究竟在校验什么2.1 Boot1 引脚划出的两个世界dev boot 与 Flash boot开发板上一根 Boot1 引脚把芯片的运行方式劈成了两半。Boot1 拉高进 dev bootBootROM 上电后不去碰外部 Flash而是把控制权交给内部 RAM等调试器或烧录工具把代码塞进来再跑Boot1 拉低、Boot0 也拉低进 Flash bootBootROM 才会去外部 Flash 的固定位置读镜像、校验 Header、搬运 FSBL。这两种模式的差别远不止「从哪加载」这么表面它决定了你调试阶段和量产阶段跑的根本不是同一条代码路径。dev boot 可以理解成一种「外挂生命维持」状态芯片自身的启动逻辑被跳过调试器通过 SWD 直接把 bin 写进 SRAM然后改 PC、改 MSP、开跑。这条路完全绕过了 Header 校验所以你在 IDE 里单步、打断点、看变量全都正常。很多人在这个阶段产生了错觉以为工程已经「通了」直到把 Boot1 拨回 Flash boot才发现板子一声不吭。Flash boot 则是「自力更生」没有任何外部工具介入BootROM 手里只有固化在 ROM 里的一小段代码和一把或一组校验依据。它必须仅凭外部 Flash 里的内容判断「这块镜像能不能信、该搬到哪、搬多少」。这个判断的全部输入就是镜像最前面的那 1024 字节。Serial boot 是第三种入口通过 USB 或 U(S)ART 把镜像送进来适合板子还没法烧 Flash 的产线初始阶段但它的带宽和可靠性决定了它只能做兜底不能做常规启动方式。注意切换 Boot 模式之后必须重新上电或硬复位单纯按一下复位键在部分板子上并不会重新采样 Boot 引脚。2.2 1024 字节 HeaderBootROM 眼中的「合法镜像」长什么样BootROM 是掩膜在硅片里的出厂之后一个字节都改不了。它没有文件系统、没有解析器、没有容错逻辑只能按一个死板的格式去读固定偏移上的字段。所以镜像的前 1024 字节不是「元信息」这么轻描淡写的东西它是 BootROM 与外部世界之间唯一的接口约定。签名工具干的事情本质上就是在这段 bin 前面拼出这 1024 字节。字段由谁写入BootROM 拿它做什么镜像标识Magic签名工具快速判定「这是不是一块我能认的镜像」认不出就直接停Header Version-hv参数与芯片 Cut 版本对齐版本不匹配则拒绝Option Flags-of参数携带启动选项位决定后续加载行为镜像长度工具按 bin 实际大小填决定从外部 Flash 搬多少字节进 SRAM入口地址 / 向量信息工具或链接结果校验通过后 PC 的落点完整性校验摘要工具计算判断镜像在 Flash 里是否被改坏、烧录是否完整这张表里最容易被忽略的是「镜像长度」和「完整性校验摘要」两行。长度字段决定了 BootROM 的搬运边界如果你的 bin 在烧录时被截断长度字段和实际内容对不上校验就会失败现象是复位后卡死且没有任何串口输出连错误码都看不到这也是「黑匣子」感的来源。校验摘要则解释了为什么改一个字节都不行有人手工在 bin 尾部补了几个字节想做对齐结果整个镜像校验不过查了半天以为是 Flash 坏了。还有一层容易误判Header 里的入口地址和链接地址是两回事。Header 记录的是「跳转到哪」链接脚本决定的是「代码认为自己在哪」。这两者不一致时程序能跳进去但一执行函数调用或访问全局变量就飞。后面 2.3 节讲 SRAM2 固定地址时这个坑会再出现一次。2.3 SRAM2 的 0x34180000为什么搬运地址是写死的BootROM 校验通过后把 FSBL 搬到内部 SRAM2 的 0x34180000 并跳转。这个地址是固定的不是「建议值」。原因不难推BootROM 自己运行时也要占用 RAM栈、临时缓冲、校验过程的中间状态都得有地方放它必须预留出一块自己不会踩到的区域同时固定地址让 ROM 代码足够简单不需要任何地址协商机制。代价就转移到了开发者这边FSBL 工程的链接脚本必须把代码段落到这个地址上否则 BootROM 搬过去的是「一段自认为在别处的代码」。/* FSBL 链接脚本里最关键的一行代码必须落在 BootROM 搬运的目标地址 */ MEMORY { /* 起始地址与 BootROM 的搬运目标严格一致长度受 512KB 上限约束 */ SRAM2 (rwx) : ORIGIN 0x34180000, LENGTH 512K } SECTIONS { .text : { *(.text*) *(.rodata*) } SRAM2 .data : { *(.data*) } SRAM2 .bss : { *(.bss*) *(COMMON) } SRAM2 }这段脚本的作用是把整个 FSBL 的代码、只读数据、已初始化数据和未初始化数据全部收进 SRAM2 这一段。参数上唯一需要盯死的是ORIGIN它必须等于 BootROM 的搬运目标写错一个 nibble 就会得到「能启动但立刻 HardFault」的经典症状。LENGTH写 512K 是给链接器一个上界超过它会直接报 overflow比运行期静默截断要好得多。这里有个常见的实现细节值得说清楚FSBL 被搬运时是以 bin 形态整体复制的bin 里不包含任何地址信息也不包含「.bss 需要清零」这种语义。所以 FSBL 自己的启动代码必须在main之前把 .bss 清掉、把 .data 从加载地址复制到运行地址——如果你的链接脚本让 .data 的 LMA 和 VMA 不同这段复制代码必须自己写。很多人从带内部 Flash 的 MCU 迁移过来习惯了这个活由启动文件自动完成换到 N6 上就翻车。3. 三种启动架构FSBL-only、LoadRun、XiP 到底差在哪3.1 FSBL 单工程最小的启动闭环最简单的架构是把应用直接编进 FSBL 工程里整个系统只有一个 bin、一次签名、一次烧录。SDK 里Projects\STM32N6570-DK\Examples\UART\UART_HyperTerminal_IT这个例子就是这种形态编译出来一个工程签名之后烧到外部 FlashBootROM 校验、搬运、跳转应用就跑起来了。开发阶段用 IAR 或 Keil 直接下载到 RAM 调试量产后换成签名加 Flash 烧录两条路径共用同一份代码。这种架构的好处是链路最短出问题时排查面也最小要么 BootROM 没认 Header要么 FSBL 自己跑飞没有第三段代码可以甩锅。代价是它被 512KB 这条线死死卡住。一旦应用膨胀到超过 BootROM 的搬运上限这个架构立刻失效你必须拆成两段。所以它适合的是那些功能收敛、代码量可控的验证性工程和最小系统 Demo不适合作为产品架构的长期方案。另外要注意单工程模式下 FSBL 和应用共享同一个栈和同一片 RAM 布局。开发时在 IDE 里看到的一切正常是因为调试器帮你把 RAM 初始化好了真正从 Flash 冷启动时栈指针来自向量表第一项向量表位置又取决于你是否重定位了 VTOR。这个差异在 3.2 节会变成必须显式处理的步骤。3.2 FSBL LoadRun把 code 和 data 一起搬进内部 SRAM应用超过 512KB 之后标准解法是把 FSBL 和应用拆成两个独立工程。FSBL 保持精简只做搬运工应用完整地躺在外部 Flash 里由 FSBL 负责把它整段复制到内部 SRAM然后跳过去执行。这就是 Load Run。它和 3.1 的关键区别在于BootROM 只搬 FSBL应用是第二级搬运的对象而这一次搬运的规则完全由你写。/* FSBL 里典型的 Load Run 搬运逻辑示意 */ #define APP_FLASH_BASE 0x70080000U /* 应用在外部 Flash 中的起始地址 */ #define APP_SRAM_BASE 0x34000000U /* 应用被搬运到的内部 SRAM 起始地址 */ #define APP_HDR_SIZE 0x40U /* 应用自定义头存长度和入口 */ void fsbl_load_and_run(void) { uint32_t app_size *(volatile uint32_t *)(APP_FLASH_BASE 4U); uint32_t app_entry *(volatile uint32_t *)(APP_FLASH_BASE 8U); /* 整段复制应用镜像必须是「可直接搬到运行地址执行」的形态 */ memcpy((void *)APP_SRAM_BASE, (const void *)(APP_FLASH_BASE APP_HDR_SIZE), app_size); /* 搬运完必须让缓存失效否则 CPU 可能取到搬运前的旧指令 */ SCB_InvalidateICache(); SCB_InvalidateDCache(); /* 设置新栈顶、重定向向量表再跳到应用入口 */ __set_MSP(*(volatile uint32_t *)APP_SRAM_BASE); SCB-VTOR APP_SRAM_BASE; ((void (*)(void))app_entry)(); }参数上最需要理解的是APP_SRAM_BASE的选择它不能和 FSBL 自己占用的 SRAM2 那段重叠否则memcpy会把正在执行的 FSBL 代码覆盖掉现象是复制到一半就 HardFault看起来像「随机崩溃」。第二个是SCB_InvalidateICache()与SCB_InvalidateDCache()这两行——Cortex-M55 有数据缓存和指令缓存memcpy写进去的数据可能还压在 D-Cache 里没落到 SRAMCPU 取指又可能命中 I-Cache 里的旧内容。这两行不是可选的保险删掉之后在小镜像上可能侥幸能跑镜像一大就必然出问题属于典型的玄学 bug 来源。第三段__set_MSP加VTOR重定向也不能省。应用有自己的向量表、自己的栈顶如果沿用 FSBL 的 MSP应用一进中断就会踩到 FSBL 的栈帧如果 VTOR 没改中断向量还是指向 FSBL 的异常处理函数症状是「主循环跑得好好的一进串口中断就死」。这两个点合起来是 Load Run 架构里血泪经验最集中的地方。3.3 FSBL XiP把外部 Flash 直接当成代码段XiPExecute in Place走的是另一条路不复制直接执行。FSBL 执行完之后把外部 Flash 对应的 XSPI 控制器配置成 memory-mapped 模式让 0x70000000 起始的一段地址空间在 CPU 看来和普通内存没有区别然后 PC 直接跳到应用的第一条指令。省下的是那一次memcpy的时间和一整块 SRAM 占用代价是每次取指都要走外部总线和 Flash 的读时序。这里的关键在「配置 XSPI」这四个字上它不是打开一个开关就完事。memory-mapped 模式需要设定读命令、地址字节数、dummy cycles空转周期、以及 Flash 侧的等待时序。dummy cycles 设少了读回来的指令数据错位程序行为会变得完全不可预测设多了性能白白损失。此外还要考虑 Flash 的跨页读取行为和缓存策略——指令缓存命中时和从 SRAM 执行没区别未命中时延时就暴露出来了。XiP 还有一个隐含约束应用在链接时必须按 XiP 地址0x70000000 加偏移来链接而不是按 SRAM 地址。这意味着 3.2 和 3.3 两种架构下同一个应用源码需要两套链接配置编译产物不能互换。很多人在两种模式之间切换时忘了改链接脚本得到一个「能烧进去、跳过去就 HardFault」的镜像然后回头怀疑签名——排查方向一错半天就没了。3.4 512KB 这个数从哪来BootROM 的搬运上限为什么单工程方案会卡在 512KB因为 BootROM 只负责一次搬运它预留的搬运能力和校验缓冲有固定上限这个上限是 512K 字节。这是硬件链路里的硬性限制无法通过调参数绕过也不是 Flash 容量或者 SRAM 容量的问题。判断方法很直接看 FSBL 工程编译出来的 bin 大小贴到边界上就该考虑拆架构了。超过之后的处理方式就是把 FSBL 做薄。FSBL 只保留「初始化 XSPI、读应用头、搬运、跳转」这几件事代码量通常只有几十 KB应用的全部内容放到外部 Flash 的另一段区域由 FSBL 按自己的节奏分段搬运。这样不仅绕过了 512KB还带来一个额外好处FSBL 可以做得足够稳甚至可以在这里加应用完整性校验、加双备份切换、加版本回滚这类「后悔药」机制而这些逻辑放在应用里是做不到的——应用自己都还没被加载起来。提示拆分架构之后要同步更新签名流程。BootROM 只校验 FSBL 那一个 bin 的 Header应用 bin 不需要签名除非你在 FSBL 里自己做了校验但两者的烧录地址必须严格错开不能重叠。4. 镜像签名机制那 1024 字节是怎么被拼出来的4.1 签名命令的每个参数在改什么签名这件事听起来很重落到操作上其实就一条命令。但这条命令里每个参数都对应 Header 里的一个字段改错任何一个症状都是「烧录成功、复位无反应」且没有任何报错信息只能靠-dump把 Header 打印出来逐个比对。# Cut 1.1 芯片使用的签名命令 STM32MP_SigningTool_CLI.exe \ -bin FSBL.bin \ # 输入编译器产出的原始 bin没有任何头部 -nk \ # no keys不加密钥签名只做 Header 封装 -of 0x80000000 \ # option flags启动选项位组合 -t fsbl \ # 镜像类型声明为 fsbl -o FSBL-trusted.bin \ # 输出带 1024 字节 Header 的可启动镜像 -hv 2.1 \ # header version必须与芯片 Cut 版本匹配 -dump FSBL-trusted.bin # 把生成后的 Header 打印出来供核对 # Cut 2.0 芯片只改一处header version 换成 2.3 STM32MP_SigningTool_CLI.exe \ -bin FSBL.bin -nk -of 0x80000000 -t fsbl \ -o FSBL-trusted.bin -hv 2.3 -dump FSBL-trusted.bin-nk这个参数值得单独说。它的含义是 no keys也就是不使用密钥相关选项工具只做 Header 封装和完整性摘要不做非对称签名。这在开发阶段完全够用因为 BootROM 此时验的是「格式对不对、内容完不完整」不是「你是谁签的」。如果产品需要绑定密钥就得换成带 key 的参数组公钥的哈希也要一并写进 Header那是另一套流程。-of 0x80000000是 option flags官方示例给的就是这个值直接照抄最稳。这一位的具体语义由工具的版本和芯片 Cut 共同定义自己猜着改的风险极高——改错之后 Header 依然能生成、依然能烧进去但 BootROM 会用另一套加载策略去处理表现为「明明签名成功了却起不来」。-dump是这条命令里最有价值的部分它把生成后的 Header 直接打出来。养成签名后立刻 dump 一遍、核对 version、长度、入口地址的习惯能省掉大量「烧录成功但不启动」的排查时间。4.2 Cut 版本与 header version2.1 和 2.3 不能混用同一颗型号的芯片不同 Cut 版本对 Header 格式的要求不一样。Cut 1.1 对应-hv 2.1Cut 2.0 对应-hv 2.3。这个对应关系是硬的Header version 与芯片 Cut 不匹配时BootROM 直接判定镜像非法不搬家、不跳转、不给串口输出。Cut 版本从哪里看最直接的办法是看芯片表面的 Rev Code 标记或者在工具连接芯片时回读到的 revision 信息。麻烦在于很多人手上有多块板子、多个批次的样片混着用的时候很容易拿 Cut 1.1 的命令去签 Cut 2.0 的板子。现象非常迷惑同一份源码、同一个工程、同一套签名脚本在 A 板上跑得好好的换到 B 板就不启动。如果没有「Cut 版本」这个概念你会往 Flash 型号、焊接、供电方向查一查就是半天。工程实践上的建议是把 header version 做成编译或脚本里的一个变量跟着板子批次走而不是硬编码在某个批处理文件里。批量生产时这一点尤其重要因为签错版本的镜像烧进去之后产线测试工位只会报「不启动」不会告诉你原因。4.3 调试阶段不用签名所以它骗过了很多人这条必须单独拎出来说用 IDE 在 dev boot 模式下调试时工程不需要签名。代码由调试器直接写进 RAMPC 直接跳过去BootROM 那套 Header 校验根本没被触发。于是「编译、下载、单步、串口有输出」这一整套流程跑通给人一种工程已经完成的错觉。真正的分水岭在切换到 Flash boot 的那一刻。此时唯一能依赖的只有外部 Flash 里的内容而 BootROM 只认带合法 Header 的镜像。所以完整的交付流程一定是编译出 bin → 签名生成 trusted bin → 通过 External loader 烧到外部 Flash 的指定地址 → Boot 引脚切到 Flash boot → 上电。缺任何一步现象都是静默不启动。这也是为什么很多团队会把「签名」写进构建脚本的最后一环而不是留成一个人工步骤。人工步骤在赶进度的时候一定会被跳过而跳过的代价是现场返工。5. 烧录避坑从 dev boot 切到 Flash boot 的高频翻车点5.1 现象hex 烧进去了切到 Flash boot 就是不启动原因几乎总是地址。编译器产出的 hex 文件里带着链接地址而这个地址默认指向内部 RAM不是外部 Flash 的 0x70000000。用 CubeProgrammer 直接下载 hex工具会老老实实按 hex 里记录的地址往 RAM 里写Flash 上一个字节都没变。切到 Flash boot 后 BootROM 去读外部 Flash读到的是空白自然不启动。解决办法是烧 bin 并显式指定地址或者把 hex 的烧录地址手动改成外部 Flash 的映射地址。前一种更稳因为 bin 本身不带地址写哪完全由命令行决定不存在「工具自作主张」的空间。# 命令行烧录注意最后的地址参数这是 bin 唯一的落点依据 STM32_Programmer_CLI -c portSWD modeHOTPLUG ap1 \ -el $DKEL \ # External loader由环境变量指向对应板型的 loader -hardRst \ # 烧录前后各做一次硬复位保证 Flash 处于可控状态 -w FSBL-trusted.bin 0x70000000 # 写入文件并指定目标地址-el后面跟的是 External loader 的标识开发板出厂时自带自制板需要自己实现。-hardRst不能省外部 Flash 在异常状态下可能拒绝写命令加硬复位能让每次烧录从确定的状态开始。5.2 现象命令行烧录报「找不到 loader」或直接失败原因是没有把 loader 标识和工具路径准备好。-el $DKEL里的$DKEL是一个环境变量里面存的是 loader 的名字或路径Linux 下用$DKEL、Windows 下用%DKEL%同时STM32_Programmer_CLI所在目录要加进 PATH否则命令行根本找不到这个可执行文件。解决办法是把这两个环境变量配好并重启终端。自制板没有现成 loader 时需要先实现一个能操作目标 Flash 的 loader 程序——这一步没有捷径因为工具不知道你的 Flash 用什么命令字、什么时序。5.3 现象调试器连不上或者连上了但程序跑不起来原因是 Reset 方式选错了。IDE 的调试配置里 Reset 方式必须设为 Software否则调试器尝试用硬件复位信号去接管芯片而 N6 在部分启动模式下的复位行为与调试器的预期不一致结果就是连接握手失败或者连上之后立刻断开。解决办法是改完 Reset 方式之后先退出调试会话重新下载、重新启动调试而不是在已连接的会话里直接改配置。改完不重启会话配置不会生效你会以为是配置项没找对。5.4 现象签名明明成功了Header 也对就是不启动原因通常是 Header version 和芯片 Cut 不匹配或者 Boot 引脚没切回来。前者在 4.2 节讲过后者更常见——烧录时板子处于 dev boot 模式烧完忘了把 Boot1 拨回 Flash boot直接按复位芯片还是从 RAM 启动RAM 里是空的。解决办法是建立一个固定的收尾动作烧录 → 断电 → 拨 Boot 引脚 → 上电。-hardRst只能复位芯片改不了引脚电平这两件事必须分开做。5.5 现象想在线调试 Flash boot 起来的程序attach 不上这条跟 Cut 版本有关。Cut 1.1 的 Flash 启动模式并没有关闭调试口可以用 attach to running target 的方式挂上去看现场换到更新的 Cut 之后这个行为可能变化。所以「能不能 attach」不能当作通用能力来设计调试流程。解决办法是把日志和状态寄存器当作主要观测手段串口打印在 FSBL 阶段就要初始化好让它成为启动链路上第一个能说话的东西。attach 能挂上是运气串口有输出才是可靠信息。6. 设计取舍两级引导换来了什么又拿走了什么把启动拆成 BootROM FSBL 两段本质上是一次「用复杂度换灵活性」的交易。芯片厂商放弃了在片内放用户 Flash换来的是更小的 die 面积、更低的成本以及让用户按需选择外部 Flash 容量和类型的自由。对做图像、做音频、跑轻量模型的场景来说这个自由很值——内部 Flash 那点容量根本不够放模型权重。但代价被完整地转移到了启动链路上多了一级搬运、多了一个必须正确的 1024 字节 Header、多了一个必须自己实现的 External loader、多了一条 512KB 的硬边界。这条边界反过来塑造了应用的架构。凡是代码量有增长预期的项目最终都会走到 FSBL 应用两段式而一旦分成两段向量表重定向、栈切换、缓存失效这几件事就从「启动文件帮我做」变成了「我必须自己做而且要按正确顺序做」。这也是为什么从传统 MCU 迁移过来的开发者第一次在 N6 上做 Load Run 时几乎必然要在缓存那一行上栽一次。签名机制的取舍同样明显。-nk让开发阶段的门槛降到最低一条命令就能出可启动镜像但真正的密钥绑定、防回滚、防降级这些能力需要额外的密钥管理和版本策略那套东西一旦引入构建流程和产线流程都要跟着改。选择在哪一步引入取决于产品对固件完整性的要求等级而不是技术上能不能做。验证这套链路是否真的通了我的做法是三步走先用-dump确认 Header 字段逐个正确再用串口在 FSBL 的第一行和跳转前各打一条日志最后把 Boot 引脚切换后断电冷启动三次。三次都稳定才算通过只成功一次就收工往往是把时序或缓存问题留给了现场。从那以后我每次改链接脚本或者换 Cut 版本都强制把这三步重走一遍不再相信「上次是好的」这种判断。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑