srec_cat实战:STM32固件HEX转BIN与OTA打包全攻略

发布时间:2026/9/28 9:02:26
srec_cat实战:STM32固件HEX转BIN与OTA打包全攻略 说实话干嵌入式这么多年跟文件格式打交道的次数比很多人想象的要多。你辛辛苦苦在Keil里编译完一个STM32工程生成的.hex文件安静躺在目录里代码确实编过了、也能烧可一旦遇到量产、OTA升级或者Bootloader做IAP需求往往会变成——能不能给我一份BIN文件。原因很简单BIN格式干净、体积小、不带地址信息传输和处理都方便。但HEX转BIN这件事直接用某个图形工具点一下的场合实在太少更多时候你要在构建脚本里自动化完成、要拼接两块固件、要按地址抠数据、要往包里塞CRC。srec_cat就是为这些场景设计的命令行瑞士军刀。这篇文章我用自己实际折腾过的STM32项目经验讲清楚它的5种最常用玩法以及每一招背后踩过的坑。1. 为什么嵌入式开发离不开HEX与BIN格式转换1.1 HEX和BIN到底差在哪先花几十秒把格式本质说清楚。HEXIntel HEX是一种文本格式每一行由冒号开头、由ASCII字符组成记录了数据长度、地址、记录类型和校验和。一个字节的数据在HEX里要占用两个字符再加上地址和校验开销体积膨胀得很明显。BIN就干脆多了它把内存里的数据原样导出来一个字节就是八比特没有任何包装。所以同样一段程序BIN通常会比HEX小三分之一左右这个差异在OTA传输时是很实在的。HEX最大的优势是自带地址信息。你拿一个HEX文件给烧录器烧录器能自己知道每一段数据该写到哪里哪怕你的代码分散成多段不连续地址它也能正确摆放到Flash里。而BIN不带地址烧录器、调试器、产线工具烧BIN的时候你必须手动告诉它从哪个Flash地址开始写。STM32常规情况是0x08000000这一点很多人第一次用J-Flash烧BIN时闹过笑话——默认地址没改烧进去之后程序完全不跑。另外提一个容易被忽略的细节STM32的Flash地址在0x08000000这个高位上HEX文件里是用扩展线性地址记录04记录来拼出高位地址的。所以你打开一份STM32工程生成的HEX会看到很多04记录它们的作用是把基地址抬到0x08000000这个宏大的地址空间。srec_cat读HEX的时候会把地址算明白输出BIN的时候再按顺序把字节排出来这个解析工作对使用者是透明的但理解它有助于你判断为什么转出来的BIN大小和预期不一样。1.2 什么时候必须转BIN我梳理了三个高频场景。第一个是OTA升级。走无线或者串口升级时固件包越小越好HEX这种带地址的文本格式在传输和校验上都不方便而且STM32做IAP的时候Bootloader自己要把数据写到Flash根本不需要地址记录——起始地址都是约定死的。第二个是量产烧录。很多产线用的离线烧录器、自动烧录夹具直接吃BIN文件填好起始地址就开始批量烧一份BIN配上地址参数就能复制出无数片板子。第三个是固件打包和差分升级。Bootloader加App合并成一个完整BIN、算CRC、做加密这些操作用srec_cat一步就能完成比手工折腾稳定得多。还有一个场景经常被忽略有时候你在网上找了个开源工程对方只给了HEX你想把其中某一段比如Bootloader段抠出来复用到别的项目里。直接用文本编辑器去改HEX不现实把HEX丢进srec_cat按地址范围提取成BIN才是最优雅的做法。哪怕你不做OTA不搞量产光这一个用法都值得把srec_cat装上。2. srec_cat环境准备与基础认知2.1 SRecord工具包下载与安装srec_cat属于SRecord工具包作者是Peter Miller一个历史挺悠久但一直维护得很好的开源项目支持Windows、Linux、macOS。Windows用户直接去官网或者SourceForge下载安装包装完记得把安装目录加进系统PATH。Linux更简单Debian/Ubuntu用sudo apt install srecordCentOS/RHEL用sudo yum install srecordmacOS用户brew install srecord。装完在终端敲一下srec_cat -V能看到版本号说明就绪了。我的建议是直接装最新版。老版本对某些参数的写法支持不够好尤其是负数偏移、CRC这类进阶选项新版本才稳。另外SRecord工具包里的其他命令比如srec_cmp、srec_info、srec_sort也都挺实用不过今天的主角是srec_cat。2.2 常用参数速览srec_cat的核心思维可以理解成一个或多个输入文件经过一系列变换操作输出一个目标文件。输出的格式由-o后面跟的文件类型参数决定。我自己平时最常用的参数整理成了一张表参数作用示例-intel指定Intel HEX格式input.hex -intel-binary指定裸二进制格式-o out.bin -binary-offset地址整体偏移正负都可以-offset -0x8000-within只保留指定地址范围的数据-within 0x08000000 0x100-exclude排除指定地址范围的数据-exclude 0x08008000 0x38000-fill在指定范围内填充指定字节-fill 0xff -within start len-crc32-b-e在指定地址写入CRC32大端-crc32-b-e 0x0801FFFC-o指定输出文件-o firmware.bin注意srec_cat处理多个输入文件时如果两个文件在地址上有重叠默认会报data overlap错误。这个特性很多新手会撞上后面专门说。另外要记住输出的地址映射关系如果输入HEX地址从0x08000000开始输出BIN时文件第一个字节对应的还是0x08000000。BIN文件本身不存地址这个映射关系要你自己记在文档里传给产线或者上位机同事的时候地址写错了就是一堆废片。3. 五种实战用法详解3.1 用法一HEX直接转BIN这招最简单但也是后面所有操作的基础。命令只有一行srec_cat firmware.hex -intel -o firmware.bin -binaryfirmware.hex是Keil、IAR或者GCC编译生成的Intel HEX文件-intel告诉srec_cat输入是HEX格式-o指定输出文件-binary表示输出裸BIN。执行完你会得到一个比HEX更小的BIN文件。实际项目里我一般不会裸转。因为HEX文件可能包含多段不连续数据直接转BIN时中间空洞部分srec_cat在不同版本下的处理方式不完全一样结果不可控。为了输出结果可预期我习惯先填充再转srec_cat firmware.hex -intel -fill 0xff -within 0x08000000 0x10000 -o firmware.bin -binary这条命令把0x08000000到0x080000000x10000即64KB范围内所有空洞填上0xFF确保BIN是连续完整的64KB。对于STM32来说Flash擦除后本来就是0xFF填充0xFF不会改变烧录逻辑反而让文件大小可预期上位机也好处理。这里补充一个特别容易踩的注意事项BIN没有地址信息烧录时务必手动选择起始地址。用STM32CubeProgrammer、J-Flash、ST-Link Utility烧BIN时地址都填0x08000000根据你实际的链接地址。我见过不止一次有人用J-Flash默认地址0x0烧录结果程序完全不跑还以为是固件编译出问题了。3.2 用法二地址偏移——Bootloader与App解耦时的核心操作STM32项目玩到带IAP Bootloader的架构地址偏移就是家常便饭。App的链接起始地址通常是0x08008000或更靠后但BIN文件生成、合并、烧录时经常要把数据整体平移。场景A你想把一份起始地址在0x08008000的App HEX转成一份从0x08000000开始的BIN方便直接烧录到Flash起始位置命令是srec_cat app_0x08008000.hex -intel -offset -0x8000 -o app_0x08000000.bin -binary注意参数是-offset -0x8000负号别漏了。如果你是想反过来把一个本来在0x08000000的固件平移到0x08008000比如为App运行时预留Bootloader空间那就写正偏移srec_cat app_0x08000000.hex -intel -offset 0x8000 -o app_0x08008000.hex -intel这里我特意输出成HEX而不是BIN因为你想保留地址信息后续可能还要合并、校验。输出成HEX的好处是随时能再折腾不至于丢了地址源头。偏移操作只是搬运数据但有两点必须清醒认识。第一STM32的向量表不会因为srec_cat偏移而自动修改。如果App确实要运行在新的地址你必须在链接脚本和系统初始化代码里把SCB-VTOR或者向量表基地址改成对应位置否则中断一个都进不去表面上看程序在跑实际一触发中断就飞了。第二偏移后如果两个文件在地址区间上产生重叠srec_cat合并时会直接报错这时候你需要换一种合并策略比如把重叠区域重新划分或者用填充把间隔顶开。3.3 用法三多文件合并——一次性生成完整量产固件带Bootloader的STM32产品最终量产固件往往是Bootloader和App合在一起的。你可以先用srec_cat把两个HEX合并成一份完整BINsrec_cat boot.hex -intel app.hex -intel -fill 0xff -within 0x08000000 0x40000 -o combined.bin -binary这条命令把Bootloader假设地址0x08000000起和App假设地址0x08008000起合并把从0x08000000到0x080000000x40000256KB之间的空洞全部填成0xFF最后输出一个完整的256KB BIN。量产的时候把这个BIN烧到0x08000000Bootloader和App就一次到位了产线不用区分两个文件省了不少事。如果你的两个文件里有一个地址带偏移或者想调整App在合并后整体镜像中的位置先按第3.2节的偏移处理好再合并更不容易出错。另外合并时还可以考虑是不是需要给输出BIN设置块大小。有些烧录器对BIN的分块有要求可以使用-Output_Block_Size参数调整不过大多数场景默认就够用。还有一个容易踩的坑合并时报data overlap。出现这个基本可以断定是不同输入文件的地址区间重叠了。要么是链接脚本里Bootloader和App的Flash区域划重了要么是同一个文件被不小心重复追加了。检查链接脚本里的FLASH起始地址和长度是最直接的排查方法。3.4 用法四按地址范围提取数据项目做到后期经常需要从一个大固件里抠出某一段。比如你要单独升级Bootloader或者只想提取App区生成OTA差分包。用-within参数按地址范围截取# 提取0x08000000到0x080000000x800032KB的数据 srec_cat full.hex -intel -within 0x08000000 0x8000 -o bootloader.bin -binary-within后面两个参数第一个是起始地址第二个是长度字节。如果只想剔除某个区域也可以用-exclude排除掉不想要的范围# 去掉App区域的完整固件剩下Bootloader srec_cat full.hex -intel -exclude 0x08008000 0x38000 -o bootloader_only.bin -binary注意-exclude第二个参数同样是长度这条命令把0x08008000到0x080080000x38000224KB的范围排除掉剩下的就是0x08000000到0x08008000之间的Bootloader段。这个操作在低版本srec_cat上偶尔会有点小差异输出后建议用srec_info或者直接看文件大小验证一下。提取完再输出HEX或BIN完全看你要干什么。做差分升级的话通常输出BIN然后把起始地址和长度写进升级包的头部信息。我在实际项目里用这个提取法生成了Bootloader的独立升级包因为Bootloader区域一旦有bug要修复不可能让客户重新刷整包单独提取出Bootloader做成小升级包成本低很多。3.5 用法五填充对齐与CRC校验最后这招在OTA升级里用处最大给固件做对齐和校验。很多Bootloader在接收升级包时要求包大小是某个值比如4KB的整数倍或者尾部要带一个CRC32。srec_cat一条命令就能把填充和校验都搞定。比如我想把App固件从0x08008000开始补齐到32KB大小并且在最后4字节写入CRC32大端srec_cat app.hex -intel -fill 0xff -within 0x08008000 0x8000 -crc32-b-e 0x0800FFFC -o app_ota.bin -binary这条命令的执行逻辑是先加载app.hex在0x08008000到0x080080000x800032KB范围内填充0xFF把所有空洞填死然后在地址0x0800FFFC处写入4字节的CRC32CRC计算范围是整个填充后的32KB区域。srec_cat会自动排除写入CRC的那4个字节所以不会把自己算进去这个细节我在工程里验证过。很多老工程师会用CRC16命令类似-crc16-b-e 0x0800FFFE留2字节给CRC16。Bootloader端收到包之后对整个包算一次CRC和末尾带的CRC比对一致就说明固件没被传坏。这里有个容易忽略的点srec_cat的CRC默认是标准CRC-32和zlib那条常见多项式一致但STM32硬件CRC外设的初始值和多项式有时跟标准CRC-32对不上。所以我一般直接在Bootloader端用软件算标准CRC32让两边统一省得来回排查校验不一致的问题。4. STM32项目中的真实应用案例4.1 KEIL MDK构建后自动生成BIN以前用Keil MDK做STM32After Build脚本里通常调fromelf直接生成BIN$K\ARM\ARMCC\bin\fromelf.exe --bin -o $L\L.bin $L\L.axf这条命令现在依然能用但fromelf只有ARMCCAC5编译器才在这个路径升级到ARM Compiler 6AC6之后路径会变成$K\ARM\ARMCLANG\bin\fromelf.exe不少人第一次升级时在这里卡了半天。而且fromelf只做AXF到BIN的转换不能合并、不能填充、不能算CRC。所以我现在的做法是在After Build里直接调srec_cat把编译、填充、CRC一步到位C:\tools\srecord\srec_cat.exe $L\L.hex -intel -fill 0xff -within 0x08000000 0x10000 -crc32-b-e 0x0800FFFC -o $L\L_ota.bin -binary编译一完成工程目录里自动多出一个带了CRC、填充到64KB的OTA固件。上位机工程师要的就是这个不用每次手动去转也不用担心版本不对应。这里提醒三件事第一After Build命令行里如果srec_cat.exe路径带空格一定要用双引号包起来第二Keil的宏$L是输出目录L是当前Target的输出文件名不含扩展名组合起来正好是HEX文件路径第三命令行里如果要串多个命令复杂逻辑建议写进一个.bat批处理After Build里只调一句bat维护起来清爽很多。4.2 OTA升级包生成完整流程把OTA包生成的一条完整链路串一遍你就能理解前面几招怎么组合使用了。假设项目结构是Bootloader在0x08000000到0x08007FFF32KBApp在0x08008000开始Flash总共512KB。OTA只升级App部分。第一步编译App工程生成app.hex。第二步用srec_cat处理Appsrec_cat app.hex -intel -fill 0xff -within 0x08008000 0x10000 -crc32-b-e 0x08017FFC -o app_ota.bin -binary这条命令把App固件从0x08008000开始补齐到64KB0x10000并在0x08017FFC处写入CRC32。注意0x08017FFC是App区域最后4字节正好落在填充区域内不会覆盖真实代码前提是App真实代码小于64KB链接脚本里要留够余量。第三步把这64KB的BIN交给上位机打包。上位机在文件头加4字节魔数比如0xAA55、2字节版本号、4字节固件长度、4字节固件CRC组成最终的OTA升级包。这里再强调一次srec_cat算的是标准CRC32上位机打包时千万别用别的CRC变体否则算出来对不上又得排查半天。第四步STM32 Bootloader通过串口或无线收到升级包按头部的长度信息接收完整然后在RAM里对整包数据跑一遍CRC32和头部携带的CRC比对一致才允许写入App Flash区域。写入完成后跳转到AppApp启动时把中断向量表指向0x08008000系统就正常起来了。这套流程我在好几个产品上复用稳定性和效率都非常好。5. 常见问题与踩坑记录5.1 地址不对齐导致烧录失败这是转BIN后最容易遇到的问题。前面反复说过BIN没有地址烧录器需要你填起始地址。可很多朋友填了地址仍然烧不进去或者说烧进去了跑不起来。我遇到过两类典型问题一是J-Flash的Probe address默认是0x0忘了改成0x08000000烧进去的自然是一堆无效数据二是STM32CubeProgrammer里烧BINAddress那栏填了0x08000000但文件类型选错了选了Intel Hex去解析BIN工具直接报文件格式不符。解决办法很简单BIN文件一律选Bin/Raw类型不要选Intel Hex。另外还有一个对齐问题不少STM32系列Flash编程的最小单位是半字16位或字32位如果你处理的BIN总大小不是对齐的一些烧录器会拒绝烧录或者自动补齐。srec_cat输出BIN时按实际数据长度来如果末尾真实代码长度是奇数建议在链接脚本里做好对齐或者在填充阶段把范围定成偶数大小这样生成的BIN就不会出现奇怪的边界问题。5.2 HEX起始地址不是0的坑很多人第一次用srec_cat转STM32的HEX发现生成的BIN怎么只有几十KB甚至更小以为文件损坏了。其实这是正常的。STM32的HEX地址从0x08000000开始srec_cat输出的BIN第一个字节就是0x08000000的内容文件长度等于HEX里真实数据覆盖的连续范围长度跟Flash基地址是0x08000000还是0x00000000没有关系。比如你只烧了0x08008000开始的App转出来的BIN就只有App那一截没有前面0x08000000到0x08007FFF的Bootloader所以文件小是正常的。如果你需要生成一个从0x08000000开始、包含前面空洞的完整Flash镜像就必须先用-fill把起始地址之前的空洞也填上。或者在输入时加-offset把0x08008000的数据平移到0x08000000再输出。具体用哪种取决于你要这个BIN干什么——如果是给产线烧整片Flash用填充完整镜像更省事如果只是给OTA用直接让BIN只包含App区域就够了还能省点传输流量。5.3 Windows环境下的路径与引号陷阱Windows下使用srec_cat最大的敌人是路径里的空格和反斜杠。比如你把SRecord装在C:\Program Files\srecord\命令行直接写srec_cat可能找不到PATH没配好写完整路径又可能因为空格被拆开。我的习惯是给工具专门建一个无空格的目录比如C:\tools\srecord\然后把这个目录加进PATH命令行就清爽很多。在批处理或Keil的After Build里如果路径含空格用双引号包住整个路径。但注意srec_cat参数里既要有文件名路径又要有各种选项引号千万别乱套。我见过有人写成这样srec_cat C:\path\to\file.hex -intel -o C:\path\to\out.bin -binary把参数和路径粘在一起了运行直接报错。正解是工具路径和文件路径各自用引号独立包裹C:\tools\srecord\srec_cat.exe C:\path\to\file.hex -intel -o C:\path\to\out.bin -binary另外在Keil After Build里用srec_cat时报错说找不到文件先在cmd里手敲一遍同样的命令把Keil的宏展开后实际路径验证一下往往能快速定位是路径写错还是宏没替换成功。Keil宏展开后的路径尾部可能带反斜杠拼接时注意别变成双反斜杠那也会出问题。最后分享一点我的个人习惯现在不管项目多简单我都会在构建脚本里把HEX转BIN、填充0xFF、附加CRC这些操作自动化而不是依赖哪天手动去转一次。人总会忘事今天你记得手动转BIN、手动算CRC两个月后接手的不一定是同一个人。把工具链自动化的成本其实很低换来的却是每个版本固件都一致、可复现。srec_cat这个东西真的值得花半小时研究一下投入产出比非常高。