refactor(flash-gui): 对齐 DLPilot 重构 GUI 并内置 OpenOCD 发布链
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
DAPLink Flash — GD32 固件烧录 · 补齐 · 镜像拼接 — 入口与命令行模式
|
||||
================================================================
|
||||
配合 GD32E230 + DAPLink/CMSIS-DAP(或 ST-Link/J-Link) 使用, OpenOCD 烧录;
|
||||
内含固件补齐(pad)与镜像拼接(merge)工具。发布包内置 OpenOCD 工具链。
|
||||
|
||||
模块划分(重构自原单文件 daplink_flash_gui.py, 界面/打包方式对齐 DLPilot):
|
||||
app_gui.py CustomTkinter 主界面(双栏仪表盘)
|
||||
openocd_runner.py 工具链定位 / OpenOCD 命令构建与子进程
|
||||
fw_image.py 补齐(pad)与拼接(merge) + 芯片参数表
|
||||
config.py 运行目录与 flash_gui_settings.json 配置
|
||||
|
||||
依赖: 全部纯标准库 + customtkinter(py -m pip install customtkinter);
|
||||
内置工具链不存在时需本机已装 OpenOCD 或在界面/参数里指定路径。
|
||||
|
||||
命令行模式(自动化脚本用):
|
||||
python flash_gui.py 启动 GUI
|
||||
python flash_gui.py --seconds 5 GUI 5 秒自动关闭(冒烟测试)
|
||||
python flash_gui.py --detect 检测烧录器与目标芯片
|
||||
python flash_gui.py --flash app.bin 烧录(BIN 默认按基地址 0x08000000)
|
||||
python flash_gui.py --flash app.bin --base 0x08002000 --no-reset
|
||||
python flash_gui.py --erase 整片擦除(按配置的 Flash 大小)
|
||||
python flash_gui.py --read dump.bin 读取整片 Flash
|
||||
python flash_gui.py --reset 复位运行
|
||||
python flash_gui.py --pad app.bin 补齐到 1K 边界(app_pad.bin)
|
||||
python flash_gui.py --merge bl.bin app.bin 拼接整片镜像(*_BL.bin)
|
||||
python flash_gui.py --version 打印 OpenOCD 版本
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import config
|
||||
import openocd_runner
|
||||
from fw_image import (APP_ADDR_DEFAULT, FLASH_BASE_DEFAULT, FLAG_ADDR_DEFAULT,
|
||||
FLAG_VALUE_DEFAULT, merge_firmware, pad_bin, parse_int)
|
||||
|
||||
|
||||
def _toolchain(args):
|
||||
"""命令行工具链解析: 参数 > 配置文件 > 自动定位(内置/本机/PATH)"""
|
||||
saved = config.load_settings()
|
||||
exe = args.openocd or (saved['openocd'] if os.path.isfile(saved['openocd']) else '')
|
||||
scripts = args.scripts or (saved['scripts'] if exe == saved['openocd'] else '')
|
||||
if not exe:
|
||||
exe, scripts2, source = openocd_runner.locate_toolchain()
|
||||
scripts = scripts or scripts2
|
||||
print(f'工具链: {exe} ({source})')
|
||||
if not os.path.isfile(exe):
|
||||
sys.exit(f'找不到 OpenOCD: {exe}\n'
|
||||
f'可用 --openocd 指定, 或把内置工具链放在 exe 旁 toolchain/openocd/')
|
||||
if not scripts or not os.path.isdir(scripts):
|
||||
sys.exit(f'找不到 OpenOCD scripts 目录: {scripts or "(空)"}\n可用 --scripts 指定')
|
||||
return exe, scripts
|
||||
|
||||
|
||||
def _ocd_cmd(args, script):
|
||||
saved = config.load_settings()
|
||||
exe, scripts = _toolchain(args)
|
||||
return openocd_runner.build_cmd(
|
||||
exe, scripts, args.interface or saved['interface'],
|
||||
args.target or saved['target'],
|
||||
str(args.speed) if args.speed else saved['speed'],
|
||||
args.serial or saved['serial'], saved['extra'], script)
|
||||
|
||||
|
||||
def run_openocd(args, title, script):
|
||||
"""跑一条烧录命令, 实时回显输出, 返回退出码"""
|
||||
cmd = _ocd_cmd(args, script)
|
||||
print('== %s ==' % title)
|
||||
print('命令: ' + subprocess.list2cmdline(cmd))
|
||||
code, _lines = openocd_runner.run_sync(cmd)
|
||||
return code
|
||||
|
||||
|
||||
def run_pad(args):
|
||||
if not os.path.isfile(args.pad):
|
||||
sys.exit(f'找不到输入文件: {args.pad}')
|
||||
align = parse_int(str(args.align))
|
||||
if align is None or align <= 0:
|
||||
sys.exit(f'对齐字节无效: {args.align}')
|
||||
pad_byte = parse_int(args.pad_byte)
|
||||
if pad_byte is None:
|
||||
pad_byte = 0xFF
|
||||
stem, ext = os.path.splitext(args.pad)
|
||||
out = args.output or stem + '_pad' + ext
|
||||
_ensure_parent(out)
|
||||
original = os.path.getsize(args.pad)
|
||||
padded = pad_bin(args.pad, out, align, pad_byte)
|
||||
print(f'补齐完成: {out}')
|
||||
print(f' 原始 {original} B → 补齐后 {padded} B (+{padded - original}), 对齐 {align} B')
|
||||
return 0
|
||||
|
||||
|
||||
def run_merge(args):
|
||||
bootloader, app = args.merge
|
||||
for p in (bootloader, app):
|
||||
if not os.path.isfile(p):
|
||||
sys.exit(f'找不到文件: {p}')
|
||||
flash_size = parse_int(str(args.flash_size))
|
||||
app_addr = parse_int(args.app_addr)
|
||||
flag_addr = parse_int(args.flag_addr)
|
||||
flag_value = parse_int(args.flag_value)
|
||||
if flag_value is None:
|
||||
flag_value = FLAG_VALUE_DEFAULT
|
||||
out = args.output or os.path.splitext(app)[0] + '_BL.bin'
|
||||
_ensure_parent(out)
|
||||
try:
|
||||
info = merge_firmware(bootloader, app, out,
|
||||
app_addr, flag_addr, flash_size, flag_value)
|
||||
except ValueError as exc:
|
||||
sys.exit(f'拼接失败: {exc}')
|
||||
print(f'拼接完成: {out}')
|
||||
print(f' Bootloader {info["bootloader_size"]} B @0x{FLASH_BASE_DEFAULT:08X}')
|
||||
print(f' Application {info["app_size"]} B @0x{info["app_addr"]:08X}')
|
||||
print(f' Flag @0x{info["flag_addr"]:08X} 镜像 {info["output_size"]} B')
|
||||
return 0
|
||||
|
||||
|
||||
def _ensure_parent(path):
|
||||
parent = os.path.dirname(os.path.abspath(path))
|
||||
if parent:
|
||||
os.makedirs(parent, exist_ok=True)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description='DAPLink Flash — GD32 固件烧录 · 补齐 · 镜像拼接 '
|
||||
'(无参数则启动 GUI)')
|
||||
ap.add_argument('--seconds', type=int, default=0,
|
||||
help='GUI 自动退出秒数(冒烟测试)')
|
||||
# -- 烧录操作(互斥, 按下述顺序判优) --
|
||||
op = ap.add_mutually_exclusive_group()
|
||||
op.add_argument('--detect', action='store_true', help='检测烧录器与目标芯片')
|
||||
op.add_argument('--flash', metavar='FILE', help='烧录固件 (BIN/HEX/ELF)')
|
||||
op.add_argument('--erase', action='store_true', help='整片擦除')
|
||||
op.add_argument('--read', metavar='OUT', help='读取整片 Flash 到 OUT')
|
||||
op.add_argument('--reset', action='store_true', help='复位芯片并运行')
|
||||
op.add_argument('--version', action='store_true', help='打印 OpenOCD 版本')
|
||||
# -- 固件工具 --
|
||||
op.add_argument('--pad', metavar='FILE', dest='pad', help='补齐 BIN 到页边界')
|
||||
op.add_argument('--merge', nargs=2, metavar=('BL', 'APP'),
|
||||
help='拼接 Bootloader + APP → 整片镜像')
|
||||
ap.add_argument('--align', default='1024', help='--pad 对齐字节(默认 1024)')
|
||||
ap.add_argument('--pad-byte', default='0xFF', help='--pad 填充字节(默认 0xFF)')
|
||||
ap.add_argument('-o', '--output', help='--pad/--merge 输出路径(默认自动命名)')
|
||||
ap.add_argument('--app-addr', default='0x%08X' % APP_ADDR_DEFAULT,
|
||||
help='--merge APP 地址(默认 0x08002000)')
|
||||
ap.add_argument('--flag-addr', default='0x%08X' % FLAG_ADDR_DEFAULT,
|
||||
help='--merge 标志地址(默认 0x0800FFFC)')
|
||||
ap.add_argument('--flag-value', default='0x%08X' % FLAG_VALUE_DEFAULT,
|
||||
help='--merge 标志值(默认 0xEEEEEEEE)')
|
||||
ap.add_argument('--flash-size', default='0x10000',
|
||||
help='Flash 大小(默认 0x10000, --erase/--read/--merge 用)')
|
||||
# -- 烧录参数 --
|
||||
ap.add_argument('--base', default='0x%08X' % FLASH_BASE_DEFAULT,
|
||||
help='BIN 烧录基地址(默认 0x08000000)')
|
||||
ap.add_argument('--no-erase', action='store_true', help='烧录前不擦除')
|
||||
ap.add_argument('--no-verify', action='store_true', help='烧录后不校验')
|
||||
ap.add_argument('--no-reset', action='store_true', help='烧录后不复位运行')
|
||||
ap.add_argument('--speed', type=int, help='适配器速度 kHz(默认取配置 4000)')
|
||||
ap.add_argument('--serial', help='探针序列号(多探针时用)')
|
||||
ap.add_argument('--interface', help='interface cfg(默认 interface/cmsis-dap.cfg)')
|
||||
ap.add_argument('--target', help='target cfg(默认 target/gd32e23x.cfg)')
|
||||
ap.add_argument('--openocd', help='OpenOCD 可执行文件路径(默认自动定位)')
|
||||
ap.add_argument('--scripts', help='OpenOCD scripts 目录(默认自动定位)')
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.pad:
|
||||
sys.exit(run_pad(args))
|
||||
if args.merge:
|
||||
sys.exit(run_merge(args))
|
||||
if args.version:
|
||||
exe, _scripts = _toolchain(args)
|
||||
print(openocd_runner.version(exe) or '无法获取版本')
|
||||
sys.exit(0)
|
||||
if args.detect:
|
||||
sys.exit(run_openocd(args, '检测设备', openocd_runner.script_detect()))
|
||||
if args.erase:
|
||||
sys.exit(run_openocd(args, '整片擦除',
|
||||
openocd_runner.script_erase(args.flash_size)))
|
||||
if args.flash:
|
||||
sys.exit(run_openocd(args, '烧录固件', openocd_runner.script_flash(
|
||||
args.flash, args.base,
|
||||
erase=not args.no_erase, verify=not args.no_verify,
|
||||
reset=not args.no_reset)))
|
||||
if args.read:
|
||||
sys.exit(run_openocd(args, '读取 Flash',
|
||||
openocd_runner.script_read(args.read, args.flash_size)))
|
||||
if args.reset:
|
||||
sys.exit(run_openocd(args, '复位芯片', openocd_runner.script_reset()))
|
||||
|
||||
# 无操作参数 → 启动 GUI(实现在 app_gui.py)
|
||||
import app_gui
|
||||
app_gui.run_gui(args.seconds)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user