From 42534a28b8de60df4fc3dc8c9a46b776b84331ff Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Mon, 31 Aug 2026 21:59:13 +0800 Subject: [PATCH] Add RTT touch diagnostics: tp status/live commands and live event monitor. Harden reset/recover when RTT is missing, and stream touch/UI events only on press for field debugging. Co-authored-by: Cursor --- APP/app_log.c | 12 +++ APP/app_log.h | 4 + project/src/main.c | 1 + tools/rtt_log_dump.py | 3 + tools/rtt_recover.py | 90 +++++++++++++++++ tools/rtt_reset.py | 29 +++++- tools/rtt_touch_live.py | 207 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 341 insertions(+), 5 deletions(-) create mode 100644 tools/rtt_recover.py create mode 100644 tools/rtt_touch_live.py diff --git a/APP/app_log.c b/APP/app_log.c index 9eb1c4a..f61da20 100644 --- a/APP/app_log.c +++ b/APP/app_log.c @@ -161,6 +161,18 @@ uint8_t app_log_try_command(const char *cmd) LOG_I("LOG", "level set %c", s_debug_level); return 1U; } + if (strncmp(cmd, "tp status", 9) == 0) { + TP_LogStatus(); + return 1U; + } + if (strncmp(cmd, "tp live off", 11) == 0) { + TP_SetLiveMode(0); + return 1U; + } + if (strncmp(cmd, "tp live", 7) == 0) { + TP_SetLiveMode(1); + return 1U; + } if (strncmp(cmd, "sys reset", 9) == 0) { app_log_sys_reset(); return 1U; diff --git a/APP/app_log.h b/APP/app_log.h index 3b736b6..e34f9de 100644 --- a/APP/app_log.h +++ b/APP/app_log.h @@ -53,6 +53,9 @@ void app_log_set_ui_page(const char *name); #define LOG_TP(fmt, ...) ((void)0) #endif +/* 触摸按下:始终 INFO,便于 log dump 排查硬件/坐标 */ +#define LOG_TPI(fmt, ...) LOG_I("TP", fmt, ##__VA_ARGS__) + #else #define LOG_I(cat, fmt, ...) ((void)0) @@ -60,6 +63,7 @@ void app_log_set_ui_page(const char *name); #define LOG_E(cat, fmt, ...) ((void)0) #define LOG_D(cat, fmt, ...) ((void)0) #define LOG_TP(fmt, ...) ((void)0) +#define LOG_TPI(fmt, ...) ((void)0) #endif diff --git a/project/src/main.c b/project/src/main.c index bfd1175..f65184f 100644 --- a/project/src/main.c +++ b/project/src/main.c @@ -106,6 +106,7 @@ int main(void) LCD_Dump_Poll(); #if !DEBUG_LCD_DUMP app_log_poll(); + TP_LivePoll(); #endif rt_thread_delay(1); diff --git a/tools/rtt_log_dump.py b/tools/rtt_log_dump.py index c48340e..2fd7322 100644 --- a/tools/rtt_log_dump.py +++ b/tools/rtt_log_dump.py @@ -11,6 +11,7 @@ from datetime import datetime DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A") DUMP_CMD = b"log dump\n" +PROBE_CMD = b"tp status\n" END_MARKERS = (b"LOG_DUMP_END",) @@ -113,6 +114,8 @@ def dump_log(out_path: str, device: str, timeout_s: float) -> None: print("RTT0 stale:", text.strip()[:200]) time.sleep(0.05) + send_command(jlink, PROBE_CMD) + time.sleep(0.25) send_command(jlink, DUMP_CMD) buffer = b"" diff --git a/tools/rtt_recover.py b/tools/rtt_recover.py new file mode 100644 index 0000000..b655570 --- /dev/null +++ b/tools/rtt_recover.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Recover K1 from J-Link halt / black screen: hardware reset + run (no RTT required).""" + +from __future__ import annotations + +import argparse +import time + +DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A") + + +def import_deps(): + try: + import pylink # noqa: F401 + except ImportError as exc: + raise SystemExit("Missing dependency: pip install pylink-square") from exc + + +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 pylink.errors.JLinkException as exc: + last_error = exc + jlink.close() + raise SystemExit(f"Failed to connect J-Link: {last_error}") + + +def run_go(jlink) -> None: + try: + jlink.reset(halt=False) + except Exception: + pass + try: + if hasattr(jlink, "restart"): + jlink.restart() + else: + jlink.go() + except Exception: + jlink.go() + + +def recover(device: str, wait_boot_s: float) -> None: + jlink = connect_jlink(device) + try: + halted = jlink.halted() + print(f"Target halted={halted}") + print("Reset + run...") + run_go(jlink) + if wait_boot_s > 0: + print(f"Waiting {wait_boot_s:.0f}s for auto boot...") + time.sleep(wait_boot_s) + run_go(jlink) + print("Done. Screen should show UI; if still black, run flash_app.bat") + finally: + try: + run_go(jlink) + except Exception: + pass + jlink.close() + + +def main() -> None: + parser = argparse.ArgumentParser(description="Recover K1 from halt/black screen via J-Link") + parser.add_argument("--device", default="AT32F403AC") + parser.add_argument("--wait-boot", type=float, default=4.0) + args = parser.parse_args() + import_deps() + recover(args.device, args.wait_boot) + + +if __name__ == "__main__": + main() diff --git a/tools/rtt_reset.py b/tools/rtt_reset.py index 71a796c..25be790 100644 --- a/tools/rtt_reset.py +++ b/tools/rtt_reset.py @@ -49,8 +49,10 @@ def find_rtt_control_block(jlink, ram_base: int = 0x20000000, ram_size: int = 0x needle = b"SEGGER RTT" chunk = 0x1000 was_halted = jlink.halted() + did_halt = False if not was_halted: jlink.halt() + did_halt = True try: for off in range(0, ram_size, chunk): data = bytes(jlink.memory_read8(ram_base + off, min(chunk, ram_size - off))) @@ -58,11 +60,21 @@ def find_rtt_control_block(jlink, ram_base: int = 0x20000000, ram_size: int = 0x if idx >= 0: return ram_base + off + idx finally: - if not was_halted: + if did_halt: resume_cpu(jlink) return None +def hw_reset_and_run(jlink, wait_s: float = 4.0) -> None: + """J-Link 硬件复位并释放 CPU(RTT 不可用时的唯一可靠恢复手段)。""" + print("Hardware reset + run...") + resume_cpu(jlink) + if wait_s > 0: + print(f"Waiting {wait_s:.0f}s for boot...") + time.sleep(wait_s) + resume_cpu(jlink) + + def resume_cpu(jlink) -> None: """J-Link often halts the core on reset; must run() or UI stays black.""" try: @@ -106,7 +118,16 @@ def send_reset(device: str, timeout_s: float, wait_boot_s: float, hw_fallback: b cb = find_rtt_control_block(jlink) if cb is None: - raise SystemExit("SEGGER RTT control block not found in SRAM") + print("SEGGER RTT control block not found in SRAM.") + if hw_fallback: + hw_reset_and_run(jlink, wait_boot_s) + print("Done (hardware recovery). If still black, run: flash_app.bat") + else: + raise SystemExit( + "RTT not available. Re-run without --no-hw-fallback, or flash_app.bat" + ) + return + print(f"RTT CB @ 0x{cb:08X}") resume_cpu(jlink) @@ -138,12 +159,10 @@ def send_reset(device: str, timeout_s: float, wait_boot_s: float, hw_fallback: b if not ack_seen: print("No SYS_RESET_OK from firmware.") if hw_fallback: - print("Using J-Link hardware reset fallback...") - resume_cpu(jlink) + hw_reset_and_run(jlink, 0.0) else: print("Hint: re-run with --hw-fallback or flash latest firmware.") - # Critical: release CPU after reset — otherwise screen stays black. print("Resuming CPU after reset...") resume_cpu(jlink) diff --git a/tools/rtt_touch_live.py b/tools/rtt_touch_live.py new file mode 100644 index 0000000..090b893 --- /dev/null +++ b/tools/rtt_touch_live.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""Live-stream K1 touch logs via J-Link RTT while you touch the screen. + +Usage: + python rtt_touch_live.py + python rtt_touch_live.py --all # show every RTT line + python rtt_touch_live.py --seconds 120 # auto-stop after N seconds +""" + +from __future__ import annotations + +import argparse +import sys +import time + +DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A") +LIVE_ON_CMD = b"tp live\n" +LIVE_OFF_CMD = b"tp live off\n" + +# Default: touch / UI hit / message path +DEFAULT_KEYS = ("[TP", "[UI", "[MSG") + + +def import_deps(): + try: + import pylink # noqa: F401 + except ImportError as exc: + raise SystemExit("Missing dependency: pip install pylink-square") from exc + + +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}", flush=True) + return jlink + except pylink.errors.JLinkException 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) -> int | None: + 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 wait_rtt_ready(jlink, timeout_s: float = 15.0) -> None: + deadline = time.time() + timeout_s + while time.time() < deadline: + try: + if jlink.rtt_get_num_up_buffers() > 0: + return + except Exception: + pass + time.sleep(0.2) + raise SystemExit("RTT control block found but buffers not ready") + + +def send_command(jlink, payload: bytes, retries: int = 40) -> None: + for _ in range(retries): + wrote = jlink.rtt_write(0, list(payload)) + if wrote > 0: + print(f">> {payload.decode('ascii', errors='replace').strip()}", flush=True) + return + time.sleep(0.15) + print("WARN: failed to send RTT command", flush=True) + + +def want_line(line: str, show_all: bool, keys: tuple[str, ...]) -> bool: + if show_all: + return True + return any(k in line for k in keys) + + +def run_live(device: str, show_all: bool, seconds: float, out_path: str | None) -> None: + jlink = connect_jlink(device) + out_fp = open(out_path, "w", encoding="utf-8", newline="\n") if out_path else None + try: + if jlink.halted(): + if hasattr(jlink, "restart"): + jlink.restart() + else: + jlink.go() + time.sleep(0.3) + + cb = find_rtt_control_block(jlink) + if cb is None: + raise SystemExit("SEGGER RTT control block not found — is app running?") + print(f"RTT CB @ 0x{cb:08X}", flush=True) + + jlink.rtt_start(cb) + wait_rtt_ready(jlink) + + # Drain backlog + for _ in range(8): + junk = jlink.rtt_read(0, 4096) + if not junk: + break + + print("Waiting ~8s for UI/touch task...", flush=True) + time.sleep(8.0) + for _ in range(4): + junk = jlink.rtt_read(0, 4096) + if not junk: + break + + # Event-only: firmware no longer floods idle samples. + # Optional: tp live just tags the session; leave it off by default. + print("Event log ON — only touch events will print. Touch the screen. Ctrl+C to stop.", flush=True) + print("-" * 60, flush=True) + + buf = b"" + t0 = time.time() + while True: + if seconds > 0 and (time.time() - t0) >= seconds: + print("\n(time limit reached)", flush=True) + break + + chunk = jlink.rtt_read(0, 4096) + if chunk: + buf += bytes(chunk) + while True: + nl = buf.find(b"\n") + if nl < 0: + break + raw = buf[:nl] + buf = buf[nl + 1 :] + line = raw.decode("utf-8", errors="replace").rstrip("\r") + if not line: + continue + if want_line(line, show_all, DEFAULT_KEYS): + print(line, flush=True) + if out_fp: + out_fp.write(line + "\n") + out_fp.flush() + else: + time.sleep(0.01) + except KeyboardInterrupt: + print("\nStopped by user.", flush=True) + finally: + try: + jlink.rtt_stop() + except Exception: + pass + try: + if hasattr(jlink, "restart"): + jlink.restart() + else: + jlink.go() + except Exception: + pass + jlink.close() + if out_fp: + out_fp.close() + print(f"Saved: {out_path}", flush=True) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Live K1 touch RTT monitor") + parser.add_argument("--device", default="AT32F403AC") + parser.add_argument("--all", action="store_true", help="Print all RTT lines") + parser.add_argument("--seconds", type=float, default=0, help="Auto stop after N seconds (0=forever)") + parser.add_argument( + "--out", + default="", + help="Also append filtered lines to this file", + ) + args = parser.parse_args() + import_deps() + run_live(args.device, args.all, args.seconds, args.out or None) + + +if __name__ == "__main__": + main()