tools: add MCU + ExtFlash ALL.res flash helper
Script flashes APP via J-Link then streams ALL.res over RTT flash all; include a minimal loadbin command file for APP-only updates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
cae4eb84a1
commit
8ac13ce6e9
|
|
@ -0,0 +1,10 @@
|
||||||
|
HideDeviceSelection 1
|
||||||
|
si SWD
|
||||||
|
speed 4000
|
||||||
|
device AT32F403AC
|
||||||
|
connect
|
||||||
|
halt
|
||||||
|
loadbin project/IAR_V7.4/YNGJ-GT1-M/Exe/YNGJ-GT1-M.bin, 0x08008000
|
||||||
|
r
|
||||||
|
g
|
||||||
|
exit
|
||||||
|
|
@ -0,0 +1,289 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Flash K1 MCU APP (J-Link) + ExtFlash ALL.res (RTT flash all).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python tools/flash_mcu_and_extflash.py
|
||||||
|
python tools/flash_mcu_and_extflash.py --mcu PATH.bin --all PATH.res
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PROJ = Path(__file__).resolve().parents[1]
|
||||||
|
REPO = PROJ.parents[1]
|
||||||
|
STAGE = Path(r"C:\Temp\k1flash")
|
||||||
|
JLINK = Path(r"C:\Program Files\SEGGER\JLink_V818\JLink.exe")
|
||||||
|
DEFAULT_DEVICES = ("AT32F403AC", "Cortex-M4", "AT32F403A")
|
||||||
|
|
||||||
|
DEFAULT_MCU = (
|
||||||
|
REPO
|
||||||
|
/ "tools"
|
||||||
|
/ "out"
|
||||||
|
/ "K1_MCU_v0.2.11_toneRes_v0914_20260914_d06e44b"
|
||||||
|
/ "YNGJ-GT1-M_MCU_v0.2.11_20260914.bin"
|
||||||
|
)
|
||||||
|
FALLBACK_MCU = PROJ / "project" / "IAR_V7.4" / "YNGJ-GT1-M" / "Exe" / "YNGJ-GT1-M.bin"
|
||||||
|
|
||||||
|
DEFAULT_ALL = REPO / "tools" / "out" / "extflash_ALL_tone0914_ui0902.res"
|
||||||
|
FALLBACK_ALL = (
|
||||||
|
REPO
|
||||||
|
/ "tools"
|
||||||
|
/ "out"
|
||||||
|
/ "K1_MCU_v0.2.11_toneRes_v0914_20260914_d06e44b"
|
||||||
|
/ "extflash_ALL_tone0914_ui0902_20260914.res"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def kill_debuggers() -> None:
|
||||||
|
for n in (
|
||||||
|
"cspybat.exe",
|
||||||
|
"CSpyBat.exe",
|
||||||
|
"JLink.exe",
|
||||||
|
"JLinkGUIServer.exe",
|
||||||
|
"JLinkRTTClient.exe",
|
||||||
|
"JFlash.exe",
|
||||||
|
):
|
||||||
|
subprocess.run(["taskkill", "/F", "/IM", n], capture_output=True)
|
||||||
|
time.sleep(0.8)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_path(primary: Path, fallback: Path) -> Path:
|
||||||
|
if primary.is_file():
|
||||||
|
return primary
|
||||||
|
if fallback.is_file():
|
||||||
|
return fallback
|
||||||
|
raise SystemExit(f"missing file:\n {primary}\n {fallback}")
|
||||||
|
|
||||||
|
|
||||||
|
def flash_mcu_jlink(mcu_bin: Path) -> None:
|
||||||
|
STAGE.mkdir(parents=True, exist_ok=True)
|
||||||
|
staged = STAGE / "YNGJ-GT1-M.bin"
|
||||||
|
shutil.copy2(mcu_bin, staged)
|
||||||
|
cmdfile = STAGE / "flash_app.jlink"
|
||||||
|
cmdfile.write_text(
|
||||||
|
"\n".join(
|
||||||
|
[
|
||||||
|
"HideDeviceSelection 1",
|
||||||
|
"si SWD",
|
||||||
|
"speed 4000",
|
||||||
|
"device AT32F403AC",
|
||||||
|
"connect",
|
||||||
|
"halt",
|
||||||
|
f"loadbin {staged}, 0x08008000",
|
||||||
|
"r",
|
||||||
|
"g",
|
||||||
|
"exit",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
encoding="ascii",
|
||||||
|
)
|
||||||
|
if not JLINK.is_file():
|
||||||
|
raise SystemExit(f"missing JLink.exe: {JLINK}")
|
||||||
|
print(f"[1/3] J-Link flash MCU → 0x08008000 ({staged.name}, {staged.stat().st_size} bytes)")
|
||||||
|
r = subprocess.run(
|
||||||
|
[
|
||||||
|
str(JLINK),
|
||||||
|
"-Device",
|
||||||
|
"AT32F403AC",
|
||||||
|
"-If",
|
||||||
|
"SWD",
|
||||||
|
"-Speed",
|
||||||
|
"4000",
|
||||||
|
"-AutoConnect",
|
||||||
|
"1",
|
||||||
|
"-CommandFile",
|
||||||
|
str(cmdfile),
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
out = (r.stdout or "") + (r.stderr or "")
|
||||||
|
print(out[-1200:] if out else f"rc={r.returncode}")
|
||||||
|
if r.returncode != 0:
|
||||||
|
raise SystemExit(f"J-Link MCU flash failed rc={r.returncode}")
|
||||||
|
if "Failed" in out and "O.K." not in out:
|
||||||
|
raise SystemExit("J-Link MCU flash reported failure")
|
||||||
|
time.sleep(2.5)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_jlink(device: str):
|
||||||
|
import pylink
|
||||||
|
|
||||||
|
jlink = pylink.JLink()
|
||||||
|
jlink.open()
|
||||||
|
try:
|
||||||
|
jlink.exec_command("HideDeviceSelection = 1")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
|
||||||
|
last = None
|
||||||
|
for candidate in (device, *DEFAULT_DEVICES):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
jlink.exec_command(f"Device = {candidate}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
jlink.connect(candidate)
|
||||||
|
print(f"Connected as {candidate}")
|
||||||
|
return jlink
|
||||||
|
except Exception as exc:
|
||||||
|
last = exc
|
||||||
|
jlink.close()
|
||||||
|
raise SystemExit(f"J-Link connect failed: {last}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_rtt_cb(jlink, ram_base: int = 0x20000000, ram_size: int = 0x18000):
|
||||||
|
needle = b"SEGGER RTT"
|
||||||
|
chunk = 0x1000
|
||||||
|
was_halted = jlink.halted()
|
||||||
|
if not was_halted:
|
||||||
|
jlink.halt()
|
||||||
|
try:
|
||||||
|
for off in range(0, ram_size, chunk):
|
||||||
|
data = bytes(jlink.memory_read8(ram_base + off, min(chunk, ram_size - off)))
|
||||||
|
idx = data.find(needle)
|
||||||
|
if idx >= 0:
|
||||||
|
return ram_base + off + idx
|
||||||
|
finally:
|
||||||
|
if not was_halted:
|
||||||
|
if hasattr(jlink, "restart"):
|
||||||
|
jlink.restart()
|
||||||
|
else:
|
||||||
|
jlink.go()
|
||||||
|
time.sleep(0.1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def rtt_read_text(jlink, timeout_s: float = 0.2) -> str:
|
||||||
|
deadline = time.time() + timeout_s
|
||||||
|
chunks: list[bytes] = []
|
||||||
|
while time.time() < deadline:
|
||||||
|
try:
|
||||||
|
data = bytes(jlink.rtt_read(0, 512) or [])
|
||||||
|
except Exception:
|
||||||
|
data = b""
|
||||||
|
if data:
|
||||||
|
chunks.append(data)
|
||||||
|
deadline = time.time() + timeout_s
|
||||||
|
else:
|
||||||
|
time.sleep(0.02)
|
||||||
|
return b"".join(chunks).decode("ascii", errors="replace")
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for(jlink, marker: str, timeout_s: float) -> str:
|
||||||
|
deadline = time.time() + timeout_s
|
||||||
|
buf = ""
|
||||||
|
while time.time() < deadline:
|
||||||
|
buf += rtt_read_text(jlink, 0.15)
|
||||||
|
if marker in buf:
|
||||||
|
return buf
|
||||||
|
raise SystemExit(f"Timeout waiting for {marker!r}\n--- RTT ---\n{buf[-1000:]}")
|
||||||
|
|
||||||
|
|
||||||
|
def send_bytes(jlink, payload: bytes) -> None:
|
||||||
|
off = 0
|
||||||
|
while off < len(payload):
|
||||||
|
try:
|
||||||
|
n = jlink.rtt_write(0, list(payload[off : off + 64]))
|
||||||
|
except Exception:
|
||||||
|
time.sleep(0.05)
|
||||||
|
continue
|
||||||
|
if n is None or n <= 0:
|
||||||
|
time.sleep(0.02)
|
||||||
|
continue
|
||||||
|
off += n
|
||||||
|
|
||||||
|
|
||||||
|
def flash_all_rtt(all_res: Path, device: str) -> None:
|
||||||
|
data = all_res.read_bytes()
|
||||||
|
size = len(data)
|
||||||
|
print(f"[2/3] RTT flash ALL.res @ 0x0 ({all_res.name}, {size} bytes)")
|
||||||
|
|
||||||
|
jlink = connect_jlink(device)
|
||||||
|
try:
|
||||||
|
print("hardware reset...")
|
||||||
|
jlink.reset(halt=False)
|
||||||
|
time.sleep(4.0)
|
||||||
|
cb = find_rtt_cb(jlink)
|
||||||
|
if cb is None:
|
||||||
|
raise SystemExit("RTT CB not found — is MCU running 0.2.11+ with APP_LOG?")
|
||||||
|
print(f"RTT CB @ 0x{cb:08X}")
|
||||||
|
jlink.rtt_start(cb)
|
||||||
|
time.sleep(0.5)
|
||||||
|
_ = rtt_read_text(jlink, 0.5)
|
||||||
|
|
||||||
|
send_bytes(jlink, f"flash all {size}\n".encode("ascii"))
|
||||||
|
log = wait_for(jlink, "FLASH_ALL_GO", 20.0)
|
||||||
|
print(log.strip().splitlines()[-1])
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
chunk = 256
|
||||||
|
sent = 0
|
||||||
|
t0 = time.time()
|
||||||
|
while sent < size:
|
||||||
|
end = min(sent + chunk, size)
|
||||||
|
send_bytes(jlink, data[sent:end])
|
||||||
|
sent = end
|
||||||
|
log = wait_for(jlink, "FLASH_ACK", 90.0)
|
||||||
|
if "FLASH_ALL_OK" in log:
|
||||||
|
print(log.strip().splitlines()[-1])
|
||||||
|
print(f"ALL.res done in {time.time() - t0:.1f}s")
|
||||||
|
break
|
||||||
|
if sent % (128 * 1024) == 0 or sent == size:
|
||||||
|
print(f" {sent}/{size} ({100.0 * sent / size:.1f}%) {time.time() - t0:.1f}s")
|
||||||
|
else:
|
||||||
|
log = wait_for(jlink, "FLASH_ALL_OK", 90.0)
|
||||||
|
print(log.strip().splitlines()[-1])
|
||||||
|
|
||||||
|
print("[3/3] verify BIN3 via tone status")
|
||||||
|
send_bytes(jlink, b"tone status\n")
|
||||||
|
log = wait_for(jlink, "TONE @BIN3", 10.0)
|
||||||
|
for line in log.strip().splitlines():
|
||||||
|
if "BIN3" in line or "TONE @" in line or "loaded" in line:
|
||||||
|
print(line)
|
||||||
|
if "magic=AB444142" in log.replace(" ", "") or "magic=AB" in log:
|
||||||
|
# formats like magic=AB444142 or magic=AB44...
|
||||||
|
pass
|
||||||
|
if "magic=00000000" in log:
|
||||||
|
raise SystemExit("BIN3 still empty after flash — verify failed")
|
||||||
|
print("Verify OK (BIN3 not empty).")
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
jlink.rtt_stop()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
jlink.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--mcu", type=Path, default=DEFAULT_MCU)
|
||||||
|
ap.add_argument("--all", dest="all_res", type=Path, default=DEFAULT_ALL)
|
||||||
|
ap.add_argument("--device", default="AT32F403AC")
|
||||||
|
ap.add_argument("--skip-mcu", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
mcu = resolve_path(args.mcu, FALLBACK_MCU)
|
||||||
|
all_res = resolve_path(args.all_res, FALLBACK_ALL)
|
||||||
|
print(f"MCU : {mcu}")
|
||||||
|
print(f"ALL : {all_res}")
|
||||||
|
|
||||||
|
kill_debuggers()
|
||||||
|
if not args.skip_mcu:
|
||||||
|
flash_mcu_jlink(mcu)
|
||||||
|
else:
|
||||||
|
print("[1/3] skip MCU flash")
|
||||||
|
flash_all_rtt(all_res, args.device)
|
||||||
|
print("Done. Please power-cycle / long-press power and test universal pick.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue