136 lines
4.2 KiB
Python
136 lines
4.2 KiB
Python
#!/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()
|