资讯动态

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

发布时间:2026/10/17 2:33:06 来源:尧图企业网站定制
简介这是一款面向嵌入式开发者的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, offsetbase_addr) # 写入 HEX 文件 ih.write_hex_file(hex_path) # 打印地址范围方便核对 print(faddress 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_addrNone, end_addrNone): 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, pad0xFF) print(fexported: 0x{ih.minaddr():08X} - 0x{ih.maxaddr():08X}) if __name__ __main__: hex_to_bin(full.hex, full.bin)逻辑说明HEX 里可能存在不连续的段直接转 BIN 时中间空洞会被填充。pad0xFF是常见做法因为 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, offsetbase1) ih.loadbin(bin2, offsetbase2) # 检查是否有地址重叠 if ih.minaddr() base1 or ih.maxaddr() base2 0x100000: print(warning: check address overlap manually) ih.write_hex_file(out_hex) print(fmerged range: 0x{ih.minaddr():08X} - 0x{ih.maxaddr():08X}) if __name__ __main__: # 假设 bootloader 在 0x08000000app 在 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(faddress overlap: 0x{overlap_start:08X} - 0x{overlap_end:08X}) # 合并 ih1.merge(ih2) ih1.write_hex_file(out_hex) print(fmerged: 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, align0x800): 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(faligned to 0x{aligned_end:08X})参数align根据你的 Flash 扇区大小设置常见是 0x8002KB或 0x10004KB。填充值用 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], offsetseg[base]) print(floaded {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(foutput: {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看是否有地址越界或重叠告警。从那以后我每次做固件合并都强制走一遍「打印地址范围 → 重叠检查 → 对齐填充 → 反向校验」这四步再急也不跳过。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑