From 956eeee1a50a8abec7f0dc164a4924edd2fc62c7 Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Mon, 31 Aug 2026 12:31:53 +0800 Subject: [PATCH] Add RTT sys reset command and remote reboot script with CPU resume. Co-authored-by: Cursor --- APP/app_log.c | 19 +++ device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c | 10 +- tools/LOG_README.md | 10 ++ tools/rtt_reset.py | 185 ++++++++++++++++++++++ 4 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 tools/rtt_reset.py diff --git a/APP/app_log.c b/APP/app_log.c index 9121606..9eb1c4a 100644 --- a/APP/app_log.c +++ b/APP/app_log.c @@ -129,6 +129,15 @@ static void app_log_print_status(void) SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "\n"); } +static void app_log_sys_reset(void) +{ + LOG_I("SYS", "reset via RTT"); + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "SYS_RESET_OK\n"); + rt_thread_mdelay(20); + __disable_irq(); + nvic_system_reset(); +} + uint8_t app_log_try_command(const char *cmd) { if (cmd == NULL || cmd[0] == '\0') { @@ -152,6 +161,10 @@ uint8_t app_log_try_command(const char *cmd) LOG_I("LOG", "level set %c", s_debug_level); return 1U; } + if (strncmp(cmd, "sys reset", 9) == 0) { + app_log_sys_reset(); + return 1U; + } return 0U; } @@ -172,6 +185,12 @@ static void app_log_feed_char(char c) } s_cmd_buf[s_cmd_len++] = c; s_cmd_buf[s_cmd_len] = '\0'; + + if (strstr(s_cmd_buf, "sys reset") != NULL) { + s_cmd_len = 0U; + s_cmd_buf[0] = '\0'; + (void)app_log_try_command("sys reset"); + } } void app_log_poll(void) diff --git a/device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c b/device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c index dc91780..4472cf3 100644 --- a/device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c +++ b/device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c @@ -8,9 +8,10 @@ #define LCD_DUMP_RTT_DOWN_CH 0 #define LCD_DUMP_CMD "DUMP" #define LCD_DUMP_TAP_CMD "TAP" +#define LCD_DUMP_RESET_CMD "sys reset" #define LCD_DUMP_MAGIC "SCRN" #define LCD_DUMP_FMT_RGB565 1 -#define LCD_DUMP_CMD_BUF_SIZE 16 +#define LCD_DUMP_CMD_BUF_SIZE 32 #pragma pack(1) typedef struct { @@ -226,6 +227,13 @@ static uint8_t LCD_Dump_CommandPending(void) continue; } + if (strstr(s_cmd_buf, LCD_DUMP_RESET_CMD) != NULL) { + s_cmd_len = 0U; + s_cmd_buf[0] = '\0'; + (void)app_log_try_command(LCD_DUMP_RESET_CMD); + continue; + } + if (c == 0x01 || strstr(s_cmd_buf, LCD_DUMP_CMD) != NULL) { s_cmd_len = 0U; s_cmd_buf[0] = '\0'; diff --git a/tools/LOG_README.md b/tools/LOG_README.md index d16b765..10b3f5f 100644 --- a/tools/LOG_README.md +++ b/tools/LOG_README.md @@ -31,6 +31,15 @@ python rtt_log_dump.py --out problem.log | `log clear` | 清空 RAM 日志 | | `log status` | 查看 boot 次数、条数、溢出、当前 UI 页 | | `log level D` | 开启 Debug 级(含触摸细节);`I` 仅 Info 以上 | +| `sys reset` | MCU 软件复位(自动重新开机,**RAM 日志会丢失**) | + +或通过脚本(**复位后脚本会自动 `go()` 释放 CPU,避免 J-Link 停核黑屏**): + +```bash +python rtt_reset.py +``` + +若固件较旧无 `sys reset`,脚本会自动用 J-Link 硬件复位兜底。 ## 日志格式 @@ -44,6 +53,7 @@ python rtt_log_dump.py --out problem.log - 屏幕截图:`python rtt_lcd_capture.py --out screen.png` - 日志导出:`python rtt_log_dump.py --out problem.log` +- 远程复位:`python rtt_reset.py` ## 触摸问题排查要点 diff --git a/tools/rtt_reset.py b/tools/rtt_reset.py new file mode 100644 index 0000000..71a796c --- /dev/null +++ b/tools/rtt_reset.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""Send sys reset via J-Link RTT to reboot K1 MCU (software NVIC reset).""" + +from __future__ import annotations + +import argparse +import time + +DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A") +RESET_CMD = b"sys reset\n" +ACK = b"SYS_RESET_OK" + + +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 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: + resume_cpu(jlink) + return None + + +def resume_cpu(jlink) -> None: + """J-Link often halts the core on reset; must run() or UI stays black.""" + try: + jlink.rtt_stop() + except Exception: + pass + try: + jlink.reset(halt=False) + except Exception: + pass + try: + if hasattr(jlink, "restart"): + jlink.restart() + else: + jlink.go() + except Exception: + jlink.go() + time.sleep(0.05) + + +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_reset(device: str, timeout_s: float, wait_boot_s: float, hw_fallback: bool) -> None: + jlink = connect_jlink(device) + ack_seen = False + try: + if jlink.halted(): + print("Target was halted, resuming before RTT...") + resume_cpu(jlink) + time.sleep(0.2) + + cb = find_rtt_control_block(jlink) + if cb is None: + raise SystemExit("SEGGER RTT control block not found in SRAM") + print(f"RTT CB @ 0x{cb:08X}") + + resume_cpu(jlink) + time.sleep(0.15) + jlink.rtt_start(cb) + wait_rtt_ready(jlink) + + wrote = 0 + for _ in range(30): + wrote = jlink.rtt_write(0, list(RESET_CMD)) + if wrote > 0: + break + time.sleep(0.2) + if wrote <= 0: + raise SystemExit("Failed to send sys reset on RTT down channel 0") + print(f"Sent sys reset ({wrote} bytes)") + + deadline = time.time() + timeout_s + while time.time() < deadline: + chunk = jlink.rtt_read(0, 1024) + if chunk: + text = bytes(chunk) + if ACK in text: + ack_seen = True + print("SYS_RESET_OK (firmware handled reset)") + break + time.sleep(0.05) + + if not ack_seen: + print("No SYS_RESET_OK from firmware.") + if hw_fallback: + print("Using J-Link hardware reset fallback...") + resume_cpu(jlink) + 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) + + if wait_boot_s > 0: + print(f"Waiting {wait_boot_s:.0f}s for auto boot...") + time.sleep(wait_boot_s) + resume_cpu(jlink) + print("Done. Device should be back on mode-select screen.") + finally: + try: + resume_cpu(jlink) + except Exception: + pass + jlink.close() + + +def main() -> None: + parser = argparse.ArgumentParser(description="Reboot K1 via RTT sys reset command") + parser.add_argument("--device", default="AT32F403AC", help="J-Link device name") + parser.add_argument("--timeout", type=float, default=3.0, help="Seconds to wait for ACK") + parser.add_argument( + "--wait-boot", + type=float, + default=4.0, + help="Seconds to wait after reset for auto power-on (0 to skip)", + ) + parser.add_argument( + "--no-hw-fallback", + action="store_true", + help="Do not use J-Link hardware reset when firmware ACK is missing", + ) + args = parser.parse_args() + + import_deps() + send_reset(args.device, args.timeout, args.wait_boot, not args.no_hw_fallback) + + +if __name__ == "__main__": + main()