hex文件转bin文件工具:嵌入式固件格式转换全解析

发布时间:2026/9/3 23:59:56
hex文件转bin文件工具:嵌入式固件格式转换全解析 简介针对嵌入式开发中HEX转BIN的常见需求这份资源是一款专门转换Intel HEX格式与二进制BIN文件的工具面向电子工程师与嵌入式学习者解决程序烧录前格式不兼容的问题。工具支持将十六进制文本解析为纯二进制数据并保留地址与校验信息处理能力适合单片机、EPROM等场景使用。资源压缩包共30个文件以C语言源码、可执行程序、头文件及说明文档为主整体大小仅353KB轻量实用。包含源代码意味着开发者可查看转换逻辑甚至自行编译修改而附带的Makefile、README和更新日志有助于快速上手与二次开发。目前已有3741人学习下载说明该工具在实际项目中具备一定参考价值。通过源码与文档读者既能直接转换固件文件也能深入理解HEX与BIN的结构差异、校验算法与地址映射原理对掌握二进制格式转换的底层实现很有帮助。 在嵌入式开发里混久了你迟早会遇到一个看着不起眼、但特闹心的问题手里的程序文件是 .hex可烧录器、OTA 升级包或者产线工具偏偏只认 .bin。项目标题“hex文件转bin文件工具”就这么出现在我的工作目录里。刚开始我也觉得这玩意随便找个软件点两下就行但真上手才发现转换不是简简单单把文本改成二进制那么回事里面牵扯到地址对齐、校验和、空洞填充这些细节没搞明白之前转出来的文件常常让人一脸懵。这篇内容就围绕 hex 转 bin 的过程展开讲讲两种格式到底差在哪哪些场景必须转踩过哪些坑以及我最后沉淀下来的几套可复现的转换方案。不管你是用 Keil、IAR、MounRiver 做单片机开发还是做 bootloader 升级、固件加密只要跟着实操部分走一遍基本就能把这事彻底搞定。1. 为什么嵌入式开发总绕不开 hex 和 bin 这两个格式1.1 hex 文件是披着文本外衣的二进制Intel HEX 格式本质上是一种文本格式用 ASCII 字符保存数据。每一行以冒号开头后面依次是长度、地址、类型、数据和校验和。比如常见的一行:1020000060F37F4060F37F4060F37F4060F37F40A2拆开看就是10表示后面有 16 个数据字节2000是这行数据要写入的起始地址00表示这是普通数据记录接着是 16 个字节的机器码最后A2是校验和。校验和的计算方式是本行所有字节相加取低 8 位取反再加 1保证最终累加和为 0。这个设计让 hex 文件在传输和存储过程中如果出现字符损坏能被快速发现。你可能会想这么麻烦的格式为什么能活到现在一个重要原因就是它的可读性和容错性。编译出来的固件不连续比如 Flash 里代码段在 0x08000000配置字在 0x0800F000中间隔了大片空洞hex 文件可以只保存实际有数据的地方每一行记录独立地址即可。这样文件体积不会因为空洞而爆炸调试和查看时也方便用文本编辑器打开就能直接看到数据特征。1.2 bin 文件才是芯片真正执行的东西bin 文件就纯粹多了它是原始二进制数据的连续排列没有任何地址信息也没有校验和。烧录时根本不用解析直接把内容按顺序写进存储介质就行所以芯片执行、bootloader 加载、固件 OTA 升级几乎都倾向使用 bin。但正因为 bin 没有地址信息它默认被烧录到起始地址而且中空的区域必须用某个填充字节补上。如果你从 0x08000000 开始写代码代码段到配置区之间空了几十 KB转成 bin 就得硬生生把这几十 KB 用 0xFF 或者 0x00 填满文件体积一下子膨胀。可以理解为 hex 是带路标的日记bin 是整本连续印刷的图书——前者方便查找和传输后者方便直接读取和执行。这也是为什么很多项目要用 hex 转 bin编译链默认产出 hex特别是 Keil MDK、MounRiver 这类 IDE 默认配置但烧录器软件、量产工具、远程升级服务只吃 bin。你不动手转就得卡在交付环节。2. 转换工具选型命令行为主、脚本为辅、在线工具兜底2.1 四种主流转换方案对比真正动手转时工具选择很多但各有适用场景。我把踩过坑之后留下的方案整理成了表格方案适用场景优点缺点arm-none-eabi-objcopy嵌入式常规开发平台工具链自带一条命令搞定支持地址偏移、裁剪对没有安装工具链的新手不友好srec_cat多段合并、区间提取、填充定制功能极其强大支持几乎全部格式命令参数细碎上手需要看文档Python 脚本intelhex 库或手写解析特殊格式、批量处理、二次加工可控性强能嵌入自动化流程需要 Python 环境执行效率低于原生工具在线转换工具偶尔一次、临时救急打开浏览器就能用涉及敏感固件有泄密风险不推荐在正规项目里用我个人的取舍逻辑是日常开发优先用 objcopy因为装了交叉编译链基本就有它处理复杂地址映射和文件合并时上 srec_cat需要把转换动作集成到 CI 或者产线脚本里时就用 Python 写个独立小工具。2.2 为什么我不推荐完全依赖图形化工具市面上很多hex 转 bin 小助手之类的 GUI 工具点两下确实能出结果。但问题在于这类工具几乎不告诉你内部是怎么处理的。地址空洞填充了什么值、起始地址从哪里算起、多段 hex 是否会被错误拼接这些全是被黑盒处理掉的。有一次我用某个在线工具把有多个扩展地址段的 hex 转成 bin它直接把前面两段的 0x1FFF0000 和 0x08000000 按顺序连在一起导致烧进去直接跑飞排查了很久才发现是转换工具搞的鬼。所以涉及正式项目时我倾向于用命令行或者脚本方式至少你能清楚知道每一步发生了什么。命令行工具虽然看着生硬但可重复、可追踪、可自动化这恰恰是工程化交付最看重的特性。3. 实操三种主流转换方法3.1 arm-none-eabi-objcopy 一条命令搞定常规转换最省心的方式就是 objcopy。编译固件时用的工具链里一般自带这个程序比如 STM32 用arm-none-eabi-objcopy或者国产 RISC-V 工具链里的riscv-none-embed-objcopy。基本用法是arm-none-eabi-objcopy -I ihex -O binary firmware.hex firmware.bin-I ihex表示输入格式是 Intel HEX-O binary表示输出为原始二进制。如果只想提取某个区间的数据比如只取前三十二字节的启动向量或者某个配置块可以用--only-section配合链接脚本或者用--change-addresses调整输出基址但后者不改变实际数据位置只是影响生成 bin 时的偏移。需要注意objcopy 转换时如果 hex 文件里首行地址不是从 0 开始的它会自动按首地址作为 bin 的起点。但有些工具链会把 hex 的起始地址放在 0x08000000转换出来的 bin 开头就会有一段空白区域。这时候需要先确认 bin 是要完整包含 Flash 整段还是只包含代码实际占据的有效区间两种需求下 objcopy 参数是不同的。3.2 srec_cat 处理多段合并和地址填充当 hex 文件里有多个不相邻的数据段或者要手动指定填充字节、生成固定大小的 binobjcopy 就不够精细了。srecord 工具包里的srec_cat是处理这类需求的利器。比如我要把app.hex转换成从 0x08000000 开始、整段 64KB、空洞用 0xFF 填充的 binsrec_cat app.hex -intel -fill 0xFF -over app.hex -o app_filled.bin -binary拆解一下-intel声明输入格式-fill 0xFF表示用 0xFF 填充空洞-over app.hex是作用域表示在原始 hex 的地址范围内填充。这样转换出来的 bin 就是连续完整的 64KB适合直接倒入烧录器。如果你想把两个 hex 合并成一个 bin比如 bootloader 和 app 分别编译但最终产线烧录只需要一个文件srec_cat boot.hex -intel app.hex -intel -o merged.bin -binarysrec_cat 会自动处理重叠区域的覆盖逻辑但如果有地址重叠会报错相当于帮你拦住了一个高危风险点。3.3 Python 脚本从解析原理到自定义转换虽然 objcopy 和 srec_cat 已经很强大但总有它们不适合的场景——比如要从 hex 里提取某几个指定扇区的数据或者把地址重新映射、加一段自定义头部。这时候写一个 Python 脚本反而最灵活。Python 的intelhex库封装好了解析逻辑几行就能完成转换from intelhex import IntelHex ih IntelHex(firmware.hex) ih.tobinfile(firmware.bin)但如果你所在环境没法装第三方库或者想要更深入地控制解析过程手写一个简单的解析器也很值得。Hex 每行记录的核心字段除了数据和地址还有类型标记00是数据记录04是扩展线性地址05是起始线性地址01是文件结束。遇到04类型时它的数据就是后续数据的段基址需要左移 16 位参与地址计算。下面是一个我常用作模板的手写解析脚本它能从 hex 文件中提取指定地址区间的 bin 数据并在空洞处用 0xFF 填充import argparse def hex_to_bin(hex_path, bin_path, base_addr0x08000000, total_size0x10000, fill0xFF): data bytearray([fill] * total_size) extended_addr 0 with open(hex_path, r) as f: for line in f: line line.strip() if not line.startswith(:): continue byte_len int(line[1:3], 16) addr int(line[3:7], 16) rec_type int(line[7:9], 16) payload bytes.fromhex(line[9:9 byte_len * 2]) if rec_type 0x04: # extended linear address extended_addr int.from_bytes(payload, big) 16 elif rec_type 0x00: # data record full_addr extended_addr addr if full_addr base_addr or full_addr byte_len base_addr total_size: continue offset full_addr - base_addr data[offset:offset byte_len] payload elif rec_type 0x01: break if bin_path: with open(bin_path, wb) as f: f.write(data) return data if __name__ __main__: parser argparse.ArgumentParser(descriptionhex to bin converter) parser.add_argument(hex_file) parser.add_argument(bin_file) parser.add_argument(--base, typelambda x: int(x, 0), default0x08000000) parser.add_argument(--size, typelambda x: int(x, 0), default0x10000) parser.add_argument(--fill, typelambda x: int(x, 0), default0xFF) args parser.parse_args() hex_to_bin(args.hex_file, args.bin_file, args.base, args.size, args.fill)脚本里最核心的就是extended_addr的累积处理。很多 hex 文件地址超过 0xFFFF 后会用 0x04 行切段如果不把段基址左移 16 位叠加进去后续所有数据地址都会错乱。实测中这是最容易写错的位置我曾经因为漏掉这一段转换出来的 bin 前 64KB 全是错误的排查了好几个小时。3.4 Keil MDK 工程内直接生成 bin 文件如果用的是 Keil MDK 环境不想离开 IDE 去额外转换可以通过配置 User 页签实现编译后自动生成 bin。具体步骤是在 Options for Target - User - After Build/Rebuild 里加一行fromelf.exe --bin -o $LL.bin #L这里fromelf.exe是 Keil 自带的 ARM 映像转换工具$LL.bin表示与链接产物同名但扩展名为 .bin 的文件#L是链接器的输出文件即 .axf。这样每次点编译工程目录下就会自动多出 bin 文件省去手动转换的步骤。不过要踩过一个坑fromelf 转换生成 bin 文件的起点取决于分散加载文件里首个执行域的地址。如果你的工程镜像不是从 0x08000000 起始而是包含多个 RO 段转换出来的 bin 可能不是你预想的 Flash 镜像。所以用任何工具转换后一定要用文本对比或者 bin 查看工具确认轮廓别等到烧录时才追悔。4. 实战中的常见坑与排查记录4.1 校验和报错别急着怪工具有次同事拿一个客户发来的 hex 文件转换objcopy 直接报错提示 unexpected end of file 或者 invalid hex。我检查之后发现文件被邮件系统截断了几行但是文本编辑器打开看着又正常因为文件按行损坏校验和根本过不了。这时候我的排查思路是先看文件尾部有没有:00000001FF这行结束记录没有就说明文件不完整再随机抽几行手工计算校验和对比最后的两位。如果校验和全部对不上大概率是传输过程出了问题重新让客户用压缩包发送更靠谱。转换工具报错不是坏事至少它拦截了不完整文件进烧录流程。4.2 地址不连续导致 bin 文件膨胀另一个高频坑就是 hex 里地址跳变过大。芯片内部 Flash 一般从 0x08000000 开始但有些 hex 文件会把固件信息放在 0x0800F000配置区放在 0x0800F800中间有几十 KB 的空洞。如果直接把整个 hex 转成 bin 并且填充 0x00bin 文件会非常大影响传输和升级效率。我实际遇到过一个 bug某个 OTA 升级包从 80KB 变成 220KB就是因为把 hex 里的空洞全部用 0x00 填充了。固件本身只有 80KB但是地址区间横跨两段工具自动补零导致包体膨胀。后来用 srec_cat 只提取有效数据段再分段打包文件就恢复正常了。所以动手转换前先搞清楚你需要的 bin 到底应该是完整映射一整块 Flash还是只包含有效代码段。前者适合烧录器直接烧录后者适合 OTA 压缩传输。4.3 提取指定区间数据有时候你不需要整个 bin只想要 hex 里的某段数据。比如只提取 bootloader 部分或者只拿出某个校准参数表。用 srec_cat 非常直接srec_cat firmware.hex -intel -split 0x08000000 0x08004000 -o boot.bin -binary-split后面跟起始地址和结束地址表示只截取这个范围内的数据。如果用 Python 脚本上面的hex_to_bin函数只需要修改base_addr和total_size就可以实现同样效果逻辑更透明。测试过几次之后我越来越倾向于使用自己的脚本因为出了问题你知道能查哪里而黑盒工具往往连日志都不给。4.4 填充字节和大小端别想当然空洞填充字节选什么和具体芯片和升级方案强相关。大多数 Flash 空白区域是 0xFF因为闪存擦除后就是全 1。如果你非要填 0x00烧进去虽然不影响正常代码但在某些做 CRC 校验的 bootloader 里计算出来的校验和会和官方版本不一致导致升级包被拒绝。大小端也是一样。hex 文件里每一行数据的字节顺序和 bin 文件一致不需要额外翻转。但如果你要把 hex 中的某个 16 位或者 32 位变量提取出来单独处理就得留意芯片的大小端模式。ARM Cortex-M 默认小端低字节在前但有些外设配置数据可能是大端存储转换时就需要手动交换字节序。5. 转换后验证不能被省略的收尾动作5.1 用对比工具确认 bin 内容转换完之后我强烈建议做一次加载地址和数据内容的双重校验。直接打开 hex 文件看代码段起始地址和转换后的 bin 文件开头几个字节做比对。常见的做法是用文本编辑器打开 hex找到第一条:10数据记录对应的地址然后去 bin 文件对应偏移位置看数据是否一致。如果是 Linux 环境用xxd快速看 bin 头部xxd -l 128 firmware.bin检查开头是否有 0x00 开头的栈指针比如 STM32 中断向量表第一个字是初始栈指针第二个字是复位向量如果发现全是 0xFF 或者全是 0x00说明地址映射或者转换参数出了问题。5.2 对比文件哈希保证一致性还有一种验证方式是对转换前后做逻辑等价性检查但 hex 和 bin 没有直接的哈希可比性因为地址信息被剥离了。我的做法是先用objcopy -I ihex -O binary生成一个标准参考 bin然后和自己改过的脚本方案输出做 MD5 对比md5sum reference.bin script_output.bin如果哈希一致说明脚本逻辑正确如果不一致再逐段对比差异范围。这种方式在修改转换脚本时特别有价值能迅速发现回归问题。这事的本质还是对固件交付链路负责其实hex 文件转 bin 文件工具这个需求背后本质上是对固件交付链路的把控。你在开发环境里用 IDE 顺手编译出 hex以为大功告成但产线烧录、OTA 打包、固件加密这些环节才是真正考验格式理解的地方。转换工具不是简单地换个扩展名它涉及到地址连续性、数据完整性、目标芯片存储布局等多方面因素。根据我自己在多个项目里反复折腾的经验给你一个最稳妥的搭配方案日常开发用 objcopy 或 Keil 的 fromelf 自动生成 bin遇到地址空洞、多段合并、区间提取这类复杂需求用 srec_cat如果做产线工具或者自动化构建毫不犹豫上 Python 脚本把地址范围、填充字节都做成参数化配置。这样无论 MCU 怎么换、工具链怎么变转换这条链路始终掌握在自己手里。最后再啰嗦一句任何转换完成之后抽几秒看看文件大小和头部数据别让它成为你晚上加班的导火索。本文还有配套的精品资源点击获取