news 2026/11/2 12:27:36

STM32N6 FSBL启动全流程:BootROM校验、镜像签名与Flash烧录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32N6 FSBL启动全流程:BootROM校验、镜像签名与Flash烧录

简介:围绕STM32N6启动体系与FSBL机制展开的技术文档,面向从事ARM架构MCU开发、希望掌握ST首颗带NPU芯片启动流程、镜像签名与工程配置的工程师及技术爱好者。内容从片内BootROM以及Dev boot、Flash boot/Serial boot模式切入,系统梳理仅运行FSBL、FSBL加载并运行应用、外部Flash原地执行(XiP)三类启动路径,并解释BootROM对FSBL头部的校验、搬运地址、PC跳转和512KB拷贝上限等关键约束。文档还介绍ST Signing Tool对二进制镜像进行签名的方法,区分Cut版本的命令参数与header查看方式,并以IAR+STM32N6 DK板示例工程串起项目创建、下载部署和注意事项,便于读者建立从启动模式判断到工程调试、问题排查的完整思路。资源包内含1个PDF文件,约2.07MB,篇幅紧凑,适合按启动流程和工程框架查阅。已有162人学习。

1. 上电之后那几毫秒:STM32N6 的 FSBL 到底在干什么

板子焊好、程序烧完、串口一个字节都不吐,Debug 里单步却一切正常——这是我第一次碰 STM32N6 时最懵的一个下午。问题不在代码逻辑,在这颗芯片的启动模型:片内只有一小块 ROM 用来放 BootROM,用户代码必须落在外部 Flash,中间还夹着一层叫 FSBL(First Stage Boot Loader,第一阶段引导加载器)的东西。它要先被签名、被 BootROM 校验、被搬进内部 SRAM,才有资格去拉起你的应用。这篇按我自己跑通的顺序,把 STM32N6 的 FSBL 从 CPU 启动模式、三种启动链路、Binary Signing 签名,一路拆到 Flash Programming 把镜像写进外部 Flash 的完整过程,适合手里已经有 DK 或 Nucleo 板、准备把工程从 RAM 调试搬到 Flash 启动的嵌入式开发者。

2. 三种启动链路:FSBL 单体、Load&Run 与 XiP 的取舍

BootROM 是固化在片内 ROM 里的一小段程序,上电第一件事就是它跑起来。它做三件事:根据 BOOT 引脚判断走 dev boot 还是 flash boot,读取目标区域的镜像头做校验,校验通过后把镜像搬到内部 SRAM 并把 PC 跳过去。所以真正决定「你的代码能不能跑」的,不是编译器,而是这段你改不了的 ROM 代码。它认的规则很死:镜像头必须合法,目标地址必须对得上,搬运长度不能超过它自己的能力上限。

开发板上的 BOOT1 引脚决定了两种大模式。Flash boot(Boot1 = 0、Boot0 = 0)意味着 BootROM 去外部 Flash 找应用,如果那片 Flash 是空的或者头不对,芯片就直接停在 ROM 里,表现就是「上电没反应」。Dev boot 则是 BootROM 尝试从内部 RAM 启动,这时候你用调试器把代码下载进 RAM 就能跑,不需要签名、不需要外部 Flash。搞不清这两个模式的区别,后面所有调试都会变成玄学。

2.1 FSBL 单体:1024 字节 Header 与 0x34180000

最简的一条链路是 FSBL 工程里直接把应用代码也编进去,只烧一个 bin。BootROM 启动后先验证 FSBL 的头——就是镜像最前面那 1024 字节的 Header 信息。这段头是用官方脚本对 FSBL 做签名时生成的,不签名就没有合法头,BootROM 会直接拒绝。校验通过后,BootROM 把整个 FSBL 程序搬到内部 SRAM2 的 0x34180000,然后 PC 指针跳过去开始执行 FSBL,而 FSBL 里就是你的 main。

这里有个很容易被忽略的硬约束:既然 BootROM 固定往 0x34180000 搬,那你的链接脚本就必须让向量表落在这个地址上。用 IAR 的话,.icf 里要显式改,不能沿用模板默认值。我一般第一件事就是把链接地址钉死:

/* FSBL 的向量表必须落在 BootROM 的搬运目标地址,否则跳转后取向量直接飞 */ define symbol __ICFEDIT_intvec_start__ = 0x34180000; define symbol __ICFEDIT_region_RAM_start__ = 0x34180000; define symbol __ICFEDIT_region_RAM_end__ = 0x341FFFFF; /* 堆栈单独划一段,给 DMA 缓冲和 FSBL 自己的临时缓冲留出余量 */ define symbol __ICFEDIT_region_STACK_start__ = 0x341FF000; define symbol __ICFEDIT_region_STACK_end__ = 0x341FFFFF;

__ICFEDIT_intvec_start__决定中断向量表的绝对地址,region_RAM_start__决定代码段起点,两者必须一致。region_RAM_end__我习惯留一点余量,不要顶到 SRAM2 的末尾,因为 FSBL 运行时还要放栈和堆。如果你用的是 GCC 的 .ld,对应改MEMORY里的RAM (rx) : ORIGIN = 0x34180000,效果一样。改完一定要在 map 文件里确认__vector_table真的落在 0x34180000,而不是被链接器优化到别处。

这条链路的优点是简单,一个工程、一次编译、一次烧录,不用维护两份镜像。缺点是所有东西都挤在这一个 bin 里,而 BootROM 的搬运能力是有天花板的,这个天花板后面会讲到。

2.2 FSBL + Load&Run:把 code 和 data 一起搬进 SRAM

工程一大,单体 FSBL 就不够用了,这时候要拆成两个工程:FSBL 和 APP。FSBL 的职责变成「搬运工 + 跳板」——它从外部 Flash 里把 APP 的完整内容读出来,包括 .text 代码段和 .data 数据段,统统拷到内部 SRAM 的指定位置,然后再跳转过去执行。

跳转这个动作看着简单,实际有几个必须做对的步骤,漏一个就是 HardFault:

/* APP 镜像在内部 SRAM 的落点,由 FSBL 和 APP 两边的链接脚本共同约定 */ #define APP_BASE_ADDR 0x34000000U typedef void (*app_entry_t)(void); void fsbl_jump_to_app(uint32_t app_base) { /* 向量表第 0 项是初始 MSP,第 1 项是 Reset_Handler 地址 */ uint32_t app_sp = *(volatile uint32_t *)(app_base); uint32_t app_pc = *(volatile uint32_t *)(app_base + 4U); /* 跳转前必须关中断,否则旧向量表还可能被 SysTick 之类的打断 */ __disable_irq(); /* 反初始化 FSBL 用过的外设,尤其是 SysTick 和已经开着的串口 */ SysTick->CTRL = 0U; SysTick->LOAD = 0U; SysTick->VAL = 0U; /* 把向量表基址交给 APP,这一步不做,APP 里的中断全会跳到 FSBL 的表 */ SCB->VTOR = app_base; /* 切栈:MSP 换成 APP 的初始栈顶,再跳 Reset_Handler */ __set_MSP(app_sp); ((app_entry_t)app_pc)(); }

app_base是 APP 镜像被搬运到的 SRAM 起始地址,必须和 APP 工程编译时的链接地址完全一致,两边对不上就是取到垃圾指令。SCB->VTOR这一行是把向量表基址重定位,APP 在 SRAM 里跑,中断向量表也跟着在 SRAM 里,不重定位的话中断会跳回 FSBL 的老表。__set_MSP必须在最后调用,因为一旦换了栈,函数里的局部变量就全废了,所以它后面不能再有任何依赖栈的操作。

搬运本身我一般不用裸memcpy处理整个镜像,而是让 FSBL 带一张段描述表,逐段搬:源地址、目的地址、长度三个字段,循环处理。这样 APP 的链接脚本改了地址,只要同步改表,FSBL 的搬运逻辑不用动。数据从外部 Flash 读的时候要走 External Loader 背后的那套 Flash 驱动,直接按地址读是不行的,因为此时 Flash 还没被配置成内存映射模式。

2.3 FSBL + XiP:让 PC 直接指到外部 Flash

第三种是 XiP(Execute in Place),思路完全不同:FSBL 不再搬 APP 的代码,而是把外部 Flash 控制器配置成内存映射(memory-mapped)模式,让那片 Flash 变成 CPU 可以直接取指的空间,然后 PC 直接跳到 Flash 里 APP 的第一条指令。

这条链路在代码体积大的场景下几乎是唯一选择,因为 SRAM 装不下,而 XiP 只占用少量 RAM 做数据段和栈。代价是执行速度受外部 Flash 的读时序和 XCACHE 配置影响很大。配置不到位的时候,程序能跑但慢得离谱,或者偶发取指错误——这类问题特别难查,因为表现像是随机的。

我踩过的具体坑是:FSBL 里把 Flash 切到内存映射之后,忘了同步配置缓存和预取,APP 跑起来功能正常,但一开中断就偶尔 HardFault。后来把 XCACHE 的配置放在跳转之前、并且在跳转后立刻 invalidate 一次,问题消失。另一件事是 APP 工程的链接地址必须改成外部 Flash 的映射地址(示例里是 0x70000000 这一带),不能再按 SRAM 编,否则编出来的绝对地址全是错的。向量表同理,SCB->VTOR要设成 Flash 里的向量表地址。

还有一点容易忘:XiP 模式下如果 APP 需要写外部 Flash(比如存参数),必须先退出内存映射模式再擦写,写完再切回去,否则写入会失败甚至把 Flash 状态机搞乱。这个切换逻辑放在 FSBL 里做不了,得在 APP 里自己封一层。

2.4 512KB 上限与架构选择

选哪种架构不是风格问题,是硬约束问题。BootROM 单次能拷贝的镜像有大小上限,公开的说法是最大 512KB 字节。超过这个数,单体 FSBL 就搬不动了,必须拆成 2.2 或 2.3 的架构,由 FSBL 自己分多次搬或者干脆不搬。

架构适用场景主要约束
FSBL 单体Demo、功能验证、代码小于 512KB超过 512KB BootROM 搬不动
FSBL + Load&Run中等体积、要求执行确定性高占用内部 SRAM,APP 体积受 SRAM 限制
FSBL + XiP大体积应用、SRAM 紧张依赖外部 Flash 时序与缓存配置,调试难度高

我自己的判断顺序是:先算 APP 的 bin 有多大,超过 512KB 直接排除单体;再看内部 SRAM 放不放得下 code + data,放得下优先 Load&Run,因为执行速度可预期;放不下才上 XiP,并且提前把 Flash 时序和缓存的验证做在前面,别等联调的时候才发现。

3. 签名与镜像头:1024 字节 Header 决定了能不能启动

调试阶段一切正常、切成 Flash 启动就死,十有八九是签名没做。BootROM 只认带合法头的镜像,这个头不是编译器生成的,是签名工具在你编完 bin 之后加上去的。整个流程里这一步最容易被跳过,因为它在 IDE 之外,编译按钮不会提醒你。

签名前有个前置条件:本机要装好官方的编程工具套件,并把签名工具STM32MP_SigningTool_CLI.exe所在目录加进 Windows 的 PATH 环境变量。不加 PATH 的话,命令行里敲这个名字会提示找不到程序,很多人卡在这一步以为是工具没装。加完之后新开一个终端验证一下,能直接敲出帮助信息就说明通了。

3.1 一条命令加上 1024 字节头

签名的核心就是一条命令,参数不多但每个都有讲究:

# Cut 2.0 的芯片,header version 用 2.3;Cut 1.1 用 2.1 STM32MP_SigningTool_CLI.exe \ -bin FSBL.bin \ -nk \ -of 0x80000000 \ -t fsbl \ -hv 2.3 \ -o FSBL-trusted.bin \ -dump FSBL-trusted.bin

-bin指向编译器产出的原始 bin,也就是没有头的裸镜像。-nk表示 no keys,不使用密钥相关选项,开发阶段都用这个;如果后面要做安全启动再换成带密钥的参数。-of 0x80000000是 option flags,这个值按官方示例填,别自己改。-t fsbl声明镜像类型是 FSBL,工具要靠这个决定头的布局,填错了生成的头顶不到作用。-hv 2.3是 header version,和芯片的 Cut 版本绑定,下一节细说。-o是输出文件名,习惯上叫xxx-trusted.bin。-dump会把生成的头信息打印出来,方便核对。

跑完之后目录里会多出FSBL-trusted.bin,它的体积比FSBL.bin大,大的部分就是那 1024 字节的头加上对齐填充。拿ls -l或者文件属性对比一下两个文件的大小,差值合理就说明头加上了。这一步是最廉价的验证,比烧进去再重启快得多。

3.2 Cut 版本对 header version:hv 传错就是白干

芯片有 Cut 版本之分,不同 Cut 对应的 header version 不一样。Cut 1.1 用-hv 2.1,Cut 2.0 用-hv 2.3。传错的直接后果是 BootROM 校验失败,芯片停在 ROM 里,串口什么都不输出,你甚至会怀疑是不是硬件坏了。

芯片 Cut 版本header version 参数备注
Cut 1.1-hv 2.1Flash 启动模式下调试口未关闭,可 attach
Cut 2.0-hv 2.3需按新版本头格式签名

确认 Cut 版本的办法有两个:看芯片丝印上的 revision code,或者直接跑一次-dump,把生成的头打出来看里面的 Rev Code 字段。我一般两种都做一遍,丝印确认物理器件,dump 确认工具认到的版本,两边对上了才继续往下走。曾经有一次产线拿错料,丝印是旧版,我按新版签的名,折腾了半天才反应过来,从那以后签名前必看 dump。

3.3 dump 出来该核对哪几个字段

-dump打印出来的头信息不是给你看着玩的,它是排错的第一手材料。我通常核三样东西:header version 是否和芯片 Cut 匹配;payload 长度是否等于原始FSBL.bin的字节数,对不上说明输入文件错了或者工具链中间出了问题;入口地址是否是预期的那个地址,如果是 0 或者明显不对的值,说明链接阶段就有问题,签名只是把错误固化下来。

这三项里最容易出问题的是 payload 长度。如果你在签名之后又改代码重新编译了一次,但忘了重新签名,烧进去的还是旧头配新体,长度对不上,BootROM 校验直接拒绝。这种「改了代码没重签」的低级错误,在赶进度的时候特别高频。

3.4 签名只对 Flash 启动生效

这条边界必须记牢:dev boot 模式下调试,完全不需要签名。调试器把代码直接下进 RAM,BootROM 根本不参与校验,你改一行编一行跑一行,效率很高。一旦把 BOOT 引脚切到 Flash boot,规则立刻变了,BootROM 只认签名过的镜像。

所以正常的工作节奏是:开发期一直在 dev boot 下调试,功能稳定了再走一遍「编译 → 签名 → 烧录到外部 Flash → 切 Flash boot → 复位」的完整流程做验证。别在调试阶段就去折腾签名,纯属浪费时间;也别指望跳过签名能靠「改个配置」绕过,ROM 里的校验逻辑改不了。

4. 从 IAR 工程到外部 Flash:一次完整的跑通记录

理论讲完,落到实际操作。这一节以 IAR + DK 板为例走一遍,示例工程在 SDK 的Projects\STM32N6570-DK\Examples\UART\UART_HyperTerminal_IT目录下。这个 demo 的框架是单体 FSBL,应用和 FSBL 在同一个工程里,对应前面 2.1 的架构。选它当第一个跑通的例子很合适,因为不涉及双工程的地址约定,变量少。

4.1 先把板子切到 Dev boot

Dev boot 是开发的默认状态,BootROM 启动后会直接执行 RAM 里的代码。DK 板和 Nucleo 板进入 dev boot 的方式不一样,都是通过板上的 BOOT 跳线或拨码组合实现,具体位置看板子丝印。切好之后上电,芯片就在等调试器把代码送进 RAM。

这一步别偷懒。我见过有人拿着还在 Flash boot 状态的板子反复下载调试,每次都失败,然后开始怀疑调试器驱动、怀疑工程配置,实际上只是跳线没拨。板子状态是第一个要确认的东西,比任何软件配置都优先。

4.2 工程选项四连:Device、ST-LINK、Output、Debugger

打开工程后按顺序检查四个地方。第一是 Device,确认器件型号列表里能选到 STM32N6 系列,如果列表里根本没有,说明器件支持包没装,先去装补丁。第二是调试器配置,选 ST-LINK,接口和速率用默认值即可。第三是 Output 设置,确认输出格式包含 bin,因为签名工具吃的是 bin 不是 hex 也不是 elf。第四是 Debugger 里的 Reset 方式。

这四项里 Reset 方式最容易被忽略也最致命。必须设成 Software,否则调试器复位之后芯片状态不对,程序跑不起来或者一进去就 HardFault。设完之后要退出调试会话再重新进一次,配置才真正生效。这个细节我第一次遇到时调了快一个小时,以为是工程本身的问题。

4.3 编译前确认补丁包

用 IAR 或 Keil 的话,编译前必须确认 N6 相关的器件补丁包已经打上;用 CubeIDE 的话则是把 IDE 版本更新到支持 N6 的版本。补丁包就在 SDK 目录里,路径不深,翻一下就能找到。没打补丁的典型症状是编译报找不到器件定义、或者链接阶段报内存区域不存在——不是代码问题,是工具链不认识这颗芯片。

补丁装好之后用默认编译选项编译就行,不需要额外优化开关。第一次跑通的目标是「能起来」,不是「跑得快」,优化留到后面再说。

4.4 调试:这个阶段不需要签名

前面说过,调试阶段不用签名。工程编完直接点调试,代码下进 RAM,点运行,串口应该能打出预期信息。如果串口没输出,先确认串口线接的是不是 demo 里配置的那个实例,再看波特率。这个 demo 用的是中断方式收发的串口例子,跑通了就说明工具链、板子、调试器三者都正常,可以进入下一步。

调试跑通这件事本身价值很大:它把「代码逻辑问题」和「启动链路问题」彻底分开了。后面切到 Flash 启动如果失败,你就可以笃定不是应用代码写错了。

4.5 烧录到外部 Flash:命令行和 CubeProgrammer 两条路

烧录前先确认工程性质。这个 demo 是 FSBL 工程,所以要按 3.1 的流程先签名,得到FSBL-trusted.bin,然后才能往外部 Flash 写。烧录需要 External Loader,DK 板和 Nucleo 板自带对应的 loader 程序;如果是自己画的板子,得先自己开发 loader,这部分工作量不小,官方有单独的文档讲,这里不展开。

命令行方式适合脚本化和批量操作。准备工作有三步:板子切到 dev 模式;把 External Loader 的路径设成环境变量DKEL;把STM32_Programmer_CLI所在目录加进 PATH。然后执行:

# Linux:DKEL 指向 DK 板对应的 External Loader 文件 export DKEL=/opt/loader/DK_External_Loader.stldr STM32_Programmer_CLI -c port=SWD mode=HOTPLUG ap=1 \ -el "$DKEL" \ -hardRst \ -w FSBL-trusted.bin 0x70000000
:: Windows 下同样的命令,注意环境变量展开用 %% set DKEL=C:\loader\DK_External_Loader.stldr STM32_Programmer_CLI -c port=SWD mode=HOTPLUG ap=1 -el %DKEL% -hardRst -w FSBL-trusted.bin 0x70000000

-c port=SWD指定用 SWD 接口,mode=HOTPLUG表示热插拔连接,ap=1选访问端口。-el指定 External Loader,这个 loader 决定了工具怎么跟板子上的外部 Flash 通信,选错 loader 会报 flash 识别失败。-hardRst让工具在烧录前后做硬件复位。-w是写入,后面跟文件名和目标地址,这里的0x70000000是外部 Flash 的映射起始地址,必须显式给。

提示:烧 bin 一定要带地址,烧 hex 则要提前确认 hex 里的地址段。工程编译出来的 hex 默认地址通常在 RAM,直接烧进外部 Flash 会写错地方。

图形化方式用 CubeProgrammer:板子切 dev 模式后连上,先选对应的 External Loader,然后下载。下载 hex 的话工具会按 hex 里的地址段写;下载 bin 的话必须在界面上手动指定起始地址,填 0x70000000。两种方式效果一样,区别只在于 hex 自带地址信息、bin 需要你告诉它。

4.6 切回 Flash boot 并复位

烧录完成后,把 BOOT 跳线切到从外部 Flash 启动的位置,然后复位。这时候 BootROM 会去外部 Flash 读镜像、校验头、搬到 SRAM、跳转,整个链路就跑起来了。如果前面每一步都验证过,这一次复位应该和调试时表现一致。

有个细节值得记一下:在某些 Cut 版本上,Flash 启动模式并没有关闭调试口,可以用 attach to running target 的方式挂上去调试。这对定位「只在 Flash 启动时出现」的问题很有用,因为你能看到真实的运行状态,而不是靠串口打印猜。

5. 避坑与排查:这几个地方最容易翻车

这一节列的都是我自己或者身边人真实踩过的,按「现象 → 原因 → 解决」整理。这些问题有个共同点:单看每一步都对,串起来就崩,所以排查时不要只盯一个环节。

5.1 镜像与签名相关的坑

现象一:调试器里跑得好好的,切成 Flash 启动后串口一个字都不出,芯片像死了一样。

原因是镜像没签名,或者签名用的 header version 和芯片 Cut 不匹配。BootROM 校验头失败后不会给任何提示,它就是停在 ROM 里,从外部看和没供电差不多。解决方式是回到 3.1,用-dump把生成的头打出来,确认 header version 和丝印上的 Cut 版本一致,确认 payload 长度等于原始 bin 的大小。还有一种是改完代码忘了重新签名,头还是旧的,长度对不上,同样校验失败。

现象二:签名命令跑成功了,输出文件也生成了,但体积和原始 bin 一模一样,看不出加了头。

这种情况一般是-o的输出路径写错,或者输出到了别的目录,你看的那个文件其实是旧的。也可能是命令里某个参数被 shell 吃掉了。解决方式是先ls -l对比两个文件的时间戳和大小,时间戳不是刚刚就说明没写到你以为了的位置。养成签名后立刻比对体积的习惯,能省掉大量无效烧录。

现象三:同一个 bin,在 A 板上能启动,换到 B 板就不行。

先别怀疑硬件。如果两块板的芯片 Cut 版本不同,那 header version 就得换。前面说过 Cut 1.1 用 2.1、Cut 2.0 用 2.3,混用就是一边能跑一边不能跑。确认办法还是看丝印加 dump。另一种可能是两块板的外部 Flash 型号不同,起始行为和时序不一样,这时候要看 External Loader 是不是匹配对应的板子。

5.2 烧录与调试相关的坑

现象四:烧录提示成功,复位后不启动;把同样的 hex 烧回去也不行。

最典型的原因是烧错了地址。工程编出来的 hex 默认地址段在 RAM,你直接烧进外部 Flash,工具是按 hex 里记的地址写的,写进去的位置根本不是 BootROM 会去读的地方。解决方式是烧 bin 并显式指定 0x70000000,或者改工程的链接配置让 hex 的地址段落在外部 Flash 映射区再烧。这一条在文档里被反复提醒,因为它的迷惑性太强——工具报成功,芯片不启动,中间没有任何错误信息。

现象五:点调试进不去,或者一进去就停在异常处理里。

先查 Debugger 里的 Reset 方式,必须是 Software。默认的硬件复位在这颗芯片上不合适,复位后外设和 Flash 控制器状态不对,程序跑飞。改完配置之后一定要退出调试会话再重进,配置改完不退出是不生效的。第二个要查的是板子是不是还在 Flash boot 模式,调试要切回 dev boot。第三个查器件补丁包装没装,没装的话链接出来的地址映射都可能是错的。

现象六:XiP 模式下功能正常,但一开中断就偶发 HardFault,或者整体运行明显偏慢。

这不是一个原因造成的。偶发 HardFault 通常是向量表没重定位或者缓存没同步,检查SCB->VTOR是否指向 Flash 里的向量表,跳转前后是否做了缓存 invalidate。运行偏慢则要去看外部 Flash 的读时序配置和预取、缓存开关,配置保守了速度上不去,配置激进了会读错数据。我一般是先把时序按 Flash 手册的保守值配好,确认功能全通,再逐项放开缓存和预取,每改一项跑一遍压力测试,出问题能立刻定位到是哪一项。

注意:这六条里有四条都和「地址」有关——链接地址、烧录地址、向量表地址、搬运目标地址。排查 FLASH 启动类问题时,先把这四个地址逐个对齐,比读代码快得多。

6. 二次开发:把这套流程固化成自己的板子与量产脚本

把这套东西搬到自己的板子上,工作量主要在三个地方:External Loader、链接脚本、以及签名烧录的自动化。

External Loader 是第一个门槛。官方板自带 loader,自己画的板子得按实际使用的外部 Flash 型号写一个,实现初始化、擦除、写入、校验这几个回调,然后编成工具能识别的格式。这部分不难但烦,时序参数抄错一位就会表现为「偶尔写失败」。我的做法是先拿厂商提供的 Flash 驱动跑通裸读写,确认时序没问题,再包成 loader,这样出问题时能分清是驱动问题还是 loader 框架问题。

链接脚本要按架构分两套。FSBL 那套钉死在 0x34180000,APP 那套按你选的架构来:Load&Run 就指到 SRAM 的某个空闲段,XiP 就指到外部 Flash 映射区。两边的地址约定我一般写成一个共享头文件,FSBL 和 APP 都 include 它,改地址只改一处,避免两边不同步这种低级错误。

最值得投入的是把「编译 → 签名 → 烧录 → 校验」串成一个脚本。手工做这四步,漏一步就要重来一轮,尤其是量产出货的时候。脚本的核心就是把签名和烧录两条命令参数化:

#!/usr/bin/env bash # 用法:./build_flash.sh <cut版本 1.1|2.0> <bin路径> set -euo pipefail CUT="$1" BIN="$2" HV="2.3" [ "$CUT" = "1.1" ] && HV="2.1" # 1. 签名,生成带 1024 字节头的镜像 STM32MP_SigningTool_CLI -bin "$BIN" -nk -of 0x80000000 -t fsbl \ -hv "$HV" -o "${BIN%.bin}-trusted.bin" -dump "${BIN%.bin}-trusted.bin" # 2. 校验签名前后体积差,差值异常直接退出,不往下烧 ORIG=$(stat -c%s "$BIN") SIGNED=$(stat -c%s "${BIN%.bin}-trusted.bin") if [ "$SIGNED" -le "$ORIG" ]; then echo "签名输出体积异常,检查工具参数" >&2 exit 1 fi # 3. 烧录到外部 Flash 映射区起始地址 STM32_Programmer_CLI -c port=SWD mode=HOTPLUG ap=1 \ -el "$DKEL" -hardRst -w "${BIN%.bin}-trusted.bin" 0x70000000

set -euo pipefail保证任何一步失败就停,不会带着半成品继续往下走。HV根据传入的 Cut 版本自动切换,避免人工选错。体积校验那一段是关键,它把「签名没生效」这个最常见的错误拦在烧录之前——烧一次要几十秒,签名只要一秒,把检查放在前面永远划算。$DKEL从环境变量读,换板子只改环境变量不改脚本。

验证方法我固定用三步:先看-dump输出的头字段是否和预期一致;再烧录后不切模式,用 attach 挂上去确认 PC 已经跳到 FSBL 的入口;最后切 Flash boot 复位,看串口输出和功能是否完整。三步都过才算这颗板子没问题。

血泪经验是:脚本里千万别用「上一条命令成功了就认为一切正常」这种思路。签名工具返回 0 不代表头是对的,烧录工具返回 0 不代表写到了正确地址,这两件事我都吃过亏。从那以后我每次改完链接脚本或者换芯片 Cut 版本,都强制走一遍 dump 核对加体积比对,确认无误再烧。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 12:27:26

企业网站的核心价值与现代化构建策略

1. 企业网站的现状与挑战在社交媒体和电商平台盛行的今天&#xff0c;很多企业主都在思考一个问题&#xff1a;我们是否还需要维护一个独立的企业网站&#xff1f;数据显示&#xff0c;2023年全球网站数量突破18亿&#xff0c;但其中约75%处于闲置状态。这种矛盾现象背后&#…

作者头像 李华
网站建设 2026/11/2 12:25:28

OpenClaw开源自动化工具部署与优化指南

1. OpenClaw 项目概述 OpenClaw 是一款新兴的开源自动化工具平台&#xff0c;它通过模块化设计整合了任务编排、消息处理、模型集成等核心功能。作为一个全栈解决方案&#xff0c;它特别适合需要对接多种消息渠道&#xff08;如微信、飞书&#xff09;并集成AI能力的开发场景。…

作者头像 李华
网站建设 2026/11/2 12:22:36

验证码组件技术解析:从基础实现到高级安全策略

1. 验证码组件的核心价值与应用场景验证码组件是现代互联网应用中不可或缺的安全防线&#xff0c;它像一位不知疲倦的门卫&#xff0c;24小时守护着系统的入口。我在过去五年参与过17个不同规模的项目&#xff0c;无一例外都需要验证码机制。从简单的数字验证到复杂的滑块拼图&…

作者头像 李华
网站建设 2026/11/2 12:22:04

Vue3与Element Plus对话框保存按钮状态控制实战

1. Vue3与Element Plus对话框保存按钮状态控制实战在Web应用开发中&#xff0c;对话框表单的保存按钮状态管理是个看似简单却容易踩坑的细节。最近在重构一个后台管理系统时&#xff0c;我遇到了这样的需求&#xff1a;当对话框打开时自动禁用保存按钮&#xff1b;数据加载过程…

作者头像 李华
网站建设 2026/11/2 12:19:49

Java面向对象编程:重载与重写的本质区别

1. 重载与重写的本质区别 在面向对象编程中&#xff0c;方法重载&#xff08;Overloading&#xff09;和方法重写&#xff08;Overriding&#xff09;是两个容易混淆但本质完全不同的概念。我刚开始学习Java时&#xff0c;经常把这两者搞混&#xff0c;直到在实际项目中踩过几次…

作者头像 李华
网站建设 2026/11/2 12:18:29

MATLAB光学衍射仿真:从物理建模到可验证图像的完整实现

简介&#xff1a;本资源是一份面向高校物理光学课程学习者与MATLAB初学者的衍射现象仿真教学资料&#xff0c;聚焦夫琅和费衍射核心实验的编程实现与可视化分析。内容系统涵盖衍射积分法&#xff08;单缝衍射、杨氏双缝干涉、黑白光栅衍射&#xff09;与傅立叶变换法&#xff0…

作者头像 李华