尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

嵌入式固件BIN/HEX互转与合并:原理、工具与避坑指南

嵌入式固件BIN/HEX互转与合并:原理、工具与避坑指南 简介这是一款面向嵌入式开发者与单片机初学者的BIN/HEX文件互转合并小工具解决Keil等编译器生成HEX与烧录文件BIN格式不一致、需要手动转换或合并固件的问题。工具基于对话框程序实现源码简洁支持BIN转HEX、HEX转BIN以及HEX文件合并并针对Keil使用场景对A~F字符大写、超过64KB数据错乱、C8051芯片HEX转BIN错误等常见问题进行了修复实用性强。压缩包内含22个文件主要包含5个C头文件.h、4个源文件.cpp、2个可直接运行的exe程序以及工程配置、说明文档等整体仅654KB轻量易用。目前已有3652人学习/下载适合需要快速处理固件格式、进行烧录与调试的开发者参考。借助完整的对话框工程源码读者可自行编译、查看功能实现逻辑并可根据项目需求二次扩展。 干嵌入式这一行跟固件文件打交道是躲不开的。Keil里编译完MDK默认会给你生成一个HEX文件但是做OTA升级、离线烧录器、产线工装的时候往往又需要BIN文件反过来拿到一个BIN想烧进J-Link或者STM32CubeProgrammer有些工具又只认HEX。更常见的是Bootloader和App要合成一个文件烧录或者把两个不同地址的固件拼到一起这事儿用鼠标点来点去容易错直接拖进编辑器乱改更是灾难。我花了不少时间把BIN和HEX的互转、合并整个流程捋了一遍这篇文章就把能直接落地的操作和经验整理出来。1. 从“文件打不开”聊起BIN和HEX到底差在哪儿先说一个经常把人绕晕的误解BIN文件和HEX文件不是两类独立的东西它们描述的是同一份固件数据只是“包装”方式不一样。HEX文件准确说叫Intel HEX文件本质上是一个纯文本文件。你用记事本打开一个HEX会看到一行行这样的内容:020000040800F2 :10000000 00020020 55010008 99010008 A5010008 B1010008 00000000 00000000 30 :10001000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 D0 :04000005 080001C1 77 :00000001FF我来拆解一下每一行的结构看懂了这些后面你手动改HEX或者排查转换问题都会很有底每行开头的冒号:是起始标记后面两个字符是本行数据长度比如10就是16个字节再后面四个字符是基地址偏移再两个字符是记录类型00表示数据记录01表示文件结束02和04分别表示扩展段地址和扩展线性地址后面合并的时候这种记录最容易出问题中间部分就是实际数据行尾两个字符是校验和。而BIN文件就简单粗暴了它是纯二进制数据流没有任何地址信息从芯片Flash的起始地址开始往里一个字节一个字节顺序排列。你在HxD这类十六进制编辑器里打开一个BIN看到的就是固件最原始的样子。为什么这两种格式能互相转换因为HEX里所有的地址信息、校验信息都是为了“传输和烧录”服务的外包装真正有意义的是里面那16个字节一段的数据。抠掉地址和校验把数据按顺序首尾相接就是BIN反过来给BIN数据加上每行的地址、长度、校验和再按记录类型组装就是HEX。理解了这一点互转和合并就不再是什么黑魔法本质都是对数据块做重新编排。有个细节需要注意BIN文件本身不带起始地址。一个BIN文件烧到0x08000000还是烧到0x08010000烧录器是不知道的完全取决于你在烧录工具里设置的下载地址。而HEX文件自带地址烧录器按文件记录里的地址去写Flash。这就是为什么“只给文件不给地址”经常出问题的根源。2. 互转不是魔法四条实用转换路线直接抄作业很多人一上来就问“用什么软件能转”其实转换工具多得很但关键不是工具多而是不同场景下该用哪个。我用过不下十种方式这里只写四条我长期在用、稳定可靠的。2.1 Keil里直接同时生成HEX和BINMDK工程最常用如果你用的是Keil MDK编译器早就给你备好了工具链完全不用再装第三方转换软件。在MDK里勾选“Create HEX File”后每次编译只出HEX要出BIN的话需要加一句fromelf命令。具体做法进入Options for Target→User→After Build/Rebuild在“User Command”里勾选“Run #1”填入fromelf --bin --output.\output\L.bin .\output\L.axf这里有几个注意点L是MDK的内置宏表示当前工程名的缩写比如工程叫Uart_Boot实际执行的就是Uart_Boot.bin必须把fromelf.exe所在目录加进系统PATH环境变量或者用绝对路径否则编译后命令执行会报“无法识别fromelf”用--bin生成的是纯BIN用--i32生成的是带地址的HEX自己按需选建议输出到一个单独的output目录别跟axf混在一起省得清理工程时误删。这一步配置完以后每次编译Build Output窗口会提示fromelf: Image comes from .\output\xxx.axf然后就能在output文件夹里同时拿到HEX和BIN了。很多热搜里搜“keil生成bin文件”的其实答案就是这个。2.2 J-Flash直接另存为有调试器时最省事如果手里有J-Link用J-Flash做格式互转是我觉得最不费脑子的办法。操作流程不复杂打开J-Flash新建工程芯片型号选对不需要连接设备直接File→Open data file把要转的文件拖进来HEX或BIN都支持数据加载后在左边能看到带地址的排列再File→Save data file as选择目标格式导出。这里埋着一个大坑J-Flash导入BIN文件时会弹窗让你确认“Target Address”起始地址。有人不填或者随意填0结果文件打开以后数据全对但地址错乱了导出HEX再烧录代码跑到完全错误的位置。最好在打开BIN时就填入实际Flash起始地址比如STM32通常填0x08000000。用J-Flash导出HEX时它默认按当前的“加载地址”来编排记录。也就是说如果你想导出一个“从0x08000000开始的HEX”导入BIN时的起始地址就必须设为0x08000000否则导出的HEX里地址记录全是乱的。2.3 srec_cat命令行转换批量处理和合并的场景GNU出品的srecord工具套件是我处理批量文件时的首选尤其适合脚本化操作。它支持HEX、BIN、Motorola S-record等几十种格式转换、裁剪、拼接都靠一条命令行完成。Windows用户去SourceForge搜srecord下载安装包装完以后在命令行里就能用SRecCat.exe。单文件互转很简单# HEX转BIN指定偏移按0x08000000提取并从0开始排列 srec_cat input.hex -intel -o output.bin -binary # BIN转HEX指定起始地址 srec_cat input.bin -binary -offset 0x08000000 -o output.hex -intel注意-binary和-intel表示输入输出格式选项-offset加偏移量。这里要提醒一句如果HEX文件里有多段不连续的地址直接转BIN会把空洞填成0转换结果比预期大很多。所以转换前最好用-crop把地址范围裁剪到实际需要的区间例如srec_cat input.hex -intel -crop 0x08000000 0x08020000 -o output.bin -binary2.4 自己写Python脚本最灵活、能私有化定制当转换规则特殊比如要做校验和、要提取指定区间、要调整对齐时现成工具反而碍手碍脚。我用Python实现过一个小工具顺手放到CI里当打包脚本。核心逻辑其实很短import re def hex_to_bin(hex_path, bin_path): data bytearray() with open(hex_path, r) as f: for line in f: line line.strip() if not line.startswith(:): continue rec_type int(line[7:9], 16) if rec_type 0: # 数据记录 length int(line[1:3], 16) payload bytes.fromhex(line[9:9 length*2]) data.extend(payload) # 注意这里没有处理地址空洞适用于连续固件 with open(bin_path, wb) as f: f.write(data)这只是一个最简版本真正实用需要处理扩展地址记录04类型把数据写到正确的位置而不是简单拼接。我后来封装的版本是先扫一遍所有记录按地址偏移将每一条数据放到一个足够大的字节数组里最后再裁剪掉首尾的0xFF空洞这样处理不连续地址也不会出错。写脚本的过程本身也能促进对HEX格式的理解建议有时间的人试一次。3. 合并不是拼接积木多段固件合成一个文件合并是很多人真正头疼的环节。一次OTA升级往往要同时烧Bootloader和App产线希望只烧一个文件或者想把两份App合成一个双备份固件启动时根据标志位选一份加载。这些场景背后都离不了“合并”。3.1 Bootloader与App合并先说清楚地址规划合并前必须先明确每段固件的Flash起始地址和大小。以STM32F103为例我的Bootloader放0x08000000偏移0x8000处放App也就是32KB留给BootloaderFlash总容量512KB。合并后文件的理想形态是在偏移0x0000到0x7FFF填Bootloader的内容0x8000到末尾填App的内容。如果两段固件之间有空洞比如Bootloader实际只占16KB但App从32KB开始BIN文件合并时需要把空洞区填充为0xFFFlash空白的默认值而不能留空或补零。为什么是0xFF而不是0x00因为Flash擦除后的状态就是全0xFF烧录器对Flash做编程时遇到0xFF的位置会跳过速度更快也更安全。如果补零会把空白区全部真写成0不仅慢而且某些芯片对“先擦后写”有额外要求容易出问题。3.2 BIN文件合并简单直接但要注意对齐两个BIN文件合并是最直白的本质上就是“拼接数据流”。假设我有boot.bin和app.binApp的起始地址是0x08010000即Bootloader占64KB那么用Python合并的参考做法如下def merge_bin(boot_path, app_path, output_path, app_offset_hex0x10000): with open(boot_path, rb) as f: boot_data f.read() with open(app_path, rb) as f: app_data f.read() # 先按Flash容量分配整个镜像大小 merged bytearray([0xFF] * (app_offset_hex len(app_data))) # 写入Bootloader merged[:len(boot_data)] boot_data # 写入App注意对齐 merged[app_offset_hex:app_offset_hex len(app_data)] app_data with open(output_path, wb) as f: f.write(merged)合并后校验一下文件总大小应等于app_offset_hex app.bin长度如果Bootloader超过偏移就会互相覆盖脚本应该抛异常首个App向量表的SP指针和Reset_Handler地址是否符合预期。这一步建议用脚本自动校验别靠肉眼。这里特别提醒一个对齐概念很多MCU的Flash支持按“页”擦除页大小从1KB到8KB不等。如果App的起始地址不对齐页边界或者合并后的文件长度不是页大小整数倍产线烧录时某些烧录器会报警或者出现擦不干净导致程序跑飞。合并之前先查芯片的Flash页大小把App的偏移取页大小的整数倍。3.3 HEX文件合并srec_cat一条命令搞定如果两段固件都有现成的HEX文件合并HEX比合并BIN更“原生”因为HEX自带地址可以直接按地址合并而不需要手工对齐填充。srec_cat就是为此设计的srec_cat boot.hex -intel app.hex -intel -o merged.hex -intel上面这行命令会自动提取两个HEX里的所有数据记录按地址排序后生成一个新的HEX。如果你的Bootloader和App在各自独立HEX里地址都是连续且不重叠的这条命令生成的merged.hex烧录后就能正常工作。然而有个隐蔽问题两个HEX文件如果各自都带有芯片的起始地址比如都是0x08000000直接合并必然发生地址重叠。解决方法是在合并时给其中一个文件加偏移。比如App的HEX是基于链接地址0x08010000生成的那直接用上一条命令没问题如果App的HEX生成时链接地址写的是0x08000000有人图省事没改IROM起始地址合并时就要srec_cat boot.hex -intel app.hex -intel -offset 0x10000 -o merged.hex -intel这条命令把app.hex整体往高地址挪64KB。利用HEX自带地址的特性这种方式比BIN拼接要安全不少前提是你清楚每一份HEX的当前地址是什么。3.4 合并后的地址校验与CRC文件合并完很多人的反应是“烧进去再说”。但我的建议是烧录前先做一个镜像级校验别省这一步。第一用脚本或srec_cat把合并后的HEX重新转回BIN检查基因地址、长度和各段数据是否符合预期。特别是确认App的Reset_Handler向量已被放到正确的偏移位置——合并后App偏移变化但App内代码是按链接地址编译的不能简单改偏移通常需要App本身支持“运行时重定位”或者按实际烧录地址编译这点在做Bootloader跳转时尤其关键。第二如果你的固件有CRC校验机制Bootloader启动时会校验App区CRC合并后App区CRC会变化吗如果CRC在编译App时写死在固定位置不会变如果CRC是编译后脚本计算并回填的那合并时要注意别把回填的位置剪切掉。我见过不止一次合并脚本把App尾部的CRC信息覆盖成0xFF导致Bootloader每次启动都报“App校验失败”。4. 实际应用中避不开的那些坑工具用熟练之后真正耗时间的不是操作本身而是那些“第一次遇到时一脸懵、查半天才明白”的隐藏问题。这里把我在互转和合并过程中踩过的坑集中列出来。4.1 HEX文件里“扩展地址记录”才是地址真相很多人在Hex编辑器里看HEX文件见每行第3到第6个字符不同就以为那是绝对地址。这是个经典误区。以0x08000000为例HEX用的是扩展线性地址记录类型04配合当行偏移来共同定位:020000040800F2 ← 这一条表示后续数据的基地址是0x0800 16 0x08000000 :10000000 ... ← 这一行表示在基地址0x0000处写入数据如果合并时漏掉了类型04的记录或者把两个来自不同HEX文件的04记录混在一起烧录器会把数据写到完全错误的位置。手动改HEX文件时这是一类高危操作。用srec_cat合并大文件时工具会自动生成必要的扩展地址记录但是用某些简单文本拼接工具合并HEX时它们往往只是机械地把两个文件的行拼在一起后面文件缺了04记录数据全部堆到前面一个文件的地址空间里。所以这类操作我只建议用专门的工具。4.2 BIN转HEX时起始地址别填错J-Flash打开BIN文件时弹出的那个地址是最容易犯错的入口。之前帮朋友排查过一次问题他用J-Flash把一个BIN转成HEX烧录后完全跑不起来。后面发现他导入BIN时地址框填的是0x08001000导致生成的HEX里所有地址都从0x08001000开始而他的工程链接地址是0x08000000。稍微懂一点嵌入式启动流程就知道芯片复位后从0x08000000读栈顶指针数据全是错的程序必然跑飞。正确做法BIN转HEX前先弄清楚这个BIN文件的实际链接地址。如果是J-Link读出来的芯片内容通常起始地址就是Flash起始地址如果是Keil的fromelf从axf转出来的从.map文件里能查到RO Base地址。填错一个数字整个文件的地址体系就歪了。4.3 读回来的BIN和原始BIN不一样正常但不一定能用用J-Flash或J-Link Commander从芯片里Read Back出来的BIN容量通常比你编译出来的BIN大得多。比如你App实际编译出来16KB读回来的BIN可达512KB整片Flash。这是正常的因为读回的内容是整片Flash内容包含空隙。空隙区域全是你把Flash擦除后写入前的内容也就是0xFF。直接用这个读回的BIN去给另一片芯片烧录如果另一片芯片Flash容量不一致或烧录工具不自动跳过空白区就容易出问题。所以读回BIN文件时建议在J-Flash里设置读取范围只读实际固件区域。或者在读回后用0xFF裁剪去掉首尾连续空白区。这样得到的BIN体积小烧录效率高。4.4 校验和HEX自带BIN没有HEX文件的每一行都自带一个校验和烧录器写入时会校验这一行数据是否损坏。而BIN文件完全没有校验机制数据错了只能靠芯片内部的读写校验兜底。因此文件在电脑之间传输、上传到服务器、塞进烧录工装时尽量额外计算一次MD5或CRC32。尤其是产线场景靠文件日期和大小判断版本是否更新是不够的我见过生产质量事故就是操作员用了一个同名但内容过期的BIN烧了三天才发现。我目前的做法是每次固件发布自动生成一个checksum.txt里面写清楚文件名、MD5、编译时间、git commit号。虽然多一步操作但在追溯问题的时候价值极大。5. 把互转和合并做成自动化我的最终实践手工操作做得多了必然会产生重复劳动。我最后把所有流程收敛成一个打包脚本每次编译后一键完成“生成BIN、生成HEX、合并Bootloader和App、计算校验和”全流程不再依赖手动点击。脚本用批处理配合srec_cat就能实现没有Python依赖echo off set BOOT_OFFSET0x8000 echo 1. 生成BIN并从HEX校验 fromelf --bin --output.\build\boot.bin .\build\boot.axf fromelf --bin --output.\build\app.bin .\build\app.axf echo 2. 校验关键向量表 srec_cat .\build\app.bin -binary -offset %BOOT_OFFSET% -o .\build\app_place.bin -binary echo 3. 合并Bootloader和App copy /b .\build\boot.bin ^ .\build\app_place.bin ^ .\build\merged.bin echo 4. 合并文件转HEX可选 srec_cat .\build\merged.bin -binary -offset 0x08000000 -o .\build\merged.hex -intel echo 5. 生成校验文件 certutil -hashfile .\build\merged.bin MD5 .\build\checksum.txt certutil -hashfile .\build\merged.hex MD5 .\build\checksum.txt这个流程在每次编译后自动执行Bootloader和App的合并偏移通过环境变量控制改地址规划时只需要改一处不用在图形界面里反复点。整个过程已经跑了快两年没有一次因为“文件格式转换/合并出错”引起过返工。一个小经验脚本跑完不要直接去烧录先打开checksum文件确认生成时间是不是“刚刚”。脚本一旦配错路径或参数经常是“成功执行但什么都没生成”然后抓起旧文件去烧录这大概是我见过频率最高的低级事故了。BIN与HEX的互转和合并说到底不是一个多深的技术问题它考验的是对文件格式底层结构的理解以及工作流程里的严谨程度。把工具用熟把坑提前避开这类活儿就能变成彻底不占用注意力的后台流程。本文还有配套的精品资源点击获取
返回列表