资讯动态

BIN与HEX互转合并实战:嵌入式固件交付的地址对齐与校验

发布时间:2026/10/15 1:33:25 来源:尧图企业网站定制
简介这是一款面向嵌入式开发者的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客户验收文档却要 HEXBootloader 升级包按扇区偏移拼好了 BIN但仿真器加载又只吃 HEX更麻烦的是多片 Flash 分区固件要合成一个整包地址空洞还得用 0xFF 补齐。BIN 文件和 HEX 文件可以互转合并说的就是把这几种格式之间的转换、拼接、地址对齐一次做干净而不是靠某个 IDE 的导出按钮碰运气。它解决的核心问题是让同一份固件在不同工具链、不同烧录器、不同验收流程之间无损流转。适合谁写 MCU 固件、做产线工装、维护 OTA 升级包的工程师尤其是手里同时握着 BIN 和 HEX 两套文件、却说不清它们地址边界的人。2. 先把 BIN 和 HEX 的地址语义掰开为什么不能直接拼2.1 BIN 是裸数据HEX 是带地址的记录BIN 文件本质上就是一段连续的二进制字节流它自己不知道这段数据应该烧到 Flash 的哪个地址。你给它 64KB 数据它就从第一个字节排到最后一个字节中间没有地址信息也没有校验。烧录器拿到 BIN 必须额外告诉它起始地址否则只能默认从 0x08000000 这类约定地址开始写。HEX 文件常见的是 Intel HEX 格式则完全不同它是文本格式每一行是一条记录记录里包含数据长度、目标地址、记录类型、数据和校验和。也就是说 HEX 自带地址信息还能表达不连续的内存区域。这就是为什么多分区固件用 HEX 表达更自然而 BIN 更适合直接喂给烧录器。理解这个差异之后互转的逻辑就清楚了BIN 转 HEX 必须补一个起始地址HEX 转 BIN 必须处理地址空洞否则空洞会被压缩掉导致偏移错乱。2.2 合并的本质是地址空间上的区间运算很多人以为合并就是把两个文件的内容首尾相接这是最常见的误解。真正的合并是在地址空间上做区间运算每个输入文件对应一个或多个地址区间合并时要按地址排序、检测重叠、填充空洞。如果两个区间重叠你必须决定是报错、覆盖还是保留其中一个这个策略直接决定合并结果对不对。举个典型场景Bootloader 占 0x08000000 到 0x08007FFFApplication 占 0x08008000 到 0x0803FFFF。这两个区间不重叠合并后应该是一个从 0x08000000 开始、总长 0x40000 的连续镜像。但如果你拿到的 Application BIN 是从 0x08008000 开始导出的裸数据直接和 Bootloader BIN 拼接得到的文件起始地址就错了烧进去必然跑飞。2.3 工具选型objcopy、srec_cat 还是自己写脚本常见做法有三条路。第一条是 GNU objcopy它能把 ELF 转成 BIN 或 HEX也能指定 --change-addresses 做地址偏移但它不擅长多文件合并和空洞填充。第二条是 srec_cat这是专门处理 HEX、SREC、BIN 的工具支持多输入、地址偏移、填充、裁剪功能最全缺点是参数多、文档偏老。第三条是自己写 Python 脚本用 intelhex 库读写 HEX用 bytes 处理 BIN灵活度最高适合把转换逻辑固化进 CI。我一般会这样选单文件格式转换用 objcopy多文件合并加填充用 srec_cat需要集成到构建流水线、做自动化校验的用 Python 脚本。下面分别给出可复现的命令和代码。3. 用 objcopy 和 srec_cat 跑通单文件互转3.1 BIN 转 HEX必须显式指定架构和起始地址objcopy 从 BIN 转 HEX 时因为 BIN 没有地址信息必须用 -I binary 告诉它输入是裸二进制再用 --change-addresses 指定加载地址。命令如下# 把 app.bin 转成 app.hex加载地址设为 0x08008000 arm-none-eabi-objcopy \ -I binary \ -O ihex \ --change-addresses 0x08008000 \ app.bin app.hex这里 -I binary 是输入格式-O ihex 是输出 Intel HEX--change-addresses 把默认的 0 地址整体平移到 0x08008000。注意这个参数是加法偏移不是设置绝对地址如果输入 BIN 内部已经隐含了某个基址你要先算清楚差值。转换完成后用 objdump 或 hexdump 检查第一行记录的地址字段确认是 08008000 而不是 00000000。3.2 HEX 转 BIN用 srec_cat 处理地址空洞HEX 转 BIN 的坑在于地址不连续。objcopy 遇到空洞会直接压缩导致 BIN 里数据错位。srec_cat 可以用 -fill 参数把空洞填成指定值保证输出 BIN 的偏移正确# 把 app.hex 转成 app.bin空洞填 0xFF输出范围限定在 0x08008000 到 0x0803FFFF srec_cat app.hex -intel \ -fill 0xFF 0x08008000 0x0803FFFF \ -o app.bin -binary-fill 0xFF 后面跟的是填充范围和填充值这一步很关键。如果不加 -fillsrec_cat 默认只输出有数据的区间空洞会被跳过BIN 长度就和预期不符。输出范围建议显式写出避免 HEX 里混入配置区或选项字节导致 BIN 被撑大。3.3 合并两个 HEX按地址排序并检测重叠合并 Bootloader 和 Application 两个 HEXsrec_cat 可以直接接受多个输入# 合并 boot.hex 和 app.hex重叠区域直接报错 srec_cat boot.hex -intel app.hex -intel \ -o merged.hex -intelsrec_cat 默认行为是后输入的记录覆盖先输入的如果两个文件地址重叠它不会主动报错而是静默覆盖。这在调试阶段很危险。稳妥做法是先用 -crop 或 -exclude 把各自范围裁开确认无重叠后再合并。也可以加 -contradict 参数让它在检测到冲突时直接失败把问题暴露在构建阶段而不是烧录阶段。4. 用 Python 脚本把互转合并做成可复现流程4.1 安装 intelhex 并读取 HEX 的地址区间命令行工具适合手动操作但要集成到 CI 或做批量处理Python 更可控。先装库pip install intelhex读取一个 HEX 文件并打印它的地址区间from intelhex import IntelHex def load_hex(path): ih IntelHex() ih.loadhex(path) # 获取所有有数据的地址段 segments ih.segments() for start, end in segments: print(fsegment: 0x{start:08X} - 0x{end-1:08X}, size{end-start}) return ih ih load_hex(app.hex)segments() 返回的是左闭右开区间列表每个元素是 (start, end)。这一步能帮你快速判断 HEX 里到底有几段数据、有没有意外的地址跳变。如果 segments 数量超过预期说明链接脚本或导出参数有问题先别急着转 BIN。4.2 HEX 转 BIN 时按地址范围裁剪并填充下面这个函数把 HEX 转成指定地址范围的 BIN空洞填 0xFFfrom intelhex import IntelHex def hex_to_bin(hex_path, bin_path, start_addr, end_addr, fill0xFF): ih IntelHex() ih.loadhex(hex_path) # 只保留目标范围内的数据 ih ih[ start_addr : end_addr ] # 把空洞填成 fill 值 for addr in range(start_addr, end_addr): if addr not in ih: ih[addr] fill ih.tobinfile(bin_path) print(fwritten {end_addr - start_addr} bytes to {bin_path}) hex_to_bin(app.hex, app.bin, 0x08008000, 0x08040000)ih[start:end] 做的是切片超出范围的数据会被丢弃。后面的循环逐字节检查并填充虽然效率不高但对几十 KB 到几 MB 的固件完全够用。如果固件很大可以改用 ih.padding 或先构造一个全 0xFF 的 IntelHex 再合并速度会快很多。4.3 合并多个 BIN 并生成带地址的 HEX合并多个 BIN 时每个 BIN 都要指定自己的加载地址然后统一写成一个 HEXfrom intelhex import IntelHex def merge_bins_to_hex(output_hex, parts): parts: list of (bin_path, load_addr) merged IntelHex() for bin_path, load_addr in parts: with open(bin_path, rb) as f: data f.read() for offset, byte in enumerate(data): addr load_addr offset if addr in merged and merged[addr] ! byte: raise ValueError(fconflict at 0x{addr:08X}) merged[addr] byte merged.write_hex_file(output_hex) print(fmerged {len(parts)} parts into {output_hex}) merge_bins_to_hex(merged.hex, [ (boot.bin, 0x08000000), (app.bin, 0x08008000), ])这段代码在写入前做了冲突检测如果同一地址出现不同字节就抛异常。这是合并流程里最值得保留的一道防线比事后用工具比对可靠得多。参数 parts 是一个列表每项包含 BIN 路径和加载地址顺序不重要因为最终按地址写入。5. 避坑与排查合并后烧录跑飞的五个常见原因5.1 现象烧录成功但程序不运行读回 Flash 发现数据错位原因通常是 HEX 转 BIN 时没有填充空洞导致 BIN 长度比预期短烧录器按连续地址写入后后半段数据整体前移。解决方法是转换时显式指定输出范围并填充 0xFF转换后用二进制比对工具确认 BIN 长度等于 end_addr - start_addr。5.2 现象合并后的 HEX 在仿真器里加载报地址冲突原因是两个输入文件地址区间重叠srec_cat 默认静默覆盖没有报错。解决方法是合并前先用 segments() 或 srec_cat 的 -crop 把各自范围裁开合并时加 -contradict 让冲突直接失败。5.3 现象BIN 转 HEX 后烧录地址不对程序从 0x00000000 开始跑原因是 objcopy 的 --change-addresses 是加法偏移如果 BIN 本身是从 0 地址导出的偏移量就是目标基址但如果 BIN 是从某个中间地址导出的偏移量要减去原基址。解决方法是转换前确认 BIN 的导出基址转换后检查 HEX 第一行记录的地址字段。5.4 现象合并后的固件体积异常大超出 Flash 容量原因是 HEX 里混入了配置区、选项字节或调试段这些区域的地址可能远离主程序区导致合并后地址跨度被撑大。解决方法是在转换和合并时用 -crop 或切片限定地址范围只保留实际要烧录的区间。5.5 现象产线烧录工装只认 BIN但客户要求交付 HEX两边校验和不一致原因是 BIN 和 HEX 的校验方式不同BIN 通常按字节累加或 CRCHEX 自带每行校验和。解决方法是交付前用同一份源数据分别生成 BIN 和 HEX再用脚本读回两者内容做逐字节比对确认数据一致后再分别计算各自的校验值。6. 把互转合并固化进构建脚本一个可复用的校验习惯真正让这套流程可靠的不是某一条命令而是把转换、合并、校验串成一条可重复执行的流水线。我一般会在构建目录下放一个 makefile 或 shell 脚本每次出包自动做三件事生成 BIN、生成 HEX、读回比对。下面是一个最小化的校验脚本用 Python 读回 BIN 和 HEX逐字节比对from intelhex import IntelHex def verify_bin_hex(bin_path, hex_path, base_addr): with open(bin_path, rb) as f: bin_data f.read() ih IntelHex() ih.loadhex(hex_path) for offset, byte in enumerate(bin_data): addr base_addr offset if ih[addr] ! byte: raise ValueError( fmismatch at 0x{addr:08X}: bin0x{byte:02X} hex0x{ih[addr]:02X} ) print(fverified {len(bin_data)} bytes OK) verify_bin_hex(app.bin, app.hex, 0x08008000)这个脚本的价值在于它不关心你是用 objcopy 还是 srec_cat 生成的只认最终产物。只要比对通过就说明两种格式承载的数据完全一致交付给产线和客户都不会出岔子。参数 base_addr 必须和 BIN 导出时的基址一致否则比对会从第一个字节就失败。还有一个习惯值得坚持每次合并多个分区后把合并结果反解成各分区的 BIN再和原始分区 BIN 逐一比对。这一步能抓出地址偏移算错、填充值写错、重叠覆盖这类问题。我吃过一次亏Bootloader 和 Application 合并时地址偏移少加了 0x8000烧进去后 Bootloader 跳转直接跳到 Application 中间现象是程序随机跑飞查了两天才定位到合并脚本。从那以后合并后反解比对就成了固定动作。如果要把这套流程交给团队建议把转换参数、地址范围、填充值写进一个配置文件脚本读配置执行而不是把地址硬编码在命令里。这样换一颗芯片、改一次分区只需要改配置不用翻脚本。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑