news 2026/10/15 19:41:10

嵌入式固件BIN与HEX互转合并实战:地址对齐与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式固件BIN与HEX互转合并实战:地址对齐与避坑指南

简介:这是一款面向嵌入式开发者的BIN与HEX文件互转合并工具,基于MFC对话框程序实现,参考了公开的转换代码并加以完善,适合需要处理单片机固件格式转换的工程师使用。资源包共22个文件,包含5个h头文件、4个cpp源文件、2个exe可执行程序,以及rc资源脚本、dsp/dsw工程文件、ico图标等,压缩包约654KB,可直接运行或自行编译修改。该工具支持BIN转HEX、HEX转BIN以及HEX文件合并三项核心功能,并针对实际使用中的问题持续更新:修正了转HEX时A~F字符大小写导致Keil出错的问题,解决了BIN文件超过64K时数据错乱的情况,还修复了C8051所用Keil编译的HEX文件转换到BIN时的错误。目前已有3661人学习下载,适合需要快速完成固件格式转换、合并烧录文件的开发者参考使用。

1. 从一次产线刷写翻车说起:BIN 和 HEX 到底该怎么互转合并

上周帮一个做嵌入式量产的朋友排查问题,他们产线烧录时把两个不同地址段的固件分别导出成 BIN,结果合并后设备直接变砖。拆开看,问题出在 BIN 是纯二进制、没有地址信息,两个文件首尾相接并不等于地址连续;而 HEX 自带地址记录,合并逻辑完全是另一套。这类翻车在固件打包、OTA 差分、Bootloader + App 拼接场景里非常常见。BIN 和 HEX 的互转与合并,本质是「地址信息的有无」和「记录格式的解析」两件事。这份资源就是围绕这个痛点,把 bin/hex 互转、按地址合并、校验对齐这些操作讲清楚,适合做嵌入式固件打包、产线烧录、OTA 升级的从业者,新手能照着命令跑通,熟手能看清地址边界和参数取舍。

2. 先搞懂 BIN 与 HEX 的格式差异:为什么不能直接 cat 拼接

2.1 BIN 是裸数据,HEX 是带地址的文本记录

BIN 文件就是一段连续的二进制字节流,它本身不包含任何地址信息。你拿到一个 32KB 的 BIN,只知道它有 32768 个字节,但不知道这段数据应该烧到 Flash 的哪个位置。地址信息通常由烧录工具的参数、链接脚本或者产线配置来指定。

HEX 文件则完全不同。以最常见的 Intel HEX 格式为例,它是由一行行 ASCII 文本组成的记录,每行以冒号开头,包含数据长度、地址、记录类型、数据和校验和。记录类型决定了这行是数据、扩展地址还是文件结束。正因为每行都带地址,HEX 才能表达「这段数据在 0x08000000,那段数据在 0x08020000」这种非连续布局。

这个差异直接决定了互转和合并的策略:BIN 转 HEX 必须补地址,HEX 转 BIN 必须处理地址空洞,合并两个 BIN 必须确认地址连续性,合并两个 HEX 则要按地址记录做归并。

2.2 常见工具链里的格式约定

不同工具链对 BIN 和 HEX 的默认行为不一样。比如某些 IDE 默认输出 HEX,地址从链接脚本的 ROM 起始地址开始;而有些量产工具只认 BIN,需要你在命令行里显式指定基地址。我一般会先确认三件事:固件的链接起始地址、是否有多个不连续的段、目标烧录工具支持哪种格式。这三件事没确认就动手转,后面大概率要返工。

提示:拿到一个 BIN 却不知道基地址时,不要猜。先看链接脚本或 map 文件里的 ROM 区域定义,实在找不到就问固件提供方要 HEX 版本,从 HEX 里反推地址。

3. 用脚本完成 BIN 与 HEX 互转:从命令行到参数细节

3.1 环境准备与工具选型

常见做法是用 Python 的intelhex库,它同时支持读写 Intel HEX 和 BIN,跨平台,脚本化程度高。安装很简单:

pip install intelhex

选它而不是直接用objcopy的原因是:objcopy对地址空洞和段合并的处理不够直观,而intelhex提供了merge、tobinfile、fromfile这些语义清晰的接口,排查问题时能逐段打印地址范围。如果你习惯用objcopy,也可以,但下面这些脚本逻辑能帮你理解底层发生了什么。

3.2 BIN 转 HEX:补上基地址

from intelhex import IntelHex def bin_to_hex(bin_path, hex_path, base_addr): """ bin_path: 输入 BIN 文件路径 hex_path: 输出 HEX 文件路径 base_addr: BIN 数据在 Flash 中的起始地址,如 0x08000000 """ ih = IntelHex() # 从 BIN 文件加载数据,指定起始地址 ih.loadbin(bin_path, offset=base_addr) # 写入 HEX 文件 ih.write_hex_file(hex_path) # 打印地址范围,方便核对 print(f"address range: 0x{ih.minaddr():08X} - 0x{ih.maxaddr():08X}") if __name__ == "__main__": bin_to_hex("app.bin", "app.hex", 0x08000000)

逻辑说明:loadbin把 BIN 的字节流按顺序放到从offset开始的地址空间里,write_hex_file再按 Intel HEX 格式逐行输出。参数base_addr必须和链接脚本里的 ROM 起始地址一致,否则烧录后跳转地址全错。运行后打印的地址范围要和你预期的一致,比如 0x08000000 到 0x0800FFFF 表示 64KB 数据。

3.3 HEX 转 BIN:处理地址空洞

from intelhex import IntelHex def hex_to_bin(hex_path, bin_path, start_addr=None, end_addr=None): """ hex_path: 输入 HEX 文件 bin_path: 输出 BIN 文件 start_addr/end_addr: 可选,指定导出范围;不指定则用文件实际范围 """ ih = IntelHex(hex_path) if start_addr is not None and end_addr is not None: # 只导出指定地址段 ih = ih[ start_addr : end_addr ] # 地址空洞默认填 0xFF,符合 Flash 擦除后的状态 ih.tobinfile(bin_path, pad=0xFF) print(f"exported: 0x{ih.minaddr():08X} - 0x{ih.maxaddr():08X}") if __name__ == "__main__": hex_to_bin("full.hex", "full.bin")

逻辑说明:HEX 里可能存在不连续的段,直接转 BIN 时中间空洞会被填充。pad=0xFF是常见做法,因为 Flash 擦除后就是 0xFF,填 0x00 反而可能导致校验失败。如果你只想要其中一段,用切片语法指定地址范围,避免把 Bootloader 区域也导出来。

3.4 合并两个 BIN:先确认地址连续性

from intelhex import IntelHex def merge_bins(bin1, base1, bin2, base2, out_hex): """ 把两个 BIN 按各自基地址合并成一个 HEX """ ih = IntelHex() ih.loadbin(bin1, offset=base1) ih.loadbin(bin2, offset=base2) # 检查是否有地址重叠 if ih.minaddr() < base1 or ih.maxaddr() > base2 + 0x100000: print("warning: check address overlap manually") ih.write_hex_file(out_hex) print(f"merged range: 0x{ih.minaddr():08X} - 0x{ih.maxaddr():08X}") if __name__ == "__main__": # 假设 bootloader 在 0x08000000,app 在 0x08020000 merge_bins("boot.bin", 0x08000000, "app.bin", 0x08020000, "merged.hex")

逻辑说明:合并的关键是两次loadbin的offset不能重叠。如果 app 的起始地址落在 bootloader 范围内,后加载的数据会覆盖先加载的,而且不会有任何报错。所以我在脚本里加了一个粗略的范围检查,实际使用时建议打印两个 BIN 的地址区间做人工核对。合并结果输出为 HEX 而不是 BIN,是因为 HEX 保留了地址信息,后续烧录工具能正确识别两个段。

4. 合并 HEX 与地址对齐:避开重叠和空洞的坑

4.1 两个 HEX 合并的正确姿势

from intelhex import IntelHex def merge_hex(hex1, hex2, out_hex): ih1 = IntelHex(hex1) ih2 = IntelHex(hex2) # 检查地址重叠 overlap_start = max(ih1.minaddr(), ih2.minaddr()) overlap_end = min(ih1.maxaddr(), ih2.maxaddr()) if overlap_start <= overlap_end: raise ValueError(f"address overlap: 0x{overlap_start:08X} - 0x{overlap_end:08X}") # 合并 ih1.merge(ih2) ih1.write_hex_file(out_hex) print(f"merged: 0x{ih1.minaddr():08X} - 0x{ih1.maxaddr():08X}") if __name__ == "__main__": merge_hex("boot.hex", "app.hex", "merged.hex")

逻辑说明:merge方法会把第二个 HEX 的数据并入第一个,遇到同一地址时后者覆盖前者。所以合并前必须做重叠检查,否则你可能在不知情的情况下丢了一段代码。重叠检查的逻辑是取两个文件地址范围的交集,如果交集非空就报错。这个检查在产线脚本里应该作为强制步骤。

4.2 地址对齐与填充策略

有些烧录器要求固件按扇区对齐,比如 2KB 或 4KB。如果 BIN 长度不是扇区大小的整数倍,直接烧录可能失败。常见做法是在合并后做一次对齐填充:

from intelhex import IntelHex def align_hex(hex_path, out_hex, align=0x800): ih = IntelHex(hex_path) max_addr = ih.maxaddr() aligned_end = ((max_addr // align) + 1) * align # 在末尾填充 0xFF 到对齐边界 for addr in range(max_addr + 1, aligned_end): ih[addr] = 0xFF ih.write_hex_file(out_hex) print(f"aligned to 0x{aligned_end:08X}")

参数align根据你的 Flash 扇区大小设置,常见是 0x800(2KB)或 0x1000(4KB)。填充值用 0xFF 而不是 0x00,原因和前面一样。这个步骤在 OTA 差分升级时尤其重要,因为差分算法通常按固定块大小比对。

4.3 校验合并结果:别只看文件大小

合并完成后,我一般会做两件事:一是用ih.minaddr()和ih.maxaddr()确认地址范围覆盖了预期区域;二是把合并后的 HEX 转回 BIN,用md5sum和原始 BIN 比对(如果地址连续的话)。如果地址不连续,就逐段导出再比对。这一步能抓出大部分地址错位和覆盖问题。

注意:不要用文件大小判断合并是否成功。HEX 是文本格式,同样数据量下大小和 BIN 差好几倍,而且地址记录本身也占空间。

5. 避坑与排查:那些让你加班到凌晨的细节

5.1 现象:烧录后设备不启动,但校验通过

原因:BIN 转 HEX 时基地址填错,比如把 0x08000000 写成了 0x00000000。烧录工具按 HEX 里的地址写入,但芯片复位后从 0x08000000 取指令,取到的是空区域。

解决:转完后用ih.minaddr()打印起始地址,和链接脚本里的 ROM 起始地址逐位核对。不要凭记忆填。

5.2 现象:合并后 app 区域数据被截断

原因:两个 BIN 地址范围有重叠,后加载的覆盖了先加载的,但脚本没有报错。

解决:在合并脚本里强制做重叠检查,如 4.1 节的代码。如果确实需要覆盖,明确写出覆盖逻辑并记录日志。

5.3 现象:HEX 转 BIN 后文件比预期大很多

原因:HEX 里包含了不连续的段,转 BIN 时中间空洞被填充,导致文件膨胀。

解决:用切片指定导出范围,只导出需要的段。或者先打印 HEX 的所有段范围,确认哪些是有效数据。

5.4 现象:OTA 差分升级失败,提示块校验错误

原因:合并或对齐时填充值不一致,有的地方填 0x00,有的地方填 0xFF,差分算法按块比对时认为数据变了。

解决:统一填充值为 0xFF,并在对齐脚本里显式指定。差分前先用同一套脚本处理基准固件和升级固件。

5.5 现象:产线烧录工具报「地址越界」

原因:合并后的 HEX 地址范围超出了芯片实际 Flash 大小,比如把 0x08100000 之后的数据也写进去了。

解决:合并前确认芯片型号和 Flash 容量,在脚本里加一个最大地址检查,超过就拒绝输出。

6. 进阶:把互转合并做成可复用的产线脚本

前面都是单次操作的脚本,实际产线上更常见的是把整个流程串起来:从构建输出目录取 BIN,按配置合并,对齐,转 HEX,校验,最后输出到烧录目录。我一般会写一个pack.py,用配置文件描述每个段的名称、路径、基地址和对齐要求。

import json from intelhex import IntelHex def pack(config_path, out_hex): with open(config_path) as f: cfg = json.load(f) ih = IntelHex() for seg in cfg["segments"]: ih.loadbin(seg["path"], offset=seg["base"]) print(f"loaded {seg['name']}: 0x{seg['base']:08X}") # 对齐 align = cfg.get("align", 0x800) max_addr = ih.maxaddr() aligned_end = ((max_addr // align) + 1) * align for addr in range(max_addr + 1, aligned_end): ih[addr] = 0xFF ih.write_hex_file(out_hex) print(f"output: {out_hex}, range 0x{ih.minaddr():08X} - 0x{ih.maxaddr():08X}") if __name__ == "__main__": pack("pack_config.json", "production.hex")

配置文件示例:

{ "align": 2048, "segments": [ {"name": "boot", "path": "boot.bin", "base": "0x08000000"}, {"name": "app", "path": "app.bin", "base": "0x08020000"}, {"name": "param", "path": "param.bin", "base": "0x0807F800"} ] }

这样每次固件更新只需要改配置里的路径,脚本逻辑不用动。验证方法也简单:跑完后用ih.minaddr()和ih.maxaddr()确认范围,再把输出的 HEX 用烧录工具的命令行模式做一次 dry run,看是否有地址越界或重叠告警。

从那以后我每次做固件合并,都强制走一遍「打印地址范围 → 重叠检查 → 对齐填充 → 反向校验」这四步,再急也不跳过。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 19:40:47

星期二男孩概率谜题:条件概率与样本空间如何推导出13/27

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 19:39:22

企业知识库自动化实践:8 步把文档变成知识库

## 背景在学习提升场景里&#xff0c;企业文档散落各处、找人问半天是效率的主要损耗点。## 核心能力- 全程本地运行&#xff0c;原始文档与知识数据不出电脑- 8 步流水线自动化&#xff1a;解析→结构化→质检→复核→分片→向量库→验收- 内置本地大模型&#xff0c;离线推理…

作者头像 李华
网站建设 2026/10/15 19:38:43

基站板卡利旧开通:残留数据影响与规范做法

摘要&#xff1a;基站板卡从原站拆下利旧到新站时&#xff0c;若板卡内残留的原站配置数据未清除干净&#xff0c;会导致参数冲突、入网失败、邻区与切换数据错乱以及License无法导入等问题。本文梳理了残留数据对新建站点的具体影响&#xff0c;并结合典型案例给出清空板卡原有…

作者头像 李华
网站建设 2026/10/15 19:38:39

C++数据类型与变量管理核心技巧详解

1. 为什么数据类型与变量管理是C开发者的必修课刚接触C时&#xff0c;很多人会觉得数据类型和变量声明是再基础不过的内容。但真正经历过大型项目开发的老手都清楚&#xff0c;数据类型的选择和变量管理方式直接影响着程序的内存安全性、运行效率和代码可维护性。我曾在调试一个…

作者头像 李华
网站建设 2026/10/15 19:38:10

Unity Netcode网络变量进阶:自定义序列化与带宽优化实战

多人联机游戏里&#xff0c;最绕不开的就是数据同步。网络变量&#xff08;NetworkVariable&#xff09;是Unity Netcode框架下最基础、最常用的同步手段&#xff0c;但很多开发者在用它处理简单类型时很顺手&#xff0c;一旦碰到复杂数据结构、需要精细控制权限和性能时&#…

作者头像 李华