#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 合并 bootloader.hex + app.hex 为出厂烧录用的 merged_single.hex。 用法: python tools/merge_hex.py [--out OUT] [--bl bootloader.hex] [--app app.hex] 默认输入/输出均位于 PY32F040开发框架/Output/ 下。 """ import argparse import os import sys # 默认相对本脚本: ../Output (脚本位于 框架/tools/) _DEF_OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "Output") def parse_hex(path): """解析 Intel HEX -> dict[abs_addr] = byte。完整支持 0x04 扩展线性地址记录, 得到 32 位绝对地址。本工程基址 0x08000000 即由 0x04 记录(:020000040800F2)给出。""" mem = {} upper = 0 # 0x04 记录的扩展线性地址(左移到高 16 位) with open(path, "r") as f: for line in f: line = line.strip() if not line.startswith(":"): continue n = int(line[1:3], 16) addr = int(line[3:7], 16) rec = int(line[7:9], 16) if rec == 0x00: data = bytes.fromhex(line[9:9 + n * 2]) base = (upper << 16) | addr for i, b in enumerate(data): mem[base + i] = b elif rec == 0x01: # EOF break elif rec == 0x04: # 扩展线性地址 upper = int(line[9:13], 16) # 0x02/0x03/0x05 等本工程不涉及,忽略 return mem def _emit_ela(upper): """0x04 扩展线性地址记录。""" body = "02000004%04X" % upper cs = 0 for i in range(0, len(body), 2): cs += int(body[i:i + 2], 16) cs = (0x100 - (cs & 0xFF)) & 0xFF return ":%s%02X" % (body, cs) def _emit(buf, addr): body = "%02X%04X00" % (len(buf), addr) + "".join("%02X" % b for b in buf) cs = 0 for i in range(0, len(body), 2): cs += int(body[i:i + 2], 16) cs = (0x100 - (cs & 0xFF)) & 0xFF return ":%s%02X" % (body, cs) def write_hex(path, mem): """按 32 位绝对地址写出;每当高 16 位(upper)变化时插入 0x04 扩展记录, 保证 0x08000000 / 0x08004800 等基址被烧录器正确识别。""" addrs = sorted(mem) lines = [] buf = [] cur_upper = None cur = None next_addr = None def flush(): if buf: lines.append(_emit(buf, cur)) buf.clear() for a in addrs: up = (a >> 16) & 0xFFFF off = a & 0xFFFF if cur_upper is None: cur_upper = up lines.append(_emit_ela(up)) cur = off next_addr = a + 1 elif up != cur_upper: flush() cur_upper = up lines.append(_emit_ela(up)) cur = off next_addr = a + 1 elif a != next_addr: flush() cur = off next_addr = a + 1 buf.append(mem[a]) next_addr = a + 1 if len(buf) == 16: flush() cur = next_addr & 0xFFFF flush() lines.append(":00000001FF") with open(path, "w") as f: f.write("\n".join(lines) + "\n") def main(): ap = argparse.ArgumentParser() ap.add_argument("--bl", default=os.path.join(_DEF_OUT_DIR, "bootloader.hex")) ap.add_argument("--app", default=os.path.join(_DEF_OUT_DIR, "app.hex")) ap.add_argument("--out", default=os.path.join(_DEF_OUT_DIR, "merged_single.hex")) args = ap.parse_args() for p in (args.bl, args.app): if not os.path.isfile(p): sys.exit("ERROR: 找不到 %s" % p) bl = parse_hex(args.bl) app = parse_hex(args.app) print("bootloader: %d bytes, range 0x%X-0x%X" % (len(bl), min(bl), max(bl))) print("app: %d bytes, range 0x%X-0x%X" % (len(app), min(app), max(app))) overlap = set(bl) & set(app) if overlap: sys.exit("ERROR: 地址重叠 %d 处 (例如 0x%X)" % (len(overlap), min(overlap))) merged = dict(bl) merged.update(app) write_hex(args.out, merged) nlines = sum(1 for _ in open(args.out)) print("merged: %d bytes -> %s (%d 行)" % (len(merged), args.out, nlines)) if __name__ == "__main__": main()