2026-09-02 15:24:49 +08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Flash tools/out/ui0902_res.bin to W25Q128 @ 0x00100000 via RTT."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A")
|
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
DEFAULT_BIN = os.path.join(ROOT, "tools", "out", "ui0902_res.bin")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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_error = 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_error = exc
|
|
|
|
|
jlink.close()
|
|
|
|
|
raise SystemExit(f"Failed to connect J-Link: {last_error}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_rtt_control_block(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 = 30.0) -> 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[-800:]}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def send_bytes(jlink, payload: bytes) -> None:
|
|
|
|
|
off = 0
|
|
|
|
|
while off < len(payload):
|
|
|
|
|
n = jlink.rtt_write(0, list(payload[off : off + 128]))
|
|
|
|
|
if n <= 0:
|
|
|
|
|
time.sleep(0.01)
|
|
|
|
|
continue
|
|
|
|
|
off += n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
|
parser.add_argument("--bin", default=DEFAULT_BIN)
|
|
|
|
|
parser.add_argument("--device", default="Cortex-M4")
|
2026-09-02 16:23:18 +08:00
|
|
|
parser.add_argument("--no-reset", action="store_true",
|
|
|
|
|
help="Skip hardware reset (device must already be running)")
|
2026-09-02 15:24:49 +08:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
if not os.path.isfile(args.bin):
|
|
|
|
|
raise SystemExit(f"missing bin: {args.bin}")
|
|
|
|
|
|
|
|
|
|
data = open(args.bin, "rb").read()
|
|
|
|
|
size = len(data)
|
|
|
|
|
print(f"bin={args.bin} size={size}")
|
|
|
|
|
|
|
|
|
|
jlink = connect_jlink(args.device)
|
|
|
|
|
try:
|
|
|
|
|
cb = find_rtt_control_block(jlink)
|
|
|
|
|
if cb is None:
|
|
|
|
|
raise SystemExit("RTT control block not found")
|
|
|
|
|
print(f"RTT CB @ 0x{cb:08X}")
|
|
|
|
|
jlink.rtt_start(cb)
|
|
|
|
|
time.sleep(0.3)
|
|
|
|
|
_ = rtt_read_text(jlink, 0.3)
|
|
|
|
|
|
2026-09-02 16:23:18 +08:00
|
|
|
if not args.no_reset:
|
|
|
|
|
# Hardware reset clears any stuck flash_mode from a previous attempt
|
|
|
|
|
print("hardware reset...")
|
|
|
|
|
jlink.reset(halt=False)
|
|
|
|
|
time.sleep(3.5)
|
|
|
|
|
# RTT CB may move after reboot — re-find
|
|
|
|
|
try:
|
|
|
|
|
jlink.rtt_stop()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
cb = find_rtt_control_block(jlink)
|
|
|
|
|
if cb is None:
|
|
|
|
|
raise SystemExit("RTT CB missing after reset")
|
|
|
|
|
print(f"RTT CB @ 0x{cb:08X}")
|
|
|
|
|
jlink.rtt_start(cb)
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
_ = rtt_read_text(jlink, 0.5)
|
2026-09-02 15:24:49 +08:00
|
|
|
|
|
|
|
|
cmd = f"flash ui0902 {size}\n".encode("ascii")
|
|
|
|
|
send_bytes(jlink, cmd)
|
|
|
|
|
print("sent flash command, waiting GO...")
|
2026-09-02 16:23:18 +08:00
|
|
|
log = wait_for(jlink, "FLASH_UI0902_GO", 20.0)
|
2026-09-02 15:24:49 +08:00
|
|
|
print(log.strip().splitlines()[-1])
|
|
|
|
|
time.sleep(0.2)
|
|
|
|
|
|
2026-09-02 16:23:18 +08:00
|
|
|
# Smaller chunks + brief pause so erase/write can keep up with RTT down-buffer
|
|
|
|
|
chunk = 128
|
2026-09-02 15:24:49 +08:00
|
|
|
sent = 0
|
|
|
|
|
t0 = time.time()
|
|
|
|
|
send_bytes(jlink, data[0:256])
|
|
|
|
|
sent = 256
|
2026-09-02 16:23:18 +08:00
|
|
|
wait_for(jlink, "FLASH_RX", 20.0)
|
2026-09-02 15:24:49 +08:00
|
|
|
print("device receiving...")
|
|
|
|
|
while sent < size:
|
|
|
|
|
end = min(sent + chunk, size)
|
|
|
|
|
send_bytes(jlink, data[sent:end])
|
|
|
|
|
sent = end
|
2026-09-02 16:23:18 +08:00
|
|
|
if sent % 256 == 0:
|
|
|
|
|
time.sleep(0.002)
|
2026-09-02 15:24:49 +08:00
|
|
|
if sent % 4096 == 0 or sent == size:
|
2026-09-02 16:23:18 +08:00
|
|
|
log = wait_for(jlink, "FLASH_ACK", 60.0)
|
2026-09-02 15:24:49 +08:00
|
|
|
if "FLASH_UI0902_OK" in log:
|
|
|
|
|
print(log.strip().splitlines()[-1])
|
|
|
|
|
print("Done. Mode-select should redraw with new assets.")
|
|
|
|
|
return
|
|
|
|
|
if sent % (64 * 1024) == 0 or sent == size:
|
|
|
|
|
elapsed = time.time() - t0
|
|
|
|
|
print(f" {sent}/{size} ({100.0 * sent / size:.1f}%) {elapsed:.1f}s")
|
|
|
|
|
|
2026-09-02 16:23:18 +08:00
|
|
|
log = wait_for(jlink, "FLASH_UI0902_OK", 60.0)
|
2026-09-02 15:24:49 +08:00
|
|
|
print(log.strip().splitlines()[-1])
|
|
|
|
|
print("Done. Mode-select should redraw with new assets.")
|
|
|
|
|
finally:
|
|
|
|
|
try:
|
|
|
|
|
jlink.rtt_stop()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
jlink.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|