119 lines
4.1 KiB
Python
119 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
合成 GD32E230 初始烧录固件:bootloader + app → 完整 64KB Flash 镜像。
|
||
|
||
布局:
|
||
0x08000000 ┌──────────────┐
|
||
│ Bootloader │
|
||
APP_ADDR ├──────────────┤
|
||
│ Application │
|
||
│ ... │
|
||
FLAG_ADDR │ 0xEEEE标志 │
|
||
0x08010000 └──────────────┘
|
||
|
||
空白区域填充 0xFF(Flash 擦除后的默认值)。
|
||
|
||
用法:
|
||
python merge_bin.py bootloader.bin app.bin -o merged.bin
|
||
python merge_bin.py bootloader.bin app.bin --app-addr 0x08002000 --flag-addr 0x0800FFFC -o merged.bin
|
||
"""
|
||
|
||
import argparse
|
||
import struct
|
||
import sys
|
||
import os
|
||
|
||
|
||
FLASH_BASE = 0x08000000
|
||
FLASH_SIZE = 64 * 1024 # 64KB
|
||
DEFAULT_BOOTLOADER = "gd32e230f8_bootloader_hulk.bin"
|
||
|
||
|
||
def merge(bootloader_path, app_path, output_path, app_addr, flag_addr):
|
||
app_offset = app_addr - FLASH_BASE
|
||
flag_offset = flag_addr - FLASH_BASE
|
||
|
||
# 校验地址合法性
|
||
if app_offset < 0 or app_offset >= FLASH_SIZE:
|
||
print(f"错误:APP 地址 0x{app_addr:08X} 超出 Flash 范围", file=sys.stderr)
|
||
return False
|
||
if flag_offset < 0 or flag_offset + 4 > FLASH_SIZE:
|
||
print(f"错误:标志地址 0x{flag_addr:08X} 超出 Flash 范围", file=sys.stderr)
|
||
return False
|
||
|
||
# 读取文件
|
||
with open(bootloader_path, "rb") as f:
|
||
bootloader = f.read()
|
||
with open(app_path, "rb") as f:
|
||
app = f.read()
|
||
|
||
# 校验 bootloader 不超过 APP 起始地址
|
||
if len(bootloader) > app_offset:
|
||
print(f"错误:Bootloader ({len(bootloader)} 字节) 超过 APP 偏移 ({app_offset} 字节)", file=sys.stderr)
|
||
return False
|
||
|
||
# 校验 APP 不超过 flag 地址
|
||
app_end = app_offset + len(app)
|
||
if app_end > flag_offset:
|
||
print(f"错误:APP 结束地址 0x{FLASH_BASE + app_end:08X} 超过标志地址 0x{flag_addr:08X}", file=sys.stderr)
|
||
return False
|
||
|
||
# 创建 64KB 的 Flash 镜像,填充 0xFF
|
||
image = bytearray(b'\xFF' * FLASH_SIZE)
|
||
|
||
# 写入 bootloader(从 offset 0 开始)
|
||
image[0:len(bootloader)] = bootloader
|
||
|
||
# 写入 APP(从 app_offset 开始)
|
||
image[app_offset:app_offset + len(app)] = app
|
||
|
||
# 写入标志位 0xEEEEEEEE(little-endian uint32,填满 0x0800FFFC~0x0800FFFF)
|
||
struct.pack_into("<I", image, flag_offset, 0xEEEEEEEE)
|
||
|
||
# 输出
|
||
with open(output_path, "wb") as f:
|
||
f.write(image)
|
||
|
||
print(f"合成完成: {output_path}")
|
||
print(f" Bootloader : {len(bootloader):>6} 字节 @ 0x{FLASH_BASE:08X}")
|
||
print(f" Application: {len(app):>6} 字节 @ 0x{app_addr:08X}")
|
||
print(f" Flag : 0xEEEE @ 0x{flag_addr:08X}")
|
||
print(f" 总大小 : {FLASH_SIZE:>6} 字节 ({FLASH_SIZE // 1024}KB)")
|
||
return True
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="合成 GD32E230 初始烧录固件 (bootloader + app)")
|
||
parser.add_argument("app", help="Application bin 文件路径")
|
||
parser.add_argument(
|
||
"-b",
|
||
"--bootloader",
|
||
default=DEFAULT_BOOTLOADER,
|
||
help=f"Bootloader bin 文件路径 (默认: {DEFAULT_BOOTLOADER})",
|
||
)
|
||
parser.add_argument("-o", "--output", default=None, help="输出文件路径 (默认: APP文件名_BL.bin)")
|
||
parser.add_argument("--app-addr", default="0x08002000", help="APP 起始地址 (默认: 0x08002000)")
|
||
parser.add_argument("--flag-addr", default="0x0800FFFC", help="标志位地址 (默认: 0x0800FFFC)")
|
||
args = parser.parse_args()
|
||
|
||
if args.output is None:
|
||
base, ext = os.path.splitext(os.path.basename(args.app))
|
||
args.output = f"{base}_BL{ext}"
|
||
|
||
app_addr = int(args.app_addr, 0)
|
||
flag_addr = int(args.flag_addr, 0)
|
||
|
||
if not os.path.isfile(args.bootloader):
|
||
print(f"错误:找不到文件 {args.bootloader}", file=sys.stderr)
|
||
sys.exit(1)
|
||
if not os.path.isfile(args.app):
|
||
print(f"错误:找不到文件 {args.app}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
if not merge(args.bootloader, args.app, args.output, app_addr, flag_addr):
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|