移除 OpenCode 配置与命令,技能与串口单一配置源迁移至 .dsh/skills 与 .serialtest,同步更新全部文档引用
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""stlink_flash.py - ST-LINK 命令行烧录/复位/回读校验(纯标准库,可移植 skill 版)。
|
||||
|
||||
用法(**项目根目录**下运行,任意 python3):
|
||||
python <skill>/scripts/stlink_flash.py list
|
||||
python <skill>/scripts/stlink_flash.py flash [镜像.hex|.bin] # 烧录 + -Rst -Run
|
||||
python <skill>/scripts/stlink_flash.py cmp [镜像.hex|.bin] [--base 0x08000000]
|
||||
python <skill>/scripts/stlink_flash.py reset # 只复位运行
|
||||
python <skill>/scripts/stlink_flash.py halt | run
|
||||
|
||||
镜像缺省取项目档案 "hex"(解析顺序同下)。
|
||||
ST-LINK_CLI 解析(命令行 > 档案 > 环境 > PATH):
|
||||
--cli > 档案 "stlink_cli" > $DSH_STLINK_CLI > $DTU_STLINK_CLI > PATH
|
||||
(环境变量**显式设了但路径无效 = 立即报错,不静默回退**)
|
||||
|
||||
铁律(实测教训,脚本已内置):
|
||||
- 烧录必带 -Rst -Run:裸 -P 在目标核死循环时报 Complete 但内容不落地
|
||||
- flash 成功判据 = rc==0 且输出含 "Programming Complete"
|
||||
- cmp 成功判据 = 某行以 "No difference found" 开头(原样透传 CLI 输出,
|
||||
上层 run_boot_test 的 readback_ok 默认值直接可用)
|
||||
- cmp 连接时会**复位目标**(板子重打开机日志)→ 自动闭环里必须排在日志采集之后
|
||||
|
||||
退出码: 0=OK 1=探针/目标错误 2=CLI 未找到/无效 3=用法错误
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_config_file():
|
||||
env = os.environ.get("DSH_SERIAL_CONFIG")
|
||||
if env:
|
||||
return Path(env)
|
||||
cwd = Path.cwd()
|
||||
candidates = [cwd / ".serialtest" / "config.json",
|
||||
cwd / ".opencode" / "serial_config.json"]
|
||||
for c in candidates:
|
||||
if c.is_file():
|
||||
return c
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def load_config():
|
||||
f = find_config_file()
|
||||
if not f.is_file():
|
||||
return {}, ""
|
||||
try:
|
||||
raw = json.loads(f.read_text(encoding="utf-8-sig"))
|
||||
except (OSError, ValueError) as e:
|
||||
return {}, "cannot parse {}: {}".format(f, e)
|
||||
if not isinstance(raw, dict):
|
||||
return {}, "config root is not an object: {}".format(f)
|
||||
return raw, ""
|
||||
|
||||
|
||||
def find_cli(cli_arg, cfg):
|
||||
for label, val in (("--cli", cli_arg),
|
||||
("config 'stlink_cli'", cfg.get("stlink_cli")),
|
||||
("$DSH_STLINK_CLI", os.environ.get("DSH_STLINK_CLI")),
|
||||
("$DTU_STLINK_CLI", os.environ.get("DTU_STLINK_CLI"))):
|
||||
if val:
|
||||
if Path(val).is_file():
|
||||
return Path(val), label
|
||||
raise SystemExit("ENV_ERROR: {} is set but not a file: {} "
|
||||
"(fix the value; no silent fallback)".format(label, val))
|
||||
w = shutil.which("ST-LINK_CLI.exe") or shutil.which("ST-LINK_CLI")
|
||||
if w:
|
||||
return Path(w), "PATH"
|
||||
raise SystemExit("ENV_ERROR: ST-LINK_CLI.exe not found "
|
||||
"(--cli / config 'stlink_cli' / $DSH_STLINK_CLI / $DTU_STLINK_CLI / PATH)")
|
||||
|
||||
|
||||
def run_cli(cli, args, timeout):
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
proc = subprocess.run([str(cli)] + args, capture_output=True, timeout=timeout)
|
||||
rc = proc.returncode
|
||||
raw = (proc.stdout or b"") + (proc.stderr or b"")
|
||||
except subprocess.TimeoutExpired as e:
|
||||
rc = -1
|
||||
raw = (e.stdout or b"") + (e.stderr or b"")
|
||||
text = raw.decode("utf-8", errors="replace")
|
||||
for line in text.strip().splitlines():
|
||||
print(line)
|
||||
print("RC: {} elapsed_ms={}".format(rc, int((time.monotonic() - t0) * 1000)))
|
||||
return rc, text
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ap = argparse.ArgumentParser(description="ST-LINK CLI flash/reset/readback (pure stdlib)")
|
||||
ap.add_argument("command", choices=["list", "flash", "cmp", "reset", "halt", "run"])
|
||||
ap.add_argument("image", nargs="?", default=None,
|
||||
help="image file (.hex/.bin; default: config 'hex')")
|
||||
ap.add_argument("--cli", default=None, help="ST-LINK_CLI.exe path")
|
||||
ap.add_argument("--base", default="0x08000000", help="cmp base address")
|
||||
ap.add_argument("--timeout", type=int, default=None,
|
||||
help="subprocess timeout (default: flash 300 / cmp 180 / other 60)")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
cfg, err = load_config()
|
||||
if err:
|
||||
print("ENV_ERROR: {}".format(err))
|
||||
return 3
|
||||
|
||||
cli, src = find_cli(args.cli, cfg)
|
||||
print("CLI: {} ({})".format(cli, src))
|
||||
|
||||
if args.command in ("list", "reset", "halt", "run"):
|
||||
if args.image:
|
||||
print("NOTE: image argument ignored for '{}'".format(args.command))
|
||||
timeout = args.timeout or 60
|
||||
if args.command == "list":
|
||||
rc, _ = run_cli(cli, ["-List"], timeout)
|
||||
print("STLINK_RC={}".format(rc))
|
||||
return 0 if rc == 0 else 1
|
||||
if args.command == "reset":
|
||||
rc, _ = run_cli(cli, ["-c", "SWD", "-Rst", "-Run", "-Q"], timeout)
|
||||
print("STLINK_RC={}".format(rc))
|
||||
return 0 if rc == 0 else 1
|
||||
if args.command == "halt":
|
||||
rc, _ = run_cli(cli, ["-c", "SWD", "-Rst", "-Halt"], timeout)
|
||||
print("STLINK_RC={}".format(rc))
|
||||
return 0 if rc == 0 else 1
|
||||
rc, _ = run_cli(cli, ["-c", "SWD", "-Run"], timeout)
|
||||
print("STLINK_RC={}".format(rc))
|
||||
return 0 if rc == 0 else 1
|
||||
|
||||
# flash / cmp 需要镜像
|
||||
img = args.image or cfg.get("hex") or ""
|
||||
if not img:
|
||||
print("STLINK_ERROR: no image given and no config 'hex'")
|
||||
return 3
|
||||
img = Path(img)
|
||||
if not img.is_file():
|
||||
print("STLINK_ERROR: image not found: {}".format(img))
|
||||
return 3
|
||||
|
||||
if args.command == "flash":
|
||||
print("FLASH: {} then reset and run".format(img))
|
||||
rc, text = run_cli(cli, ["-c", "SWD", "-P", str(img), "-Rst", "-Run", "-Q"],
|
||||
args.timeout or 300)
|
||||
if rc == 0 and "programming complete" in text.lower():
|
||||
print("FLASH: OK")
|
||||
print("STLINK_RC=0")
|
||||
return 0
|
||||
if rc == 0:
|
||||
print("STLINK_ERROR: rc=0 but no 'Programming Complete' in output "
|
||||
"(treat as failure - possible forwarded/hung run)")
|
||||
print("STLINK_RC=1")
|
||||
return 1
|
||||
print("STLINK_RC=1")
|
||||
return 1
|
||||
|
||||
# cmp:原样透传输出("No difference found" 独占行首 = 成功判据)
|
||||
print("CMP: {} vs device flash @ {} (NOTE: connecting resets the target)".format(
|
||||
img, args.base))
|
||||
rc, text = run_cli(cli, ["-c", "SWD", "-CmpFile", str(img), args.base],
|
||||
args.timeout or 180)
|
||||
ok = any(ln.strip().lower().startswith("no difference found")
|
||||
for ln in text.splitlines())
|
||||
if ok:
|
||||
print("CMP: OK (No difference found)")
|
||||
print("STLINK_RC=0")
|
||||
return 0
|
||||
print("STLINK_ERROR: readback mismatch or compare failed")
|
||||
print("STLINK_RC=1")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user