From 2e72a7123af7c57e191f52c539864281b01a2164 Mon Sep 17 00:00:00 2001 From: david rice Date: Mon, 20 Apr 2026 10:42:51 +0100 Subject: [PATCH] Changes --- mipi_test_interactive.py | 109 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/mipi_test_interactive.py b/mipi_test_interactive.py index 5b476f6..6030fc2 100644 --- a/mipi_test_interactive.py +++ b/mipi_test_interactive.py @@ -37,7 +37,8 @@ from dotenv import load_dotenv import ai_mgmt import rigol_scope from csv_preprocessor import (analyze_lp_file, LPMetrics, - HS_BURST_AMPLITUDE_MIN_MV, FLICKER_LP_LOW_MAX_NS) + HS_BURST_AMPLITUDE_MIN_MV, FLICKER_LP_LOW_MAX_NS, + analyze_int_file, CLK_LP_LOW_MIN_NS) load_dotenv(Path(__file__).parent / ".env") @@ -573,6 +574,94 @@ def _fetch_registers(ts: str, iteration: int) -> None: print(f" REGISTERS: error — {e}") +def _arm_scope_for_clk_startup() -> None: + """ + Configure scope for CLK lane LP startup and arm with :SINGle (non-blocking). + Trigger: CLK+ (Ch1) falling edge — fires as CLK leaves LP-11, before DAT0+. + Call this BEFORE display ON so the trigger is armed when the LP-11→HS sequence starts. + """ + for ch in (1, 2, 3, 4): + scope.write(f":CHANnel{ch}:SCALe {LP_V_SCALE:.3f}") + scope.write(f":CHANnel{ch}:OFFSet {LP_V_OFFSET:.3f}") + time.sleep(0.05) + scope.write(":TRIGger:EDGE:SOURce CHANnel1") + scope.write(":TRIGger:EDGE:SLOPe NEGative") + scope.write(f":TRIGger:EDGE:LEVel {LP_TRIG_LEVEL:.3f}") + scope.write(":TRIGger:SWEep NORMal") + scope.write(f":TIMebase:SCALe {LP_SCALE:.3E}") + scope.write(f":ACQuire:POINts {LP_POINTS}") + time.sleep(0.3) + scope.write(":SINGle") + time.sleep(0.1) + print(" CLK STARTUP: scope armed on CLK+ falling edge.") + + +def _collect_clk_startup(ts: str, iteration: int, timeout: float = 10.0) -> list[str]: + """ + Poll for CLK startup trigger, save, transfer, and analyse the capture. + Returns LP summary strings (empty list if trigger timed out). + The CLK LP-00 duration is checked against the 300 ns SN65DSI83 lock minimum. + """ + print(" CLK STARTUP: waiting for trigger...") + deadline = time.time() + timeout + triggered = False + while time.time() < deadline: + try: + status = scope.ask(":TRIGger:STATus?").strip().upper() + if status in ("STOP", "TD"): + triggered = True + break + except Exception: + pass + time.sleep(0.1) + + if not triggered: + print(" CLK STARTUP: trigger timeout — CLK may already be in continuous HS.") + _restore_hs_config() + return [] + + _save_pass_channels("lp", iteration, ts) + _restore_hs_config() + + try: + copied, _ = ai_mgmt.transfer_csv_files() + print(f" CLK STARTUP: {copied} file(s) transferred.") + except Exception as e: + print(f" CLK STARTUP TRANSFER ERROR: {e}") + + summaries = [] + for channel in ("clk", "dat"): + path = DATA_DIR / f"{ts}_lp_{iteration:04d}_{channel}.csv" + if not path.exists(): + continue + try: + m = analyze_lp_file(path) + summaries.append(m.summary()) + if m.clk_lp_startup_ok is False: + print(f"\n *** CLK STARTUP WARNING: CLK LP-00 too short " + f"({m.lp_low_duration_ns:.0f} ns < {CLK_LP_LOW_MIN_NS:.0f} ns) — " + f"SN65DSI83 may fail to lock CLK lane ***\n") + except Exception as e: + print(f" CLK STARTUP ANALYSIS ERROR ({channel}): {e}") + + return summaries + + +def _analyze_int_file(ts: str, iteration: int) -> None: + """Print IRQ pin summary and alert if the SN65DSI83 asserted the IRQ line.""" + path = DATA_DIR / f"{ts}_int_{iteration:04d}.csv" + if not path.exists(): + return + try: + m = analyze_int_file(path) + print(m.summary()) + if m.int_asserted: + print(f"\n *** IRQ ASSERTED: SN65DSI83 flagged a bridge error at " + f"capture {iteration:04d} — check CSR 0xE5 for error bits ***\n") + except Exception as e: + print(f" INT ANALYSIS ERROR: {e}") + + def dual_capture(iteration: int) -> str: """ Three-pass capture per test iteration. @@ -603,7 +692,13 @@ def dual_capture(iteration: int) -> str: if n: print(f" SAVED: {v18_path.name} ({n} samples)") else: - print(" RIGOL: Waveform read failed — check connection and probe.") + print(" RIGOL CH1: waveform read failed — check connection and probe.") + int_path = DATA_DIR / f"{ts}_int_{iteration:04d}.csv" + n_int = rigol_scope.read_int_csv(int_path) + if n_int: + print(f" SAVED: {int_path.name} ({n_int} samples)") + else: + print(" RIGOL CH2: IRQ read failed.") _restore_hs_config() # ── Pass 2: HS signal quality ────────────────────────────────────────── @@ -1193,12 +1288,19 @@ def run_interactive_test() -> None: try: while True: + # ── Arm scope for CLK startup BEFORE display ON ──────────────── + ts_startup = datetime.now().strftime("%Y%m%d_%H%M%S") + _arm_scope_for_clk_startup() + # ── Display ON ───────────────────────────────────────────────── try: requests.put(URL, json={"state": "on"}, timeout=2) except requests.exceptions.RequestException as e: print(f" WARNING: display ON failed: {e}") + # ── Collect CLK startup (polls, saves, transfers, analyses) ──── + _collect_clk_startup(ts_startup, iteration) + # ── Three-pass capture ───────────────────────────────────────── ts = dual_capture(iteration) @@ -1210,6 +1312,9 @@ def run_interactive_test() -> None: except Exception as e: print(f" TRANSFER ERROR: {e}") + # ── IRQ pin analysis ─────────────────────────────────────────── + _analyze_int_file(ts, iteration) + # ── Rule-based LP analysis ───────────────────────────────────── lp_summaries, suspects = _analyze_lp_files(ts, iteration)