资讯动态

STM32固件与Option Bytes合并烧录:一次搞定hex文件生成实战

发布时间:2026/8/31 22:16:12 来源:尧图企业网站定制
你经历过这种时刻吗产线那边催着要一个烧录文件你的STM32U5G9固件编译好了但Option Bytes还没配。你打开STM32CubeProgrammer烧固件是一步设选项字节是另一步两边还不能合并操作。要是遇到SMT代工厂人家只认一个hex你只能干瞪眼。这篇文章就是来解决这个问题的把固件和Option Bytes打进同一个hex文件实现“一次烧录全部到位”。我会用实际可复现的方法讲清楚为什么要合并、hex底层格式怎么处理、工具怎么选、踩过哪些坑适合正在做量产导入或者想优化烧录流程的嵌入式工程师参考。内容不绕弯子直接上干货。1. 为什么要费劲把固件和Option Bytes塞进同一个hex1.1 量产现场的“两道工序”痛点先说说最现实的场景。STM32U5G9这种芯片功能强安全特性也多但它带来的麻烦就是Option Bytes配置项特别丰富。你在开发板上折腾没问题可一到产线问题就来了。传统做法是两道工序先用烧录器把固件写进Flash然后在图形界面里逐项配置Option Bytes再点一次烧录。两道工序意味着两倍的时间、两倍的操作次数、两倍的出错概率。最尴尬的是如果产线工人对“读保护等级”“启动模式”这些概念不熟很容易漏配或错配。我见过一次批量事故某批次板子的nBOOT0没配对整批货上电后全跑进Bootloader最后只能返工。有人会说那我写个脚本用命令行自动配置不就行了问题在于很多代工厂的烧录环节是封闭的人家只接收一个文件不接受“我一会儿再给你发个命令”这种流程。这时候单文件交付就是硬性要求。1.2 什么场景真正需要单文件交付不是所有项目都需要合并但下面这几类场景强烈建议用单文件方案。第一类是委外烧录。尤其是一些中小团队板子交给SMT厂贴片后直接在厂里烧录。代工厂的烧录工位通常只接受一个hex文件你给它两个文件它要么不知道怎么处理要么只能帮你烧一个。把固件和OB合并了省去沟通成本。第二类是现场升级和返修。设备已经发给客户了维修工程师手里只有一个烧录器。如果固件和OB是分开的他得记得先烧哪个、后烧哪个还得知道OB具体值是多少。一个文件发过去烧完就完事。第三类是产品多版本管理。有时候同一块板子通过Option Bytes区分不同产品型号。比如U5G9的读保护等级、BOR阈值或者启动模式在不同版本里有差异。把这些差异固化到hex里每次出文件时只需要对应型号的合并文件就不会出现“固件对了但配置错了”的情况。第四类是安全等级要求高的产品。某些应用需要设置RDP读保护防止固件被读出。如果不把RDP写进hex那就得另外确保产线工人手动设置。合并成一个文件后RDP等级成为固件交付物的一部分可审计、可追踪。1.3 一个hex能带来哪些实际收益最直接的好处是烧录时间缩短。一次连接、一次写入、一次校验整个工序可能从原来的几十秒降到十几秒。别小看这几十秒日产能突破千片的产线这就是巨大的效率提升。其次是可追溯性。固件和OB本身是什么状态完全由hex文件决定。你可以把这些文件归档到版本管理系统里每次改动都有记录。出了问题拿旧的hex就能复现不需要再回忆“当初OB是怎么配的”。再一个是降低人为错误。烧录工具面对一个文件时逻辑是确定的擦除、写入、校验。如果固件和OB分离工具要处理两个文件、可能还有不同的地址和类型逻辑复杂了人也容易犯迷糊。合并后无论是ST-Link、J-Link还是脱机烧录器输入只有一个操作简单很多。当然单文件并不是银弹它要求你对Option Bytes本身有足够的理解。如果配置值本身是错的合并只会让你“错得更快”。所以进入实操前得先把Option Bytes这东西弄清楚。2. 动手前先搞清楚Option Bytes这张“硬件开关图”2.1 什么是Option Bytes和普通Flash数据有什么区别Option Bytes是芯片内部一个独立于主Flash的特殊配置区域。它不像普通Flash那样存储代码和数据而是存储芯片的硬件行为开关。你可以把它类比成电脑的BIOS设置硬盘里装的是系统和软件BIOS里存的是启动顺序、电压、安全策略这些底层参数。两者都在非易失存储里但修改方式和作用完全不同。普通Flash数据可以通过CPU执行代码来写比如你在程序里用Flash接口擦写某个扇区。但Option Bytes不行它需要一套专用的编程序列通常要往某个寄存器组里写入特定值再触发加载命令芯片才会真正把配置更新到对应的物理区域。所以“像写普通Flash一样去写OB”在硬件上是不可能的。对STM32U5G9这种Cortex-M33内核的芯片来说Option Bytes还牵扯到TrustZone等安全特性。比如你可能需要设置TZEN位来决定是否启用TrustZone。这类配置一旦写成后续的调试接口、Flash访问权限都会受影响。这就是为什么OB配置必须严谨不能指望“先随便写后面再改”。2.2 STM32U5G9上关键的选项字节含义具体到STM32U5G9Option Bytes的典型字段包括但不限于以下几类具体偏移和位定义务必以你手里的参考手册为准。我这里列几个常见项方便你建立认知框架。选项字节字段作用配置风险RDPRead Protection设置读保护等级防止调试接口读取Flash等级调高后调试连接困难需整片擦除才能降级BOR_LEVEL设置上电/掉电复位阈值阈值过高可能导致低电压应用无法启动nBOOT0 / nBOOT1设置BOOT0和BOOT1引脚对应的启动模式配置错误会导致代码不跑反而进入系统BootloadernSWBOOT0是否允许软件控制BOOT0与硬件启动策略相关改动需谨慎WWDG_SW窗口看门狗使能方式意外禁用可能导致系统稳定性问题nRST_STOP / nRST_STDBY在低功耗模式下是否产生复位影响低功耗唤醒行为IO_HOLD调试期间是否保持IO状态影响调试与复位时的外部设备状态特别注意STM32的选项字节很多都有“互补位”。比如某个字段叫BOOT0它就一定有个nBOOT0这两个值互为反码。芯片上电时会对正常值和反码做比较如果检测到的值与反码不匹配会认为配置无效甚至回退到默认配置。手动构造OB数据时如果只写正常值不写反码烧进去大概率会出问题。2.3 为什么不能像写普通Flash一样直接写OB从烧录器视角看普通Flash地址范围内的写操作工具会转化为标准的Flash编程指令比如擦除扇区、写入页。而Option Bytes区域的地址在芯片内部映射上属于系统区域它没有对应的扇区也不是你直接用Flash编程命令能改的。当你用STM32CubeProgrammer烧录一个hex文件时它会扫描文件里的地址发现某些地址落在OB映射区域就会自动把这条记录当作“选项字节编程命令”来处理。它背后调用的是芯片的选项字节加载接口流程比你想象的要复杂解锁、写数据、加反码、触发加载、等结果。这就是为什么“合并OB到hex”这件事不是把两段文件内容拼在一起就完事。你生成的hex必须包含正确的OB目标地址并且烧录工具必须识别这个地址并走OB编程流程。如果工具不识别可能直接把数据当成普通Flash内容去写结果就是写入失败严重的会让芯片状态异常。所以动手前先确认两件事第一你的hex烧录工具是否支持OB区域自动识别第二你手头的OB地址映射表准确无误。这两点不搞定后面全白搭。3. 准备工具hex格式与合并不只是“拼文件”3.1 工具链清单合并固件和Option Bytes常用的工具就是下面这几类看你的操作系统和习惯选就行。STM32CubeProgrammer是ST官方的图形化程序同时也提供命令行工具。它最大的价值是内置了OB地址识别能力你给它一个包含OB区域的hex它就能自动处理。而且它还能读取当前芯片的OB状态用来验证合并文件是否正确。SRecord工具包里的srec_cat是一个命令行工具专门用于合并、拆分、转换各种烧录文件格式。它支持Intel HEX、Motorola S19、二进制等格式处理地址扩展、校验和、地址对齐都非常方便。对嵌入式工程师来说这是文件处理的神器。GNU binutils里的arm-none-eabi-objcopy用来把ELF文件转成hex或者bin。它解决的是“固件本身”的转换问题但默认不涉及OB。你需要知道的是编译出来的东西和最终烧录文件之间还差一步这一步通常就是objcopy来完成的。如果喜欢自己掌控一切Python也是个好选择。配合intelhex库你可以方便地解析、合并、校验hex文件。不过我这里更推荐先理解格式本身再用脚本去处理这样出问题时不至于两眼一抹黑。3.2 Intel HEX格式速览记录类型、地址、校验和Intel HEX是一种把二进制数据用ASCII文本表示的文件格式每一行是一条记录格式如下:llaaaatt[dd...]cc其中ll是数据长度aaaa是16位地址tt是记录类型dd...是数据字节cc是校验和。校验和的计算方式是把本行所有字节长度、地址、类型、数据全部相加取低8位再用0x100减去这个值简单说就是“和取反加一”。记录类型里最常见的是00数据记录、01文件结束、02扩展段地址、04扩展线性地址。STM32的Flash地址范围是0x08000000超过16位能表示的0xFFFF因此需要用04号记录来指定高16位地址。合并后的hex文件如果OB地址在0x0BFA0000之类的高地址区同样需要有正确的04记录。很多人在合并hex时只关注数据记录却忽略了扩展地址记录。如果两个文件的扩展地址冲突或者合并工具没处理正确生成的hex里高地址数据可能全乱。所以任何时候都不要手动去拼接hex文本你应该用工具让它们去处理扩展记录和校验和。3.3 从ELF到hexobjcopy的正确姿势在Keil或者STM32CubeIDE里编译完默认会生成hex但如果你用的是命令行编译或者自定义构建脚本就得自己调用objcopy了。arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex这条命令比较简单。但要注意的是默认它只转换ELF文件里识别到的可加载段。如果你希望把Option Bytes也编进链接脚本作为某个段放在OB地址上那么编译器和链接器未必会按你想的来处理因为OB区域并不属于标准的Flash段。我自己的做法是固件部分正常用objcopy转出来OB部分单独用一个小的数据文件生成hex最后用srec_cat合并。这样职责清晰固件是固件OB是OB互不干扰。3.4 手动合并hex的常见误区先列几个我见过的典型错误。用编辑器直接打开两个hex复制粘贴到一起。这几乎必然会出问题因为扩展地址记录可能冲突校验和也要重新算。你不可能手动去改每行校验和。误以为“先固件后OB”就是简单的文件追加。实际上hex文件里的记录顺序并不是最重要的重要的是地址。如果工具按记录顺序处理先写Flash后写OB那没问题。但如果工具先处理OB再处理Flash可能导致安全属性先生效阻碍后续Flash写入。把Option Bytes数据写到了普通Flash地址。比如有人把OB值放在0x08000000偏移的某处以为这样就能在启动时生效。实际上0x08000000是代码区你写进去再多也只是普通数据除非固件里自己去读并加载OB寄存器但这个过程本身也是要有基础配置的。忽略RDP对编程时序的影响。如果OB里设置了高等级读保护烧录器在写入OB之前必须能连接芯片。如果工具先烧了带RDP的OB再想烧Flash可能就会遇到连接失败。正确的做法是让工具在烧Flash之后再写OB或者使用支持这种顺序的烧录流程。4. 实操从零生成单一hex的完整流程4.1 方法A用STM32CubeProgrammer直接导出合并hex这个方法最省心前提是你手上有一块已经调好的样板芯片上面固件和Option Bytes都是对的。先连接设备打开STM32CubeProgrammer在左侧选择“Memory File programming”。在“Program”标签下选择你要烧录的固件hex先烧进去。然后在“Option Bytes”界面把各项配置按你的需求设置好点“Apply”。此时设备已经处于“固件正确、OB正确”的状态。接着用“Read”功能。在“Read”栏里选择要读取的起始地址和长度。如果固件是0x08000000开始的你就把起始地址设为0x08000000长度覆盖固件大小。同时要把OB区域也读进来。如何一次读取完整区域你可以把读取的地址起点设在0x08000000长度一直延伸到包含OB区域或者分两次读取再用工具合并。但CubeProgrammer的“Read”导出功能比较直接在读取选项里选择“All”或者指定范围导出格式选择Intel HEX。重点来了CubeProgrammer导出的hex文件会包含你指定的所有地址范围的内容。如果你读取的范围覆盖了Flash和Option Bytes区域导出后的hex自然就同时包含两者。但需要注意Option Bytes区域在某些芯片上可能不能像普通Flash那样直接读出来如果工具弹出警告你就要检查OB地址映射是否正确。拿到这个导出的hex你就可以把它当作“母片备份”。以后批产量产时直接用这个hex烧录。这种方法的好处是不用手动构造OB数据缺点是必须先有一块样板而且导出时务必确认读取范围完整别漏了地址段。4.2 方法B用srec_cat一行命令合并固件与OB如果不想依赖样板芯片而是想从零构造一个包含正确OB值的hex那就用srec_cat。核心思路是先把固件转成hex再生成一个OB区域的hex最后合并。假设你的固件文件叫firmware.hexOB区域地址是0x0BFA0000再次强调以目标芯片参考手册为准我这里仅作示例你想设置的OB值为两个32位字一个正常值value一个反码~value。这时可以先用一个小工具生成OB的hex文件。例如用Python生成一个包含两个32位字的OB区域import struct def create_option_bytes_hex(filename, base_addr, value): data struct.pack(II, value, ~value 0xFFFFFFFF) addr base_addr lines [] # 每行16字节生成Intel HEX记录 for i in range(0, len(data), 16): chunk data[i:i16] address addr i record :%02X%04X00 % (len(chunk), address) checksum len(chunk) checksum (address 8) 0xFF checksum address 0xFF for b in chunk: record %02X % b checksum b checksum (-checksum) 0xFF record %02X % checksum lines.append(record) lines.append(:00000001FF) with open(filename, w) as f: f.write(\n.join(lines) \n) create_option_bytes_hex(ob.hex, 0x0BFA0000, 0x5555AAAA)这段代码生成了一个包含两个32位字的简洁hex文件。实际应用中你需要根据参考手册把value设置成你想要的选项字节组合。注意不同芯片的选项字节布局很不一样有的字段是16位甚至8位对齐反码的位置也不同千万别照搬这个示例当通用做法。然后使用srec_cat合并srec_cat firmware.hex -intel ob.hex -intel -o combined.hex -intelsrec_cat会自动处理地址扩展记录和行地址还会重新计算校验和。默认输出为32位地址模式。如果你想要更保险可以加参数-address-length4强制32位地址。这个方法的主要工作量在于生成ob.hex。如果OB值比较多建议写一个表格维护字段然后根据字段自动拼接32位字而不是手工算二进制。4.3 方法CPython脚本解析/合并/校验hex如果你想完全掌控合并过程并且不想依赖srec_cat用Python解析hex也不难。我经常用这个方式做CI集成写一个脚本编译完成后自动合并并输出检验信息。一个简单的脚本思路from intelhex import IntelHex # 读取固件 fw IntelHex(firmware.hex) # 读取OB文件 ob IntelHex(ob.hex) # 合并 fw.merge(ob) # 输出合并文件 fw.write_hex_file(combined.hex)intelhex库会自动处理地址重叠、扩展地址记录和校验和。但需要先安装pip install intelhex。如果你不想安装第三方库也可以自己写一个小解析器核心逻辑是读取每一行解析记录类型。如果是00数据记录根据扩展地址04记录合成完整的32位地址把数据存入字典。合并两个文件的字典遇到地址冲突时按规则处理比如OB覆盖固件数据就报错。重新输出hex输出时每行最大16字节计算校验和。这个流程不算复杂但有几个坑地址扩展记录的处理数据记录超过64KB时的分段文件末尾的EOF记录。手动写一遍能加深理解平时用现成库更省心。我建议至少在项目初期用Python脚本打印出合并后的地址段分布确认固件地址和OB地址没有交叉再输出最终文件。4.4 实操后的校验确认合并产物有效合并完不校验等于没做。我用过几个方法效果都不错。第一是直接在文本编辑器或hex查看器里搜索OB区域的起始地址标识。如果你的OB地址是0x0BFA0000那么在hex文件里应该有对应的扩展地址记录02000004 0BFA之类的字节。看不到这个说明OB段没有被正确包含。第二是用srec_cat的-print参数输出地址范围srec_cat combined.hex -intel -print它会列出所有数据段的起始地址和长度一眼就能看出Flash和OB区域是否都在。第三是加载到CubeProgrammer里检查。打开“Option Bytes”页面CubeProgrammer会自动解析hex中的OB段显示对应的字段。如果解析出的值和你预期一致那这个文件基本可用。前提是工具支持从hex中解析OB段并且地址识别正确。最后建议无论用哪个方法找一块工程板烧录后断电重启再读回OB状态确认。这才是终极验证。5. 烧录与排查光有hex还不够5.1 烧录工具如何识别OB区域很多人以为烧录工具只是“把文件里的数据搬进Flash”其实对于OB区域工具必须走一套完全不同的流程。这就是为什么同一个hex用不同工具烧结果可能完全不同。STM32CubeProgrammer是目前对OB支持最好的工具之一。它会分析目标hex如果发现地址落在Option Bytes映射范围就会自动调用OB编程流程。在图形界面烧录时你能看到日志里出现“Programming option bytes”之类的提示这时候它就真的在写OB了。其他烧录器比如J-Flash或者某些脱机烧录器处理方式就不一样了。它们通常需要在软件里配置一个“Option Bytes区域”告诉它哪个地址范围需要特殊处理。如果你不做配置它可能把OB地址当普通Flash地址轻则忽略重则写入失败。所以用第三方烧录器时务必查阅对应烧录器的文档确认它支持STM32U5系列OB编程并且了解如何配置。5.2 烧录顺序与复位行为这是最容易踩坑的地方。OB不像普通Flash写完不会立刻生效通常需要一次复位才会加载到芯片内部寄存器。而RDP这类安全选项它的生效时机更加严格。如果你把固件和OB放在同一个hex里烧录工具一般会先擦写Flash区域再编程OB区域最后产生系统复位让OB生效。这个过程看起来没什么问题但在高等级RDP下工具如果先写了OB再写Flash可能会因为连接被切断导致Flash写入失败。为了避免这种问题很多生产线会把流程拆成两阶段先用不带RDP的hex烧固件和普通OB然后最后阶段再烧一个包含高等级RDP的“封板”hex。这样即使中间出错板子还能修复。如果你的产品对安全要求没那么高不涉及到高等级RDP单文件烧录基本没问题。另一个行为要注意OB配置中如果包含了BOOT0相关设置芯片复位后有可能会进入系统Bootloader而不是用户Flash。烧完后如果看到串口或者调试器异常先别急着怀疑hex格式问题去检查OB里的启动配置。5.3 常见问题与排查思路整理一个表把你最可能遇到的问题和解决思路都列出来现象可能原因解决思路烧录成功但程序没跑nBOOT0/nBOOT1配置错误芯片进入Bootloader读回OB核对启动模式字段烧录器报“Invalid option bytes”OB正常值与反码不匹配确认OB数据里每个字段都带互补位合并后的hex体积异常大地址段跨度太大数据记录里填充了大量0xFF检查地址映射是否写错高地址膨胀烧录后调试器连不上RDP等级被设置成高等级使用“连接under reset”必要时整片擦除OB写入后未生效需要在断电重启后才生效工具没有执行复位或芯片只认上电复位加载手动断电重启或让工具执行复位时序合并时提示地址重叠固件地址和OB地址映射交叉检查链接脚本和OB地址是否正确TrustZone开启后部分Flash无法写入TZEN选项字节已设置安全属性限制访问关闭TZEN或使用符合安全属性的写入命令CubeProgrammer读到的OB和hex不一致烧录工具没有识别OB段或被当作普通Flash跳过确认工具版本检查hex里的扩展地址记录这些坑我大多都踩过有些甚至是模棱两可的“玄学”问题最后发现是地址映射表看错了行。所以遇到问题不要急着改hex先读回芯片实际的OB值一切都以实测为准。6. 我踩过的几个坑最后提醒几句最后这几段我不讲方法论就说亲身经历。我第一次做单文件hex时用的是手动拼接大法结果烧完第一块板子芯片直接“消失”了——调试器怎么都连不上。后来才发现我把RDP等级写成了最高级而且OB地址里恰好没有写入互补位硬件认为配置非法触发了某种保护状态。那次教训让我明白OB数据不是普通数据每个位都需要严格按参考手册来写反码不是“可选项”而是“必选项”。还有一次我用srec_cat合并文件后没有验证地址长度结果OB区域的高地址丢失了。因为默认的地址长度在某些版本里是16位而我的OB地址超出了这个范围。后来我加了-address-length4问题才解决。这个参数看着不起眼但真的能坑人。再一个典型的产线问题操作员烧完一个hex后习惯性地又打开CubeProgrammer点了一次“擦除”结果固件没了OB也恢复默认。后来我统一了流程烧录现场只允许一键烧录脚本不允许单独操作。不要低估流程控制的力量单文件方案能不能起作用一半取决于技术一半取决于管理。所以如果你决定用“固件OB单hex”方案我建议按下面的清单走一遍先在开发板上手动配置好OB验证产品功能正常。用工具读回OB导出成参考文件。用脚本或srec_cat把OB数据与固件合并生成combined.hex。用CubeProgrammer加载验证确认OB解析值与预期一致。至少拿3块板子烧录每块都断电重启、复测OB值、确认功能。把完整的combined.hex纳入版本管理记录对应的OB配置内容。另外如果你的OB里包含RDP等级3这类不可逆设置请务必在生产前做一次“封板”演练。有的团队为了省时间直接把最高等级写入单文件结果出问题后整批板子报废。稳妥的做法是准备两个hex一个普通生产hex一个带高等级RDP的封板hex。最后一道工序再烧封板hex这样前面任何环节出错都能补救。单个hex包含固件和Option Bytes本质上是一种“交付物思维”。它让开发、测试、生产三方拿到的东西是一致的减少了信息传递过程中的损耗。但它对工程师的技术功底有要求尤其是对OB的地址和互补位机制要非常清楚。只要前期准备到位这套方案会成为你量产流程里最省心的一环。

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

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

免费获取报价