feat(flash-gui): cfgs 自定义 cfg 机制 + 全操作 SRST 连接序列 + 整包 0.11 工具链

This commit is contained in:
2026-09-16 19:49:09 +08:00
parent 9a5d00ecf5
commit d776979a5a
7 changed files with 307 additions and 57 deletions
+90 -16
View File
@@ -43,15 +43,54 @@ def _app_base():
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def _find_scripts(root):
"""在工具链根目录下找 scripts 目录, 兼容两种布局:
xpack 0.11(脚本在根: scripts/) 与 0.12(脚本在 openocd/scripts/)"""
for sub in ("openocd/scripts", "scripts"):
p = os.path.join(root, sub)
if os.path.isdir(p):
return p
return None
def bundled_toolchain():
"""内置工具链路径, 不存在返回 None"""
exe = os.path.join(_app_base(), "toolchain", "openocd", "bin", "openocd.exe")
root = os.path.join(_app_base(), "toolchain", "openocd")
exe = os.path.join(root, "bin", "openocd.exe")
if os.path.isfile(exe):
scripts = os.path.join(_app_base(), "toolchain", "openocd", "openocd", "scripts")
return exe, scripts if os.path.isdir(scripts) else DEFAULT_SCRIPTS
scripts = _find_scripts(root)
return exe, (scripts or DEFAULT_SCRIPTS)
return None
def custom_cfg_dir():
"""用户自定义 cfg 目录: 程序目录旁 cfgs/(exe 模式在 exe 旁, 源码模式在 FlashTool/)"""
return os.path.join(_app_base(), "cfgs")
def list_custom_cfgs():
"""cfgs/ 下的 *.cfg 文件名列表(供界面下拉选择), 目录不存在返回空表"""
d = custom_cfg_dir()
if not os.path.isdir(d):
return []
return sorted(f for f in os.listdir(d)
if f.lower().endswith(".cfg") and os.path.isfile(os.path.join(d, f)))
def scan_cfgs(scripts_dir, kind):
"""扫描 scripts 目录下 kind(target/interface) 子目录里的 cfg 文件名。
返回 ['target/gd32e23x.cfg', ...]; scripts 无效/子目录不存在返回空表。
"""
if not scripts_dir or not os.path.isdir(scripts_dir):
return []
d = os.path.join(scripts_dir, kind)
if not os.path.isdir(d):
return []
return sorted(kind + "/" + f for f in os.listdir(d)
if f.lower().endswith(".cfg") and os.path.isfile(os.path.join(d, f)))
def locate_toolchain():
"""返回 (openocd_exe, scripts_dir, source),source ∈ 内置/本机/PATH"""
bundled = bundled_toolchain()
@@ -83,6 +122,20 @@ def ensure_cfg_path(value, folder):
return folder + "/" + value
def resolve_cfg(value, folder):
"""界面里的 cfg 引用 → OpenOCD -f 参数。
cfgs/<name>.cfg 是自定义 cfg, 转成绝对路径(不依赖 cwd);
其余(裸名/带路径/绝对路径)按原逻辑处理。
"""
value = value.strip().replace("\\", "/")
if value.startswith("cfgs/"):
p = os.path.join(custom_cfg_dir(), os.path.basename(value))
if os.path.isfile(p):
return p
return ensure_cfg_path(value, folder)
def version(exe):
"""openocd --version 首行(如 Open On-Chip Debugger 0.12.0), 失败返回 None"""
try:
@@ -99,12 +152,14 @@ def version(exe):
# 烧录脚本构建(GUI 与 CLI 共用)
# ============================================================
def build_cmd(openocd, scripts, interface, target, speed="", serial="", extra="", script=""):
"""拼 OpenOCD 命令行: -s 搜索目录 + interface/target cfg + -c 选项"""
"""拼 OpenOCD 命令行: -s 搜索目录 + interface/target cfg + -c 选项。
interface 留空时跳过(配合自带 adapter driver 的自定义 target cfg 使用)"""
cmd = [openocd.strip()]
if scripts and scripts.strip():
cmd += ["-s", scripts.strip()]
cmd += ["-f", ensure_cfg_path(interface, "interface"),
"-f", ensure_cfg_path(target, "target")]
if interface and interface.strip():
cmd += ["-f", resolve_cfg(interface, "interface")]
cmd += ["-f", resolve_cfg(target, "target")]
if speed and speed.strip():
cmd += ["-c", "adapter speed " + speed.strip()]
if serial and serial.strip() and "cmsis-dap" in interface.lower():
@@ -117,21 +172,38 @@ def build_cmd(openocd, scripts, interface, target, speed="", serial="", extra=""
def script_detect():
return "init; reset halt; flash list; exit"
return "init; %s; flash list; exit" % CONNECT_SEQ
def script_erase(flash_size_text):
size = parse_int(flash_size_text)
if size is None or size <= 0:
raise ValueError("Flash 大小无效: %s" % flash_size_text)
return ("init; reset halt; flash erase_address 0x%08X 0x%X; reset halt; exit"
% (FLASH_BASE_DEFAULT, size))
return ("init; %s; flash erase_address 0x%08X 0x%X; reset halt; exit"
% (CONNECT_SEQ, FLASH_BASE_DEFAULT, size))
def script_flash(firmware, base_text="", erase=True, verify=True, reset=True):
"""HEX/ELF 用文件内地址(base_text 留空), BIN 必须给基地址"""
# 复刻 VSCode tasks.json 的复位序列(0.11 语法, 与内置 xpack 0.11.0-3 匹配):
# init 连接(connect_assert_srst 由 cfg 声明)后显式释放 SRST 并等复位完成再 halt。
# 注意: 0.11 只认 "adapter deassert srst"; 0.12 的 "adapter srst deassert"
# 在 0.11 上是无效命令, 包 catch 会静默吞掉 → SRST 一直按住 → cannot read IDR。
# 全部操作(检测/烧录/擦除/读取/复位)都必须带本序列: 本目标必须按住复位连接,
# 否则 exam 失败(PARTNO 0x0), 后续任何访问都会 "Target not examined" 失败。
CONNECT_SEQ = "adapter deassert srst; sleep 200; catch {reset halt}; reset halt"
POST_RESET_PREFIX = "adapter deassert srst; sleep 200; "
def script_flash(firmware, base_text="", erase=True, verify=True, reset=True,
pre_reset=True, post_reset=True):
"""HEX/ELF 用文件内地址(base_text 留空), BIN 必须给基地址。
pre_reset - 烧录前硬件复位序列(SRST 释放 + halt), 复刻 VSCode 任务链
post_reset - 烧录后复位前先释放 SRST(硬件复位), 否则仅软件 reset
reset - 复位后运行(reset run); 不勾则复位后保持暂停(reset halt)
"""
ext = os.path.splitext(firmware)[1].lower()
script = "init; reset halt; "
script = "init; "
script += (CONNECT_SEQ if pre_reset else "reset halt") + "; "
addr = ""
if ext == ".bin":
base = parse_int(base_text)
@@ -143,7 +215,8 @@ def script_flash(firmware, base_text="", erase=True, verify=True, reset=True):
else "flash write_image %s%s; ") % (quoted, addr)
if verify:
script += "verify_image %s%s; " % (quoted, addr)
script += ("reset run" if reset else "reset halt") + "; exit"
tail = "reset run" if reset else "reset halt"
script += (POST_RESET_PREFIX + tail if post_reset else tail) + "; exit"
return script
@@ -151,12 +224,13 @@ def script_read(out_path, flash_size_text):
size = parse_int(flash_size_text)
if size is None or size <= 0:
raise ValueError("Flash 大小无效: %s" % flash_size_text)
return "init; reset halt; dump_image %s 0x%08X 0x%X; exit" % (
tcl_quote(out_path), FLASH_BASE_DEFAULT, size)
return "init; %s; dump_image %s 0x%08X 0x%X; exit" % (
CONNECT_SEQ, tcl_quote(out_path), FLASH_BASE_DEFAULT, size)
def script_reset():
return "init; reset run; exit"
"""对齐 VSCode Reset 任务: 释放 SRST 后复位运行"""
return "init; adapter deassert srst; sleep 200; reset; exit"
# ============================================================