STM32U5G9单.hex烧录:固件与选项字节合并实战指南
2026/8/30 20:31:06 网站建设 项目流程

上周,产线朋友把一版固件退回给我,附了一条消息:你这 hex 只烧了 flash,没有选项字节,我们上板后 RDP 还是 level 0。我愣了一下,因为默认 CubeProgrammer 里 GUI 下载选项字节一直成功,总觉得它跟着工程走了。其实不然——如果你把产物发给别人或者丢给工厂脚本,很多情况下 hex 就是 hex,选项字节是单独配置件。这次刚好新项目用了 STM32U5G9,要交付单文件 .hex,同时包含固件和选项字节,索性把整个链路完整跑了一遍。

这篇文章就把整条链路的原理、工具链和坑都整理出来,适合正在做 STM32U5 量产烧录、或者给客户交付固件包的人参考,尤其是你们也遇到“对方只收一个 hex”这种情况。我会从 Intel HEX 结构讲起,然后给出三种生成单 .hex 的方式,最后附上我在 STM32U5G9 上验证和量产时踩过的坑。

核心结论先放在前面:单 .hex 不是把固件 hex 和选项字节文本文件拼起来,而是让一个 Intel HEX 文件里同时包含两个不连续的内存区段,一个是用户闪存区(0x08000000 起始),另一个是选项字节区(STM32U5 系列通常在 0x0BF90000 起始)。工具链能识别这两个区段,并在烧录时按不同策略处理,才算真正成功。

1. 为什么需要单 .hex:交付场景里的两种误区

1.1 “能烧进去”和“单文件交付”是两回事

很多人在自己开发板上烧录时,是用 STM32CubeProgrammer 的图形界面,先加载固件 hex,再切到 Option Bytes 页签手动勾几个选项,点一下 Apply,板子就跑起来了。这种操作方式非常常见,也最容易造成一个错觉:选项字节已经被打进固件包里了。

实际上,图形界面的每次操作都是独立的。固件 .hex 文件本身没有发生任何变化,选项字节只是通过调试器的内存写接口写进了芯片的专用区域。你把那个 .hex 单独发给别人,对方烧进去,选项字节自然是默认值。

另外还有一种误区是:把固件 .hex 和选项字节 .ob 文件一起打包成 zip,就对外宣称“单包交付”。严格来说这不算错误,工厂那边用脚本也能烧,但是多人交接、版本管理、扫码追溯的时候,两个文件就多出两个同步问题:固件匹配哪一版选项字节?RDP 等级改了没有?如果能把固件和选项字节放到同一个 Intel HEX 文件里,整个交付物就是一个文件,校验和、版本号、烧录记录都围绕这一个文件转,误操作的概率会低很多。

1.2 STM32U5G9 的存储布局和烧录特殊性

STM32U5G9 属于 STM32U5 系列的高端型号,Cortex-M33 内核,带 TrustZone,闪存容量可以到 4MB 量级,双 Bank 结构,内存映射布局和以前 F1/F4 很不一样。

和单 .hex 合成直接相关的地址区域主要有三块:

区域起始地址说明
用户闪存 Bank10x08000000常规固件存放区
用户闪存 Bank20x08100000双 Bank 时的第二块固件区
选项字节区0x0BF90000选项字节的实际内存映射地址
OTP 区0x0BFA0000一次性可编程区域

注意选项字节那个地址并不是“寄存器”,而是映射到芯片存储空间的一个区域。你可以在 STM32CubeProgrammer 的 Memory 面板里直接敲 0x0BF90000 进去看内容,也可以尝试把它当内存一样写入。但内部实现上,选项字节使用的是电子熔丝/专用存储单元,写入方式和普通 Flash 不一样。

所以有一点很重要:不要把 0x0BF90000 当成普通 Flash 那样想怎么写就怎么写。它有自己的对齐规则、补码校验机制和编程时序,最好的办法是让官方工具或者官方库函数去操作,而不是自己手工拼一长串字节。

1.3 工厂为什么特别喜欢单 .hex

我在配合代工厂做程序烧录时发现,工厂的自动化测试台最喜欢的就是单文件方案。它们的流程通常是:扫码枪扫下 PCB 条码,自动加载对应固件,烧录,校验,打标,测试。如果固件包是多个文件,烧录脚本里就要多写一段逻辑去判断哪个 .ob 配哪个 .hex,一旦某条产线更新了选项字节而另一条没更新,问题就会在出货后才暴露。

单 .hex 方案下,产线脚本只需要一个参数:文件路径。固件和选项字节作为一个整体被校验和约束,哪怕操作员拷错文件,校验环节也能立刻发现。对客户交付也一样,一个文件传过去,对方打开就能烧,减少了大量沟通成本。

2. 合成前必须吃透 Intel HEX 的“非连续地址”能力

2.1 扩展线性地址记录才是单文件合成的关键

Intel HEX 并不是只能保存一段连续地址的数据。它用记录类型04(Extended Linear Address Record)来指定后续数据记录的高 16 位地址。举个例子:

:0200000408009A :10000000112233445566778899AABBCCDDEEFF00F2 :00000001FF

第一行:020000040800中,02表示后面有 2 个数据字节,0000是记录地址(固定为 0),04是记录类型,接下来0800就是高 16 位地址,意思是后续数据记录的完整地址是0x0800xxxx。第二行是普通数据记录,地址0000,合成完整地址就是0x08000000。最后一行:00000001FF是 EOF 记录。

如果要在一个文件里同时放 0x08000000 的固件和 0x0BF90000 的选项字节,只需要在适当位置再插入一条扩展线性地址记录:

:0200000408009A :10000000112233445566778899AABBCCDDEEFF00F2 :020000040BF98B :10000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 :00000001FF

第二条:020000040BF9就把基地址切到了0x0BF9xxxx,后面:10开头那条就是写到选项字节区的数据。

原理不复杂,但很多工具解析这类混合 hex 时处理方式不同。有的烧录器会自动识别所有分区,有的只会把第一个扩展线性地址之后的数据当固件,后面的直接忽略。这就是为什么合成之后还必须在真实芯片上做一遍完整烧录验证。

2.2 工具对选项字节区域的识别逻辑

ST-Link、J-Link、STM32CubeProgrammer 这类调试工具,操作带地址映射的存储区域时,通常会根据地址范围去判断目标区域类型。地址落在 0x08000000 到 0x081FFFFF 之间,就按用户 Flash 方式写入;地址落在 0x0BF90000 附近,会走选项字节编程流程,并触发对应的 option byte reload。

但并不是所有烧录器都这样。有些第三方烧录器只支持普通 Flash 编程,不处理选项字节区的特殊时序,哪怕你给的 .hex 里带了 0x0BF9 段,它也会报错。所以在给工厂选型时,一定要提前确认烧录器固件版本是否支持 STM32U5 的选项字节编程,而不是等到了量产阶段才去试。

2.3 先通过工具确认目标芯片的 OB 基址

不同系列的选项字节基址不一样。STM32F103 是 0x1FFFF800,STM32L4 系列是 0x1FFF7800,STM32U5 系列我这边测到的是 0x0BF90000。保险起见,拿到新片子时先连接 STM32CubeProgrammer,跑一下命令:

STM32_Programmer_CLI -c port=SWD mode=UR -ob read

它会打印当前芯片的选项字节寄存器值,同时也能从输出里看到工具识别出来的选项字节区域。建议把这段输出保存下来,作为后续合成 hex 时的核对基准。

3. STM32U5G9 选项字节里到底有什么,要写哪些

3.1 常用字段速查表

STM32U5 的选项字节比 F1 复杂得多,它不仅仅是 RDP 和 BOOT0 那么简单。我列一下 U5G9 上比较常用、也确实会影响固件运行的字段:

字段用途典型设置
RDP读保护等级0xAA=Level 0,0xBB=Level 1,0xCC=Level 2
nBOOT0硬件 BOOT0 引脚使能0=启用引脚,1=禁用
nBOOT1启动介质选择与 BOOT0 组合
SWAP_BANKBank 交换0=不交换,1=交换
SECWM1_START/ENDTrustZone 安全区范围按闪存页号配置
HDP隐藏保护区可选,隐蔽指定区域
PCROP专有代码保护防止读出来,注意别把自己的调试区也封了
WPRPN写保护防止意外擦写

RDP 是最容易踩坑的。Level 1 意味着调试器不能再直接读 flash,但还能擦除重新写;Level 2 一旦设置,芯片的调试口和 bootloader 访问都将被永久限制,这是不可逆的。如果你还处于开发调试阶段,千万别在发给别人的 single hex 里直接设置 Level 2,尤其是当整机没有预留离线升级通道时。

3.2 在 CubeProgrammer 里配置出想要的 OB 组合

我的习惯是先在图形界面里把选项字节配置好,再想办法导出成可复用的数据,而不是直接手写寄存器值。具体操作:

  1. 连接目标板,进入 Option Bytes 页签;
  2. 按项目需求设置 RDP、BOOT 配置、安全区边界等;
  3. 点击 Apply,让工具把选项字节写入芯片;
  4. 再切到 Memory 面板,地址栏输入0x0BF90000,读取一段数据;
  5. 确认读回来的值和页签里的设置一致。

这个流程能保证你最终拿到的数据是真实有效、经过芯片确认的,而不是自己脑补的寄存器组合。

3.3 千万不要手动拼原始字节

我曾经为了图省事,想直接在脚本里构造选项字节区域的原始数据,发现 U5G9 的选项字节并不像 F1 那样简单地把数据写在一个地址上。

选项字节区域内部有补码校验机制,某些位还会自动映射到多个存储单元,你手动拼出来的数据看着对,实际烧进去可能触发校验失败。更稳的做法是:

  • 用 CubeProgrammer 的.ob文件作为中间格式;
  • 或者用工具导出该地址范围的原始内存数据;
  • 再把它作为 Intel HEX 的一部分合并到固件里。

记住,STM32U5 的选项字节是电子熔丝形式的专用存储,不是普通 RAM 或者 Flash,你把它当成内存去写地址,本质上是让芯片内部的状态机去完成真正的烧录动作。

4. 生成单 .hex 的三种实操方式

4.1 方案一:CubeProgrammer 图形界面手工合并(适合验证)

这是最接近“所见即所得”的方式,适合第一次跑通流程,确认合成后的 hex 是否满足要求。

先把固件工程编译出 .hex,然后在 CubeProgrammer 中连接芯片,把固件 .hex 下载进去。接着按上一节的方式配置好选项字节并应用,再用 Memory 面板把0x0BF90000区域的数据导出成一个小型 .hex 文件。此时你手上有两个 hex:固件 hex 和选项字节区 hex。

用任意支持 Intel HEX 合成的工具把它们合并。没有工具的话,甚至可以在文本编辑器里操作,但容易出错,不建议。

这个方案本质上是借助芯片本身帮你算出选项字节区的有效原始数据,而不是人肉计算。缺点是必须先有一块真实芯片,没法纯离线生成。

4.2 方案二:srec_cat 命令行合成(适合批量和 CI)

srec_cat 是 SRecord 工具集里的核心命令,专门用来处理各种 hex/bin/s-record 文件,支持合并、切片、偏移、校验和计算。Ubuntu/Debian 下直接apt install srecord,Windows 下也有对应二进制包。

合成命令很简单:

srec_cat firmware.hex -Intel ob_data.hex -Intel -o combined.hex -Intel

其中ob_data.hex是你在 4.1 里导出的选项字节区数据。srec_cat 会保留各自的扩展线性地址,自动生成一个包含两个区段的完整 Intel HEX 文件。

如果想把选项字节数据本身也做成可重复生成的脚本,可以用 srec_cat 从一个二进制文件生成带指定地址的 hex:

srec_cat ob_data.bin -Binary -offset 0x0BF90000 -o ob_data.hex -Intel

这样的话,你的版本库里只需要维护一个“选项字节原始数据.bin”,每次构建固件时顺手生成合并 hex,比到处传 GUI 导出文件规范得多。

srec_cat 还支持生成 CRC 校验记录,可以把整个 hex 的校验和算出来,输出到另一个文件。这个特性在生产追溯里很有用。

4.3 方案三:Python intelhex 定制脚本(适合复杂构建链)

如果你们的固件构建流程已经用 Python 串联,或者需要额外处理 TrustZone 安全区、多 Bank 布局,用 Python 的 intelhex 库更灵活。

下面是我用的一个示例脚本,它会读取固件 hex 和选项字节 bin,按指定地址合并输出:

from intelhex import IntelHex # 读取用户固件 firmware = IntelHex("build/fw.hex") # 读取选项字节原始数据,假设文件里就是映射地址对应的字节序列 with open("build/ob_data.bin", "rb") as f: ob_data = f.read() # 把选项字节写入 0x0BF90000 起始地址 base = 0x0BF90000 for i, b in enumerate(ob_data): firmware[base + i] = b # 输出合并后的单 hex firmware.write_hex_file("output/combined.hex")

如果要从 CubeProgrammer 导出的.ob文件转换成 bin,可以用脚本解析它的键值对,然后填充到偏移地址。每个 STM32 系列的偏移定义都不同,U5 的字段排布也比较复杂,我建议初期还是先用工具导出 bin,脚本只负责拼接。

4.4 三种方式对比

方式优点缺点适合场景
GUI + 手工合并直观,适合验证概念依赖真实芯片,难以重复执行项目初期确认 OB 数据
srec_cat快速、可脚本化、跨平台不能直接生成 OB 原始数据CI 自动化批量构建
Python intelhex灵活,可集成进构建需要维护脚本和地址映射复杂安全区/多Bank工程

我个人最推荐的是 4.2 + 4.3 组合:用 GUI 在新片上生成一次 OB 原始数据,存成 bin,后续所有构建都走 srec_cat 或者 Python 自动合成,不再人工参与。

5. 合成之后怎么验证,尤其是选项字节段

5.1 先在 CubeProgrammer 里打开合成 hex 看分区

合成后的 combined.hex 不要急着烧到量产板上,先在 CubeProgrammer 的编程界面里加载它,然后切到 Memory 区域,看地址分布。正常情况下,你应该能看到 0x08000000 和 0x0BF90000 两个段。

如果你看到的只有 0x08000000,说明工具在解析时把后一段忽略了,这通常是 hex 文件里扩展线性地址记录格式不对,或者某些工具对地址跳跃有特殊限制。这时要回头检查 srec_cat 的输出警告,或者用文本方式打开 hex 确认存在:020000040BF9这一行。

5.2 命令行烧录和回读验证

我通常用命令行烧录来做完整验证:

STM32_Programmer_CLI -c port=SWD mode=UR -d combined.hex -v

-v参数让工具在烧录后执行校验。日志里会显示 flash 写入地址范围和校验状态。如果选项字节段也被正确识别,你会在日志里看到除了 flash 区之外,还有 OB 区的写入动作。

烧完后再读一遍选项字节:

STM32_Programmer_CLI -c port=SWD mode=UR -ob read

对比输出的字段值是否与预期一致。这一步能确认选项字节不是只写进了缓存,而是真的在芯片上生效。

5.3 遇到工具不认 OB 段时怎么处理

如果某些烧录场景实在不识别 hex 里的 OB 段,退而求其次的方案是单独用-ob参数配合.ob文件烧写。例如:

STM32_Programmer_CLI -c port=SWD mode=UR -d firmware.hex -ob ob_config.ob

这样至少能在一个脚本命令里完成所有动作,虽然不是单一 .hex,但比手工操作稳得多。真要交付单 .hex 给客户,就需要提前确认对方用的烧录器型号和固件版本,做一次联合验证。

5.4 回读不一致的常见原因

选项字节区回读结果和预期不一致,最常见的是这几个原因:

  • 烧录时芯片还在 Bank1 和 Bank2 切换状态,OB 未完成 reload;
  • 设置 RDP 后,调试器权限发生变化,读回来的结果被掩码;
  • 目标板电源不稳,导致 OB 编程时序中断;
  • 某些选项字段之间互相约束,比如 SECWM 和 PCROP 冲突,工具没有提示。

遇到不一致,先不要重复烧,复位一下板子,重新连上再读。如果复位后依然不对,用橡皮擦模式先全片擦除,再重新下载。

6. 量产阶段的细节:版本管理、RDP 策略和双 Bank

6.1 把版本信息和完整性校验写进 hex

单 .hex 文件在产线上流转时,靠文件名判断版本不可靠。我建议在固件里预留一个版本信息区,把编译时间、Git commit、构建序号写进去,选项字节区域也可以用固定偏移记录校验值。

这样产线在烧录后可以做一次全片校验,如果版本串不匹配,就会直接报警,避免把上一版本固件当新版本出货。

6.2 RDP 等级应该什么时候设置

如果你需要后期在线升级或者现场调试,量产时别急着设 RDP Level 2。Level 1 能够阻止普通调试器读 flash,又不影响整机升级,是一个比较折中的选择。

但要注意,Level 1 状态下,STM32CubeProgrammer 依然可以通过 sector erase 把芯片变回 Level 0,只是 flash 内容会被清空。这并不会影响工厂重复烧录,反而方便返修。只有当产品对安全要求很高,且完全不需要现场维护时,才考虑 Level 2。

6.3 双 Bank 和 SWAP_BANK 导致的启动异常

STM32U5G9 是双 Bank 结构,如果选项字节里的 SWAP_BANK 和实际固件所在 Bank 不匹配,芯片上电后可能从错误的 Bank 启动,现象就是“固件明明烧进去了,却跑不起来”。

这种坑特别隐蔽,因为固件 hex 烧录地址是对的,编译也没有问题,但一上电就进硬件错误。检查时优先看 SWAP_BANK 是否为 0,再确认 Reset Vector 是否在 Bank1 起始位置。

如果你要做 A/B 升级,就需要刻意把两个 Bank 的固件放好,然后在升级流程中切换 SWAP_BANK。这也不是简单改一个选项字节就能完事,必须考虑固件加载地址、中断向量表地址和启动顺序。

6.4 常见异常快速排查表

异常现象可能原因解决方向
烧录完芯片无法启动SWAP_BANK 不对读 OB,检查 Bank 映射
调试器无法连接RDP Level 2 已启用不可逆,只能换芯片
只能读 flash 不能写RDP Level 1 正常行为用 sector erase 后再写
选项字节总是写不进去电源电压不稳或工具不支持换官方工具,检查供电
固件跳转到其他地址失败TrustZone 安全区配置不当核对 SECWM/PCROP

说一个我在 U5G9 上体会最深的地方:这类芯片的选项字节不是“设一次就完事”的配置,它和固件加载、安全启动、双 Bank 升级都是耦合的。写进单 .hex 只是第一步,真正要保证的是,生产线上每一块板子烧完之后的运行状态,和你手里这块验证板完全一致。

我现在的做法是维护一个构建脚本,每次编译固件时自动从固定版本号的 OB bin 文件生成 combined.hex,再在 CI 里用真实开发板跑一遍烧录回读,比对 OB 关键字段。整个流程稳定跑了一个多月,产线那边再也没来问过“为什么板子烧出来没保护”。如果你们也遇到类似的交付问题,建议先从自己的烧录脚本和版本管理下手,把固件和配置当成一个整体来管理,会比每次手工点界面可靠得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询