diff --git a/tools/flash_app_only.jlink b/tools/flash_app_only.jlink new file mode 100644 index 0000000..8e707cf --- /dev/null +++ b/tools/flash_app_only.jlink @@ -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 diff --git a/tools/flash_mcu_and_extflash.py b/tools/flash_mcu_and_extflash.py new file mode 100644 index 0000000..c2c16e0 --- /dev/null +++ b/tools/flash_mcu_and_extflash.py @@ -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()