Files
STM32F4-Base/.dsh/skills/stlink-flash/scripts/stlink_flash.py
T

185 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())