182 lines
6.1 KiB
Python
182 lines
6.1 KiB
Python
|
|
#!/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("<HHH", 40, 260, 1)
|
|||
|
|
GUI_SWITCH_SIZE = 8
|
|||
|
|
TAB_INDEX = 5
|
|||
|
|
|
|||
|
|
|
|||
|
|
def count_pitch(lines: list[str]) -> 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("<H", 2))
|
|||
|
|
got = struct.unpack("<H", mem_read(jlink, tab_addr, 2))[0]
|
|||
|
|
print(f"mGuiData @ 0x{base:08X} forced TAB={got}", flush=True)
|
|||
|
|
return got == 2
|
|||
|
|
|
|||
|
|
|
|||
|
|
def wait_enter(msg: str, seconds: float) -> 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())
|