#!/usr/bin/env python3 """RTT verify normal-mode: hold pad + pick sounds; release stops. Uses real physical pad hold (inject is cleared by TM1629 scan within one tick). """ from __future__ import annotations import struct import sys import time sys.path.insert(0, ".") from rtt_pitch_reg_test import ( # noqa: E402 RttSession, connect_jlink, find_rtt_control_block, wait_rtt_ready, ) SPEED_TAIL = struct.pack(" int: return sum(1 for L in lines if "[PITCH]" in L and " on " in L) def parse_tab(lines: list[str]) -> int | None: for L in reversed(lines): if "TONE tab=" not in L: continue try: return int(L.split("tab=")[1].split()[0]) except Exception: continue return None def with_halt(jlink, fn): was = jlink.halted() if not was: jlink.halt() try: return fn() finally: if not was: if hasattr(jlink, "restart"): jlink.restart() else: jlink.go() time.sleep(0.02) def find_pattern(jlink, needle: bytes, ram_base=0x20000000, ram_size=0x18000) -> int | None: def _find(): chunk = 0x1000 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 return None return with_halt(jlink, _find) def mem_write(jlink, addr: int, data: bytes) -> None: with_halt(jlink, lambda: jlink.memory_write8(addr, list(data))) def mem_read(jlink, addr: int, n: int) -> bytes: return with_halt(jlink, lambda: bytes(jlink.memory_read8(addr, n))) def force_tab_normal(jlink) -> bool: hit = find_pattern(jlink, SPEED_TAIL) if hit is None: print("!! GUI_SPEED row not found", flush=True) return False base = (hit - 2) - GUI_SWITCH_SIZE tab_addr = base + TAB_INDEX * GUI_SWITCH_SIZE mem_write(jlink, tab_addr, struct.pack(" None: print(f"\n>>> {msg}", flush=True) print(f" ({seconds:.0f}s countdown)", flush=True) for left in range(int(seconds), 0, -1): print(f" {left}...", flush=True) time.sleep(1.0) def main() -> int: j = connect_jlink("Cortex-M4") cb = find_rtt_control_block(j) if not cb: raise SystemExit("RTT CB not found") print(f"RTT CB @ 0x{cb:08X}", flush=True) j.rtt_start(cb) wait_rtt_ready(j) sess = RttSession(j) print("\n=== Setup: 1.bin + TAB=普通(2) ===", flush=True) sess.cmd("log clear", 0.2) sess.cmd_ack("tone bin1 0", "TONE_BIN1", timeout_s=4.0, retries=4) if not force_tab_normal(j): j.rtt_stop() j.close() return 3 sess.cmd_ack("tone status", "TONE tab=", timeout_s=3.0, retries=4) print(f"tab={parse_tab(sess.lines)}", flush=True) # Stop any leftover sess.cmd_ack("chord key 23", "CHORD_KEY_OK", timeout_s=2.0, retries=3) force_tab_normal(j) print("\n=== TEST1: pick with NO pad hold (expect silence) ===", flush=True) print("请双手离开指板。", flush=True) wait_enter("双手离开指板后等待自动拨片测试", 3) force_tab_normal(j) sess.cmd("log clear", 0.2) sess.cmd_ack("tone start", "TONE_START_DONE", timeout_s=3.0, retries=5) n1 = count_pitch(sess.pump(1.5)) print(f"TEST1 PITCH on={n1} (expect 0)", flush=True) print("\n=== TEST2: hold pad + pick (expect sound) ===", flush=True) wait_enter("请按住指板任意和弦键不放(建议按住第2排中部)", 5) force_tab_normal(j) sess.cmd("log clear", 0.2) # keep KEY from physical hold; tone start without clearing if we use 'k' # But we don't know KEY — physical hold already set KEY_ID+PressFlag via scan. # tone start WITHOUT k clears KEY_ID! Must use tone start k — but then KEY kept. # If user is holding, KEY_ID is set; PressFlag is set. tone start sets StartFlag=0 # then clears KEY unless k. Use: don't clear — need tone start k AND physical KEY already set. sess.cmd_ack("tone start k", "TONE_START_DONE", timeout_s=3.0, retries=5) play = sess.pump(2.5) n2 = count_pitch(play) print(f"TEST2 PITCH on={n2} (expect >0) — keep holding!", flush=True) for L in play: if "[PITCH]" in L: print(" ", L, flush=True) break print("\n=== TEST3: release pad (expect stop) ===", flush=True) wait_enter("请松开指板(松手)", 3) force_tab_normal(j) sess.cmd("log clear", 0.15) post = sess.pump(2.0) n3 = count_pitch(post) print(f"TEST3 PITCH on after release={n3} (expect 0)", flush=True) print("\n=== TEST4: hold again + pick (expect sound) ===", flush=True) wait_enter("请再次按住指板和弦键不放", 5) force_tab_normal(j) sess.cmd("log clear", 0.2) sess.cmd_ack("tone start k", "TONE_START_DONE", timeout_s=3.0, retries=5) n4 = count_pitch(sess.pump(2.0)) print(f"TEST4 PITCH on={n4} (expect >0)", flush=True) print("可松开指板。", flush=True) time.sleep(1.0) sess.cmd_ack("chord key 23", "CHORD_KEY_OK", timeout_s=2.0, retries=3) t1, t2, t3, t4 = (n1 == 0), (n2 >= 1), (n3 == 0), (n4 >= 1) print("\n======== RESULT ========", flush=True) print(f"TEST1 no-hold silence: {'PASS' if t1 else 'FAIL'} (n={n1})", flush=True) print(f"TEST2 hold+pick sound: {'PASS' if t2 else 'FAIL'} (n={n2})", flush=True) print(f"TEST3 release stop: {'PASS' if t3 else 'FAIL'} (n={n3})", flush=True) print(f"TEST4 re-trigger: {'PASS' if t4 else 'FAIL'} (n={n4})", flush=True) ok_all = t1 and t2 and t3 and t4 print("OVERALL:", "PASS" if ok_all else "FAIL", flush=True) j.rtt_stop() j.close() return 0 if ok_all else 1 if __name__ == "__main__": raise SystemExit(main())