
做嵌入式开发的人迟早会被HEX和BIN这两个文件格式缠上。我在STM32项目里最常遇到的场景就是把Keil生成的HEX转成纯净的BIN再交给量产烧录器、IAP升级脚本或者把Bootloader和App合并成整包。刚开始我也图省事用在线转换工具直到项目需要批量出包才发现那些工具既不可控又不安全于是彻底换成了srec_cat。这是srecord工具集里的核心命令行工具能处理Intel HEX、Motorola S-record、BIN等格式之间的转换还支持裁剪、偏移、填充、合并。这篇文章就围绕STM32工程把srec_cat从HEX转BIN的5种实战用法一次性讲清楚。1. 从一次“翻车”说起为什么我要用srec_cat替代在线转换工具1.1 HEX和BIN的差距远不止文件后缀Intel HEX本质是文本文件每一行都携带地址、数据类型、数据长度和校验和。比如Keil默认生成的那些.hex文件打开后能看到大量以冒号开头的ASCII行里面既包含程序区的数据也可能包含写入RAM的初始化数据。BIN则完全不同它是纯粹的内存镜像文件里只有连续字节没有任何地址信息。这一点在STM32上影响非常大。芯片Flash起点通常是0x08000000Keil工程的IROM1地址也默认从这里开始。烧录HEX时烧录工具会按记录里的地址逐条写入烧录BIN时烧录工具只会把文件内容按顺序写到某个固定地址。换句话说同一个固件HEX能自解释BIN必须依赖“烧录地址”这个外部参数。这还不是最麻烦的。HEX文件里可能有多个地址段比如代码区在0x08000000某些RW数据初始化镜像在0x20000000。直接把这些内容转成一个连续BIN文件大小会瞬间变得非常夸张。第一次遇到这个问题时我盯着一个几百MB的BIN文件愣了半天最后才意识到是地址空洞被当成数据补齐了。这也是很多工程师不太愿意碰HEX转BIN的原因。1.2 常用工具链的隐性短板Keil自带的fromelf能生成BIN但它的主职是把AXF转成各种格式。遇到裁剪、填充、多段合并这些需求配置起来很别扭而且分散加载工程里如果存在多个执行域fromelf的输出规则又要额外研究。STM32CubeProgrammer图形界面能导入HEX再导出BIN但一次一个文件没法在CI脚本里批量处理也不方便把Bootloader和App拼到一起。J-Flash也有类似问题GUI操作适合人肉烧录不适合自动化出包。在线转换网页看起来方便但固件往往涉及公司内部代码和产品逻辑传到一个不认识的服务器上风险实在太大。另一方面在线工具对地址空洞、填充值、偏移量的处理基本是黑盒出了问题无从排查。GNU的objcopy也很强但它面向ELF/AXF更自然对Intel HEX的直接操作没有srec_cat来得顺手尤其在多段拼接时srec_cat的流水线式参数明显更直观。所以我最后的结论很明确嵌入式开发里处理HEX、BIN、SREC这种“地址相关文件”srec_cat应该是第一选择。它也许不是最花哨的工具但一定是最适合写进脚本、跑进流水线、让固件打包变成一条命令完成的那一个。2. 开工前先把参数吃透srec_cat的安装与命令结构2.1 两个平台的安装方法Linux下安装非常简单Debian/Ubuntu直接用aptsudo apt install srecord装完验证一下srec_cat -versionWindows下推荐从srecord的SourceForge页面下载预编译包解压到比如C:\srecord然后把C:\srecord加入系统PATH。如果你用MSYS2或者Cygwin也可以直接pacman -S srecord或apt-cyg install srecord。在Keil的User命令里调用时如果PATH没生效就直接写全路径例如C:\srecord\srec_cat.exe。有人会问为什么要特地把安装写出来因为srec_cat的名字实在太容易和某个自动化测试工具混淆而且Windows下解压后如果忘了加PATH后面在Keil里调用就会频繁出现找不到命令的报错。2.2 命令行里的五个高频参数srec_cat的命令行结构和常规工具不太一样它更像一条流水线前面放输入文件中间放过滤和转换操作最后用-o指定输出。在STM32场景里先记住这五个参数就够用了。参数作用STM32典型用途-intel声明输入文件是Intel HEX格式告诉srec_cat以HEX方式解析Keil生成的文件-binary声明输出为纯BIN格式生成不带地址的裸数据镜像-crop start end只保留指定地址区间的数据从整包HEX里提取App或Bootloader-offset 偏移量对所有地址做整体平移把链接地址偏移到相对0地址或RAM地址-fill 值 start end在指定区间内补数据用0xFF填充空洞生成完整Flash镜像另外还有个高频参数是-o它放在最后指定输出文件。之前见过有人把-o写在中间结果srec_cat把它当成输入文件处理报错报得莫名其妙。还有一个容易踩的细节负偏移量的写法。很多人会直接写-offset -0x08004000但在部分srec_cat版本里这样会被解析成两个选项。稳妥的写法是中间加空格写成-offset - 0x08004000。这个细节在后面的用法三里会再提到。3. 五种用法拆解覆盖STM32工程里90%的转换需求3.1 用法一一条命令把HEX转成干净BIN最基础也最常用的场景就是把Keil生成的HEX转成BINsrec_cat STM32F103.hex -intel -binary -o STM32F103.bin这里的-intel是显式告诉srec_cat输入是Intel HEX格式虽然srec_cat也能根据文件后缀猜但显式声明能避免意外。-binary则是输出格式必须写清楚否则默认可能输出成SREC或其它格式。你可以把这条命令理解为读取HEX保留里面所有记录按地址从低到高连续导出生成一个不带地址的裸数据文件。如果工程链接地址是0x08000000HEX里数据从0x08000000到0x0800FFFF那么生成的BIN就是64KB内容就是Flash的真实镜像。这个用法适合开发阶段的快速验证。比如我要把固件丢给同事做测试他手里的烧录工具只认BIN我直接一条命令转过去就行不用让他重新编译工程。这也是srec_cat最简单的形态。但要注意如果HEX里存在多个地址段比如既有Flash地址又有RAM地址直接这样转出来的BIN尺寸会非常夸张。遇到这种工程先看一眼HEX里的地址分布再用下面的用法二或用法四去限定范围。3.2 用法二按地址区间裁剪只提取App或Bootloader很多STM32项目会做IAP升级Flash布局一般是Bootloader在前App在后。比如Bootloader占0x08000000到0x08003FFFApp占0x08004000到0x0800BFFF。生产时想单独发布App升级包就不应该把整个HEX转成BIN而应该裁剪出App区srec_cat STM32_full.hex -intel -crop 0x08004000 0x0800BFFF -binary -o app.bin这条命令的意思是读入STM32_full.hex只保留0x08004000到0x0800BFFF之间的数据然后输出成BIN。最终app.bin的大小是32KB也就是0x8000字节。这里有个关键认知裁剪出来的BIN本身不携带任何地址信息。烧录或传输时你必须额外告诉目标端“这个BIN要放到0x08004000”。如果你的OTA协议里规定App包本身就对应App区那这条命令已经足够如果协议要求App包的起始地址按0计算你就还需要加上用法三的偏移操作。裁剪在固件分包场景里非常有用。我做过一个产品Bootloader和App编译在同一个工程里发布时却要分开给不同产线。用一条-crop就搞定了不用再维护两份工程也避免了App和Boot配置不一致的问题。3.3 用法三做地址偏移输出符合加载地址的BIN有些平台规定升级包里的App数据必须从偏移0开始或者你想把原本链接在0x08004000的App镜像重新放到另一个基地址去运行。这时候就需要-offset。接着上一个例子生成从0开始的App包srec_cat STM32_full.hex -intel -crop 0x08004000 0x0800BFFF -offset - 0x08004000 -binary -o app_relative.bin注意看中间这段-offset - 0x08004000意思是把所有地址整体减去0x08004000。原本在0x08004000的向量表导出后对应BIN里的相对地址0。这个文件在烧录时如果指定基地址0x08004000效果和原始App完全一致。为什么需要这种相对BIN我遇到过两种常见情况一种是OTA协议里为了省流量只传输App相对区的数据另一种是Bootloader会把App从Flash搬到RAM里运行或者跳转前把向量表地址重新映射。相对地址包配合Bootloader里的重映射逻辑能减少很多通信层面的地址换算。同样道理如果你想给App加上一个固定的正偏移可以写-offset 0x08010000把它挪到其它空闲Flash区域。这个操作在做A/B分区升级时尤其常见同一个App既能发布到A区也能通过简单偏移生成对应B区的版本。还要强调一遍负号写法在srec_cat里负偏移的-和数字之间建议加空格写成-offset - 0x08004000。避免命令行解析器把-0x08004000当成新选项这是我自己踩过的坑。3.4 用法四用0xFF填充空洞生成整片Flash镜像STM32的Flash在擦除后所有字节都是0xFF。如果你的工程只烧写了Bootloader和App中间还有空余区间直接用HEX转BIN可能会留下未定义区域也可能让BIN尺寸和实际芯片容量对不上。比如STM32F103C8T6有64KB Flash地址从0x08000000到0x0800FFFF。假设你的工程只占前半段想生成一个“整片64KB的完整镜像”可以用填充srec_cat STM32_app.hex -intel -fill 0xFF 0x08000000 0x0800FFFF -crop 0x08000000 0x0800FFFF -binary -o STM32F103_full.bin这条命令的逻辑是读入HEX在0x08000000到0x0800FFFF这个范围内把没有被HEX数据覆盖的空洞全部填成0xFF然后裁剪并输出成BIN。最终得到的是一个固定64KB的镜像文件里面的空余区域全部是0xFF。这种完整镜像有几个好处第一量产烧录时不用管文件大小烧录工具按固定地址和长度写入即可第二如果后续要做整片CRC校验一个完整的镜像会让校验逻辑非常简单第三调试时用十六进制编辑器打开BIN能直观看到Flash布局哪里是Bootloader、哪里是App、哪里是空余区一眼就能分辨。注意-fill只会补空洞不会覆盖HEX里已经存在的有效数据。所以不用担心补0xFF把代码冲掉。但如果你把填充值写成0x00那空余区域会被当成数据烧进去和Flash擦除态完全不一致某些量产检测脚本会因此误判后面第5章还会细说。3.5 用法五多段固件合并BootApp一次搞定很多产品出厂时要先把Bootloader和App一次性烧进芯片以前我会先烧Boot再烧App烧录器配置两遍。用srec_cat可以直接把两个HEX合并成一个完整BINsrec_cat bootloader.hex -intel application.hex -intel -fill 0xFF 0x08000000 0x0800FFFF -crop 0x08000000 0x0800FFFF -binary -o release.bin原理很简单srec_cat允许在一条命令里列出多个输入文件它们的数据会被同时放入同一个“地址空间”。只要Bootloader和App的链接地址不重叠就能天然共存。之前的-fill会把两者之间的空洞填上最后的-crop把范围限制在Flash地址空间内输出的release.bin就是Bootloader和App的完整合体。如果你的Boot和App分别由两个不同工程编译版本经常独立迭代这种合并方式尤其有效。我在正式发布脚本里就是这么用的CI流水线分别编译Boot和App再调一次srec_cat把产物固化成一个带版本号的release.bin整个过程不需要人工干预。如果手头不是HEX而是两个已经转好的BIN而且希望它们首尾直接拼接成一段连续数据可以用另一种方式srec_cat boot.bin -binary -cat app.bin -binary -o combined.bin这里的-cat会把app.bin紧跟在boot.bin后面形成纯顺序拼接不关心原来的地址。这种用法适合生成字库、资源文件或者做某些需要连续排列的固定数据块。4. 在STM32工程里落地的三个场景4.1 在Keil MDK编译后自动生成BINKeil默认只生成HEX不会自动生成BIN。以前我每次都要手动执行一次命令后来改成了User命令自动调srec_cat。操作路径是Options for Target - User - After Build/Rebuild。在用户命令里填一行C:\srecord\srec_cat.exe .\Objects\STM32F103.hex -intel -fill 0xFF 0x08000000 0x0800FFFF -crop 0x08000000 0x0800FFFF -binary -o .\Objects\STM32F103_full.bin注意勾选前面的Run #1复选框否则编译完不会自动执行。这个命令会在每次编译成功后直接在工程目录下生成全量BIN。这里有几个细节值得注意如果Keil的Output目录不是Objects要改成实际路径。路径里如果包含空格必须用双引号包住整个命令。我习惯把srec_cat.exe的全路径写死避免PATH变量在Keil子进程里没生效。编译报错时这条User命令不会执行所以不会把上一次的旧BIN误当成新固件。4.2 配合STM32CubeProgrammer烧录STM32CubeProgrammer烧HEX时工具会自动读取地址不需要额外输入。但烧BIN时如果不指定地址工具会直接用默认的Flash起始地址所以命令行里必须写清楚STM32_Programmer_CLI -c portSWD modeUR -d STM32F103_full.bin 0x08000000这条命令会把前面生成的完整Flash镜像烧到0x08000000。如果你的BIN是用法二里裁剪出来、只对应App区的文件就一定要把烧录地址改成0x08004000或其他实际地址否则固件会写错地方。量产阶段我也经常用STM32CubeProgrammer配srec_cat生成的整片BIN做样机烧录。因为整片BIN是固定大小、固定地址工装脚本只需要一条-d命令就能完成烧录不用额外解析HEX里的地址记录稳定性高很多。4.3 制作OTA升级包时的裁剪思路OTA升级包通常不应该携带完整的HEX记录因为里面附带地址信息会增加体积而且可能存在多个地址段协议解析复杂。我习惯在构建服务器上先做裁剪只保留App区srec_cat full_release.hex -intel -crop 0x08004000 0x0800BFFF -offset - 0x08004000 -binary -o ota_app.bin这样生成的就是一个从0开始的App裸镜像。接着再配合压缩工具比如gzip或lzma把ota_app.bin压成小的升级包。接收端拿到后解压再写入Flash的0x08004000整个OTA流程会非常干净。如果OTA协议要求校验srec_cat本身也内置了CRC相关的过滤选项可以用来给固件追加校验字段。不同版本参数略有差异具体使用时先--help查一下当前版本的CRC选项写法。这个功能我一般在自动化发布脚本里用给升级包在打包阶段自动计算并嵌入校验值避免每次人工维护校验工具。5. 实战排雷我踩过的五个srec_cat坑5.1 转换后BIN文件“膨胀”的真相有一次我把Keil生成的HEX直接转BIN转出来的文件有几百MB我以为srec_cat坏了。后来一查HEX里除了Flash地址段还有一段RAM初始化数据比如0x20000000开头的RW段。srec_cat把整个地址范围从0x08000000一直延伸到0x20000000中间所有空洞都被当成了数据源的一部分文件自然膨胀。解决办法很简单还是靠-crop限定范围srec_cat bad.hex -intel -crop 0x08000000 0x0800FFFF -binary -o good.bin养成习惯拿到一个陌生工程的HEX后先确认里面有没有多个地址段再决定要不要直接转BIN。别等到文件膨胀了才回头排查。5.2 填充值别乱选0xFF和0x00截然不同STM32 Flash擦除后是0xFF所以生成完整镜像时空洞区默认应该用0xFF填充。如果你用0x00填充这些“空余区域”在烧录时会被真实编程成0x00以后想在这些区域写入新数据还得先做擦除否则写不进去。另一方面一些校验逻辑按“空余区等于0xFF”来算整片CRC也会因为0x00填充导致校验失败。除非你的目标是RAM初始化文件或者你的外置Flash擦除态不是0xFF否则统一用-fill 0xFF。这个细节看着不起眼量产时却最容易出问题。5.3 参数顺序影响结果fill、crop、offsetsrec_cat的参数是流水线式的从左到右依次处理。同样的参数换一下顺序结果可能完全不同。比如你想填充整片Flash再输出srec_cat input.hex -intel -fill 0xFF 0x08000000 0x0800FFFF -crop 0x08000000 0x0800FFFF -binary -o out.bin如果我把-crop提到-fill前面那么区间外的数据会被先裁掉后面的填充可能找不到足够的地址范围输出就不符合预期。同理先-offset再-crop和先-crop再-offset裁切的地址范围计算方式完全不同。所以我现在的固定习惯是输入文件在前紧接着是-fill然后是-crop最后才是-binary和-o。只要按这个顺序写绝大多数场景都不会跑偏。5.4 Windows下调用与Keil环境变量细节Windows命令行里调用srec_cat最容易栽在路径解析上。如果工程目录带空格整个命令都要处理好引号否则Keil会直接报createprocess failed。常见的问题是*** error: createprocess failed, command: c:\keil_v5\...\fromelf...这种报错十有八九是User命令里的路径带空格或者反斜杠被错误转义。我一般把用户命令写成全路径并且尽量把工程放在无空格的目录下。另一个小技巧是用/代替\很多Windows工具都能识别。如果srec_cat命令本身没报错但生成的BIN是0字节检查一下-o的前面是不是少了-binary。输出格式没指定时srec_cat可能按别的格式写文件看起来就是打不开或大小不对。5.5 和fromelf、objcopy怎么选这几个工具并不冲突按场景选就行。需求推荐工具Keil工程编译后生成BINfromelf、srec_cat都可以HEX转BIN、裁剪App区、地址偏移srec_cat更直接Boot App合并成整包srec_cat首选GNU工具链下从ELF生成BINobjcopy更顺手需要CRC等格式处理srec_cat内置能力强我个人的工程里Keil的fromelf负责从AXF转出HEX和基础BINsrec_cat负责后面所有和地址、区间、合并相关的“脏活累活”。两者配合基本覆盖了STM32固件发布的全部文件处理需求。再说一句个人体会srec_cat的参数风格第一次用会觉得奇怪特别是-offset - 0x08004000这种写法总怀疑自己写错了。但只要你把几个高频参数固定成自己的模板用熟了以后它会变成开发环境和CI流水线里最不起眼却最可靠的一环。我现在很多项目的固件发布脚本里最后一步都是一条长长的srec_cat命令稳定得让人放心。