diff --git a/__pycache__/csv_preprocessor.cpython-312.pyc b/__pycache__/csv_preprocessor.cpython-312.pyc index 13818c9..d7624f7 100644 Binary files a/__pycache__/csv_preprocessor.cpython-312.pyc and b/__pycache__/csv_preprocessor.cpython-312.pyc differ diff --git a/csv_preprocessor.py b/csv_preprocessor.py index 5b69487..4875d1f 100644 --- a/csv_preprocessor.py +++ b/csv_preprocessor.py @@ -48,10 +48,16 @@ LP11_SPEC_MAX_V = 1.45 # V — LP-11 maximum voltage spec LP_LOW_DUR_MIN_NS = 50.0 # ns — minimum LP-low duration per D-PHY spec (LP-01 + LP-00 combined) HS_OSC_STD_V = 0.045 # V — rolling-std threshold above which a region is classified as HS -# Flicker detection threshold +# Flicker detection thresholds # LP-low plateau below this → SoT sequence too brief for receiver to detect → flicker risk FLICKER_LP_LOW_MAX_NS = 50.0 # ns +# HS burst amplitude below this (single-ended p-p / 2, mV) → HS burst absent after LP transition. +# On this hardware normal HS = 105–122 mV; confirmed flicker = 14–32 mV (DC / LP-11 recovery). +# Captures where LP-01/LP-00 completed normally but the bridge never entered HS mode show +# essentially zero amplitude (the burst window is DC LP-11), so lp_low alone cannot detect this. +HS_BURST_AMPLITUDE_MIN_MV = 50.0 # mV — below this, no real HS burst is present + @dataclass class ChannelMetrics: @@ -670,7 +676,18 @@ class LPMetrics: if self.hs_amplitude_mv is not None: lines.append(f" HS amplitude : {self.hs_amplitude_mv:.0f} mV (single-ended p-p/2)") if self.flicker_suspect: - lines.append(f" *** FLICKER SUSPECT: LP-low plateau absent or < {FLICKER_LP_LOW_MAX_NS:.0f} ns ***") + if (self.hs_amplitude_mv is not None + and self.hs_amplitude_mv < HS_BURST_AMPLITUDE_MIN_MV + and (self.lp_low_duration_ns is None + or self.lp_low_duration_ns >= FLICKER_LP_LOW_MAX_NS)): + lines.append( + f" *** FLICKER SUSPECT: HS burst absent " + f"(amplitude {self.hs_amplitude_mv:.0f} mV < {HS_BURST_AMPLITUDE_MIN_MV:.0f} mV) ***" + ) + else: + lines.append( + f" *** FLICKER SUSPECT: LP-low plateau absent or < {FLICKER_LP_LOW_MAX_NS:.0f} ns ***" + ) for w in self.warnings: lines.append(f" WARNING: {w}") return "\n".join(lines) @@ -732,7 +749,8 @@ def analyze_lp_file(path: Path) -> "LPMetrics": lp11_voltage_v = round(float(np.concatenate( [volts[s:e] for s, e in lp11_regions]).mean()), 3) lp11_duration_us = round( - sum((times[e] - times[s]) for s, e in lp11_regions) * 1e6, 3) + sum((times[min(e, len(times) - 1)] - times[s]) + for s, e in lp11_regions) * 1e6, 3) # ── HS burst detection ──────────────────────────────────────────────── # On DAT0+ with a uniform-colour display, HS data can look DC (no bit @@ -758,8 +776,9 @@ def analyze_lp_file(path: Path) -> "LPMetrics": for i, (lp11_s, lp11_e) in enumerate(lp11_regions): # Burst ends at start of next LP-11, or at window end burst_end = lp11_regions[i + 1][0] if i + 1 < len(lp11_regions) else len(times) - 1 - burst_dur_ns = round((times[burst_end] - times[lp11_e]) * 1e9, 1) - hs_bursts.append((lp11_e, burst_end, burst_dur_ns)) + lp11_e_idx = min(lp11_e, len(times) - 1) # guard: region end can == len(times) + burst_dur_ns = round((times[burst_end] - times[lp11_e_idx]) * 1e9, 1) + hs_bursts.append((lp11_e_idx, burst_end, burst_dur_ns)) if hs_bursts: n_hs_bursts = len(hs_bursts) @@ -776,12 +795,15 @@ def analyze_lp_file(path: Path) -> "LPMetrics": # LP-low plateau: look for a contiguous region in the exit window # where voltage < LP_LOW_V and std is low (true LP-01/LP-00 plateau) lp_low_mask = (volts < LP_LOW_V) & (rstd < HS_OSC_STD_V) - lp_low_regions = _find_contiguous_regions(lp_low_mask, min_samples=5) + # Time-based minimum: reject glitches shorter than 5 ns. + # At ~40 GSa/s (25 ps/sample) the old min_samples=5 admitted 125 ps noise spikes. + _min_lp_low = max(5, int(5e-9 / dt)) + lp_low_regions = _find_contiguous_regions(lp_low_mask, min_samples=_min_lp_low) exit_window = int(1e-6 / dt) for lplow_s, lplow_e in lp_low_regions: if s_end <= lplow_s <= s_end + exit_window: lp_low_duration_ns = round( - (times[lplow_e] - times[lplow_s]) * 1e9, 1) + (times[min(lplow_e, len(times) - 1)] - times[lplow_s]) * 1e9, 1) break # HS single-ended amplitude from the first burst (where data may vary) @@ -818,13 +840,31 @@ def analyze_lp_file(path: Path) -> "LPMetrics": if n_hs_bursts == 0: warnings.append("No HS bursts detected after LP transition") - # Flicker suspect: LP→HS sequence detected but LP-low plateau is absent or too short. - # Normal captures show ~340 ns; the confirmed flicker capture showed 0 ns. + # Flicker suspect: either the LP-low plateau is absent/short, OR the HS burst + # amplitude is too low (indicating the HS burst never actually started). + # + # The second condition catches the confirmed failure mode on this hardware: + # LP-11 → LP-01/LP-00 preamble (normal ~342 ns) → bridge misses SoT + # → driver returns to LP-11 without entering HS mode + # → burst window is DC LP-11, hs_amplitude ≈ 15–30 mV (vs normal 105–122 mV). + # In this case lp_low_duration_ns = ~342 ns (above threshold), so the LP-low + # check alone produces a false negative. + # # Only flag DAT lane (CLK is continuous HS — LP states not expected). + # NOTE: lp11_to_hs_ns is NOT used here — on this hardware a consistent noise spike + # at LP-11 exit causes the rolling-std gate to fire at ~3 ns for every capture, + # making it indistinguishable from a genuine flicker (2.8 ns confirmed flicker). + hs_burst_absent = ( + hs_amplitude_mv is not None + and hs_amplitude_mv < HS_BURST_AMPLITUDE_MIN_MV + ) flicker_suspect = ( channel == "dat" and lp_transition_valid - and (lp_low_duration_ns is None or lp_low_duration_ns < FLICKER_LP_LOW_MAX_NS) + and ( + (lp_low_duration_ns is None or lp_low_duration_ns < FLICKER_LP_LOW_MAX_NS) + or hs_burst_absent + ) ) return LPMetrics( diff --git a/mipi_test_interactive.py b/mipi_test_interactive.py new file mode 100644 index 0000000..198217c --- /dev/null +++ b/mipi_test_interactive.py @@ -0,0 +1,1273 @@ +#!/usr/bin/env python3 +""" +MIPI TEST APPLICATION - MIPI_TEST_INTERACTIVE.PY +Interactive flicker confirmation test. + +Same three-pass capture sequence as mipi_test.py. After each iteration: + 1. LP files are transferred from the scope immediately and analysed. + 2. If the rule-based LP pre-processor flags a flicker suspect, Claude is + asked to assess the single capture. + 3. If Claude agrees it looks like a flicker event, the test pauses and + asks the operator to confirm by looking at the display. + Confirmed → event is logged, HTML report is written, test STOPS. + Not flickering (false alarm) → event is logged, test CONTINUES. + 4. A final HTML report is written when the test ends for any reason. + +VERSION: 0.1 +AUTHOR: D. RICE 16/04/2026 +© 2026 ARRIVE +""" + +import csv as _csv_mod +import html +import json +import subprocess +import time +import sys +import requests +from datetime import datetime +from pathlib import Path + +import math + +import anthropic +import vxi11 +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) + +load_dotenv(Path(__file__).parent / ".env") + +# --------------------------------------------------------------------------- +# Configuration (same as mipi_test.py) +# --------------------------------------------------------------------------- + +DEVICE_BASE = "http://192.168.45.8:5000" +URL = f"{DEVICE_BASE}/display" +SCOPE_IP = "192.168.45.4" +PSU_IP = "192.168.45.3" + +# Pass 1 — signal quality (HS differential, rise/fall) +SIG_SCALE = 2e-9 # 2 ns/div → 20 ns window +SIG_POINTS = 500_000 + +# Pass 2 — protocol/frame structure (HS differential, jitter/freq) +PROTO_SCALE = 1e-6 # 1 µs/div → 10 µs window +PROTO_POINTS = 500_000 + +# Pass 3 — LP state capture (single-ended, widens vertical range to show LP-11) +LP_SCALE = 500e-9 # 500 ns/div → 5 µs window +LP_POINTS = 200_000 +LP_V_SCALE = 0.2 # V/div +LP_V_OFFSET = 0.6 # V — centres display between LP-00 (0 V) and LP-11 (1.2 V) +LP_TRIG_LEVEL = 0.6 # V — catches LP-11 → LP-01 falling edge + +DATA_DIR = Path(__file__).parent / "data" +REPORTS_DIR = Path(__file__).parent / "reports" + +# Persistent logs (shared paths — consistent with mipi_test.py / analyze_captures.py) +FLICKER_LOG = REPORTS_DIR / "flicker_log.csv" +INTERACTIVE_LOG = REPORTS_DIR / "interactive_log.csv" + +# Claude model for per-capture flicker assessment +CLAUDE_MODEL = "claude-opus-4-6" + +# --------------------------------------------------------------------------- +# D-PHY timing calculation +# --------------------------------------------------------------------------- + +# Ordered field list used for table formatting and u-boot output +_TIMING_FIELD_ORDER = [ + "lpx", "hs_prepare", "hs_zero", "hs_trail", "hs_exit", + "clk_prepare", "clk_zero", "clk_post", "clk_trail", +] + +# Maps field names → flb_dtovar property names (NXP i.MX 8M Mini Samsung DSIM driver) +_EXTRA_PROP_MAP = { + "lpx": "dsi-phy-extra-lpx", + "hs_prepare": "dsi-phy-extra-hs-prepare", + "hs_zero": "dsi-phy-extra-hs-zero", + "hs_trail": "dsi-phy-extra-hs-trail", + "hs_exit": "dsi-phy-extra-hs-exit", + "clk_prepare": "dsi-phy-extra-clk-prepare", + "clk_zero": "dsi-phy-extra-clk-zero", + "clk_post": "dsi-phy-extra-clk-post", + "clk_trail": "dsi-phy-extra-clk-trail", +} + + +def calculate_dphy_timing(pixel_clock_mhz: float, + extra_cycles: dict | None = None) -> dict: + """ + Calculate Samsung DSIM PHY timing register values for a given pixel clock. + + Assumes RGB888 (24 bpp), 4 DSI lanes — NXP i.MX 8M Mini / Ampire 1280×800. + Timing constraints from MIPI D-PHY v1.1 Table 14. + + extra_cycles: optional dict mapping field names to additional cycles above the + Round-Up minimum, e.g. {'clk_zero': 3, 'hs_prepare': 1, 'hs_trail': 1}. + + Samsung DSIM register packing: + PHY_TIMING (0xb4): (lpx << 8) | hs_exit + PHY_TIMING1 (0xb8): (clk_prepare << 24) | (clk_zero << 16) | (clk_post << 8) | clk_trail + PHY_TIMING2 (0xbc): (hs_prepare << 16) | (hs_zero << 8) | hs_trail + + Returns a dict with: pixel_clock_mhz, bit_rate_mbps, byte_clock_mhz, + byte_period_ns, ui_ns, fields, registers, violations. + """ + bpp = 24 + lanes = 4 + extras = extra_cycles or {} + + bit_rate_mbps = pixel_clock_mhz * bpp / lanes # Mbit/s per lane + byte_clock_mhz = bit_rate_mbps / 8 # MHz + byte_period_ns = 1000.0 / byte_clock_mhz # ns per byte-clock cycle + ui_ns = 1000.0 / bit_rate_mbps # ns per UI + + def _ru(t_ns: float) -> int: + return max(1, math.ceil(t_ns / byte_period_ns)) + + def _rb(t_ns: float) -> int: + return max(1, round(t_ns / byte_period_ns)) + + def _field(name: str, min_ns: float, max_ns: float | None = None) -> dict: + extra = extras.get(name, 0) + ru = _ru(min_ns) + rb = _rb(min_ns) + final = ru + extra + return { + "min_ns": min_ns, + "max_ns": max_ns, + "round_best": rb, + "round_up": ru, + "extra": extra, + "final": final, + "actual_ns": final * byte_period_ns, + } + + fields: dict = {} + + # LPX ≥ 50 ns + fields["lpx"] = _field("lpx", 50.0) + + # HS-PREPARE: 40+4UI to 85+6UI ns + hs_p_min = 40.0 + 4 * ui_ns + hs_p_max = 85.0 + 6 * ui_ns + fields["hs_prepare"] = _field("hs_prepare", hs_p_min, hs_p_max) + hs_p_actual = fields["hs_prepare"]["actual_ns"] + + # HS-ZERO: combined hs_prepare + hs_zero ≥ 145+10UI ns + hs_z_combined_min = 145.0 + 10 * ui_ns + hs_z_min = max(1.0, hs_z_combined_min - hs_p_actual) + fields["hs_zero"] = _field("hs_zero", hs_z_min) + fields["hs_zero"]["combined_min_ns"] = hs_z_combined_min + + # HS-TRAIL ≥ max(8UI, 60+4UI) ns + fields["hs_trail"] = _field("hs_trail", max(8 * ui_ns, 60.0 + 4 * ui_ns)) + + # HS-EXIT ≥ 100 ns + fields["hs_exit"] = _field("hs_exit", 100.0) + + # CLK-PREPARE: 38 to 95 ns + fields["clk_prepare"] = _field("clk_prepare", 38.0, 95.0) + clk_p_actual = fields["clk_prepare"]["actual_ns"] + + # CLK-ZERO: combined clk_prepare + clk_zero ≥ 300 ns + clk_z_combined_min = 300.0 + clk_z_min = max(1.0, clk_z_combined_min - clk_p_actual) + fields["clk_zero"] = _field("clk_zero", clk_z_min) + fields["clk_zero"]["combined_min_ns"] = clk_z_combined_min + + # CLK-POST ≥ 60+52UI ns + fields["clk_post"] = _field("clk_post", 60.0 + 52 * ui_ns) + + # CLK-TRAIL ≥ max(12UI, 60) ns + fields["clk_trail"] = _field("clk_trail", max(12 * ui_ns, 60.0)) + + # ── Register packing ────────────────────────────────────────────────── + def _f(name: str) -> int: + return fields[name]["final"] + + phy_timing = (_f("lpx") << 8) | _f("hs_exit") + phy_timing1 = ((_f("clk_prepare") << 24) | (_f("clk_zero") << 16) | + (_f("clk_post") << 8) | _f("clk_trail")) + phy_timing2 = ((_f("hs_prepare") << 16) | (_f("hs_zero") << 8) | _f("hs_trail")) + + registers = { + "PHY_TIMING": {"addr": 0xb4, "value": phy_timing}, + "PHY_TIMING1": {"addr": 0xb8, "value": phy_timing1}, + "PHY_TIMING2": {"addr": 0xbc, "value": phy_timing2}, + } + + # ── Constraint checks ───────────────────────────────────────────────── + violations: list[str] = [] + + def _chk(name: str, lo: float, hi: float | None = None) -> None: + a = fields[name]["actual_ns"] + if a < lo: + violations.append(f"{name}: {a:.2f} ns < {lo:.2f} ns (min)") + if hi is not None and a > hi: + violations.append(f"{name}: {a:.2f} ns > {hi:.2f} ns (max)") + + _chk("lpx", 50.0) + _chk("hs_prepare", hs_p_min, hs_p_max) + hs_combined = fields["hs_prepare"]["actual_ns"] + fields["hs_zero"]["actual_ns"] + if hs_combined < hs_z_combined_min: + violations.append( + f"hs_prepare+hs_zero: {hs_combined:.2f} ns < {hs_z_combined_min:.2f} ns (min)") + _chk("hs_trail", max(8 * ui_ns, 60.0 + 4 * ui_ns)) + _chk("hs_exit", 100.0) + _chk("clk_prepare", 38.0, 95.0) + clk_combined = fields["clk_prepare"]["actual_ns"] + fields["clk_zero"]["actual_ns"] + if clk_combined < clk_z_combined_min: + violations.append( + f"clk_prepare+clk_zero: {clk_combined:.2f} ns < {clk_z_combined_min:.2f} ns (min)") + _chk("clk_post", 60.0 + 52 * ui_ns) + _chk("clk_trail", max(12 * ui_ns, 60.0)) + + return { + "pixel_clock_mhz": pixel_clock_mhz, + "bit_rate_mbps": bit_rate_mbps, + "byte_clock_mhz": byte_clock_mhz, + "byte_period_ns": byte_period_ns, + "ui_ns": ui_ns, + "fields": fields, + "registers": registers, + "violations": violations, + } + + +def format_timing_table(t: dict) -> str: + """Return a fixed-width console table of all D-PHY timing fields.""" + fields = t["fields"] + hdr = (f"{'Field':<14} {'Spec (ns)':<18} {'Rnd Best':>9} " + f"{'Rnd Up':>7} {'Extra':>6} {'Final':>6} {'Actual (ns)':>12} Status") + sep = "-" * len(hdr) + lines = [sep, hdr, sep] + + for name in _TIMING_FIELD_ORDER: + f = fields[name] + min_ns = f["min_ns"] + max_ns = f.get("max_ns") + actual = f["actual_ns"] + + spec_str = (f"{min_ns:.1f} – {max_ns:.1f}" if max_ns is not None + else f">= {min_ns:.1f}") + + ok = actual >= min_ns + if max_ns is not None and actual > max_ns: + ok = False + if name == "hs_zero": + comb = fields["hs_prepare"]["actual_ns"] + actual + ok = ok and (comb >= f.get("combined_min_ns", 0)) + if name == "clk_zero": + comb = fields["clk_prepare"]["actual_ns"] + actual + ok = ok and (comb >= f.get("combined_min_ns", 0)) + status = "OK" if ok else "FAIL" + + lines.append( + f"{name:<14} {spec_str:<18} {f['round_best']:>9} " + f"{f['round_up']:>7} +{f['extra']:>5} {f['final']:>6} {actual:>12.2f} {status}" + ) + + lines.append(sep) + return "\n".join(lines) + + +def format_uboot_commands(t: dict) -> str: + """Return u-boot shell commands to apply the computed PHY timing configuration.""" + fields = t["fields"] + regs = t["registers"] + fv = {name: fields[name]["final"] for name in _TIMING_FIELD_ORDER} + + phy_t = regs["PHY_TIMING"]["value"] + phy_t1 = regs["PHY_TIMING1"]["value"] + phy_t2 = regs["PHY_TIMING2"]["value"] + + non_zero_extras = [ + (_EXTRA_PROP_MAP[name], fields[name]["extra"]) + for name in _TIMING_FIELD_ORDER + if fields[name]["extra"] > 0 + ] + + lines = [ + f"# D-PHY PHY timing registers " + f"(pixel clock {t['pixel_clock_mhz']} MHz, " + f"{t['bit_rate_mbps']:.1f} Mbit/s, " + f"byte clock {t['byte_clock_mhz']:.3f} MHz)", + "#", + f"# PHY_TIMING (0xb4) = 0x{phy_t:08x} " + f"lpx={fv['lpx']} hs_exit={fv['hs_exit']}", + f"# PHY_TIMING1 (0xb8) = 0x{phy_t1:08x} " + f"clk_prepare={fv['clk_prepare']} clk_zero={fv['clk_zero']} " + f"clk_post={fv['clk_post']} clk_trail={fv['clk_trail']}", + f"# PHY_TIMING2 (0xbc) = 0x{phy_t2:08x} " + f"hs_prepare={fv['hs_prepare']} hs_zero={fv['hs_zero']} " + f"hs_trail={fv['hs_trail']}", + "", + "# Enable Round-Up rounding (dsi-tweak bit 2)", + 'setenv flb_dtovar "${flb_dtovar} dsi-tweak=4"', + ] + + if non_zero_extras: + lines += ["", "# Extra PHY cycles above Round-Up minimum"] + for prop, val in non_zero_extras: + lines.append(f'setenv flb_dtovar "${{flb_dtovar}} {prop}={val}"') + + lines += ["", "saveenv", "boot"] + return "\n".join(lines) + + +def prompt_for_config() -> dict: + """ + Interactive startup prompt: ask for pixel clock and optional extra PHY cycles. + Prints timing table and u-boot commands, then returns a config dict. + """ + print("\n" + "=" * 64) + print(" D-PHY TIMING CONFIGURATION") + print("=" * 64) + print("RGB888, 4 DSI lanes, Samsung DSIM on NXP i.MX 8M Mini.") + print("Timing constraints: MIPI D-PHY v1.1 Table 14.\n") + + # ── Pixel clock ─────────────────────────────────────────────────────── + while True: + try: + pix_str = input("Enter pixel clock in MHz (e.g. 72.0): ").strip() + pixel_clock_mhz = float(pix_str) + if not (10.0 <= pixel_clock_mhz <= 300.0): + print(" Out of range — enter a value between 10 and 300.") + continue + break + except ValueError: + print(" Invalid — enter a number (e.g. 72.0).") + + # ── Baseline (no extras) ────────────────────────────────────────────── + t_base = calculate_dphy_timing(pixel_clock_mhz) + print(f"\nPixel clock : {pixel_clock_mhz} MHz") + print(f"Bit rate : {t_base['bit_rate_mbps']:.1f} Mbit/s per lane") + print(f"Byte clock : {t_base['byte_clock_mhz']:.3f} MHz " + f"({t_base['byte_period_ns']:.3f} ns/byte)") + print(f"UI : {t_base['ui_ns']:.3f} ns\n") + print(format_timing_table(t_base)) + + if t_base["violations"]: + print("\n *** BASELINE VIOLATIONS ***") + for v in t_base["violations"]: + print(f" ! {v}") + else: + print("\n All D-PHY v1.1 Table 14 constraints satisfied at baseline.") + + # ── Extra cycles ────────────────────────────────────────────────────── + print("\n--- Extra PHY cycles (added on top of Round-Up minimum) ---") + print("Suggested values for SN65DSI83 reliability (from dev testing):") + print(" clk_zero +3, hs_prepare +1, hs_trail +1 (others 0)") + print("Press Enter to accept 0 for each field.\n") + + extras: dict[str, int] = {} + for name in _TIMING_FIELD_ORDER: + while True: + try: + val = input(f" Extra cycles for {name:<12s} [0]: ").strip() + extras[name] = int(val) if val else 0 + if extras[name] < 0: + print(" Cannot be negative.") + continue + break + except ValueError: + print(" Enter an integer (or press Enter for 0).") + + # ── Final calculation with extras ───────────────────────────────────── + t_final = calculate_dphy_timing(pixel_clock_mhz, extras) + + if any(v > 0 for v in extras.values()): + print("\n--- Final timing (with extras) ---") + print(format_timing_table(t_final)) + if t_final["violations"]: + print("\n *** VIOLATIONS WITH EXTRAS ***") + for v in t_final["violations"]: + print(f" ! {v}") + else: + print("\n All constraints satisfied.") + + print("\n--- u-boot commands ---") + print(format_uboot_commands(t_final)) + print() + + return { + "pixel_clock_mhz": pixel_clock_mhz, + "extras": extras, + "timing": t_final, + } + +# --------------------------------------------------------------------------- +# Instrument connection +# --------------------------------------------------------------------------- + +try: + psu = vxi11.Instrument(PSU_IP) + scope = vxi11.Instrument(SCOPE_IP) + scope.timeout = 30 + psu.timeout = 5 +except Exception as e: + print(f"ERROR: CANNOT CONNECT TO INSTRUMENTS: {e}") + sys.exit(1) + +rigol_scope.connect() + +# --------------------------------------------------------------------------- +# Scope configuration (identical to mipi_test.py) +# --------------------------------------------------------------------------- + +def setup_scope(): + """Initialises scope for MIPI DSI signals (~210 MHz).""" + print("CONFIGURING SCOPE...") + cmds = [ + "*RST", ":RUN", ":STOP", + # Channel 1 — Clock D+ + ":CHANnel1:DISPlay ON", ":CHANnel1:INPut DC50", ":CHANnel1:PROBe 19.2", + ":CHANnel1:SCALe 0.1", ":CHANnel1:OFFSet 0.0", ":CHANnel1:LABel 'CLK+'", + # Channel 2 — Clock D- + ":CHANnel2:DISPlay ON", ":CHANnel2:INPut DC50", ":CHANnel2:PROBe 19.2", + ":CHANnel2:SCALe 0.1", ":CHANnel2:OFFSet 0.0", ":CHANnel2:LABel 'CLK-'", + # Channel 3 — Data Lane 0 D+ + ":CHANnel3:DISPlay ON", ":CHANnel3:INPut DC50", ":CHANnel3:PROBe 19.2", + ":CHANnel3:SCALe 0.1", ":CHANnel3:OFFSet 0.0", ":CHANnel3:LABel 'DAT0+'", + # Channel 4 — Data Lane 0 D- + ":CHANnel4:DISPlay ON", ":CHANnel4:INPut DC50", ":CHANnel4:PROBe 19.2", + ":CHANnel4:SCALe 0.1", ":CHANnel4:OFFSet 0.0", ":CHANnel4:LABel 'DAT0-'", + # Timebase + ":TIMebase:SCALe 5E-9", ":TIMebase:POSition 0", ":TIMebase:REFerence CENTer", + # Trigger — rising edge on Ch1 (Clock D+) + ":TRIGger:MODE EDGE", ":TRIGger:EDGE:SOURce CHANnel1", + ":TRIGger:EDGE:SLOPe POSitive", ":TRIGger:EDGE:LEVel 0.05", + ":TRIGger:SWEep NORMal", + # Acquisition + ":ACQuire:MODE RTIMe", ":ACQuire:INTerpolate ON", ":ACQuire:POINts 500000", + ":DISPlay:LAYout STACKED", + ":RUN", + ] + for cmd in cmds: + scope.write(cmd) + time.sleep(0.05) + print("CHANNEL SETUP COMPLETE.") + setup_math_channels() + + +def setup_math_channels(): + """F1 = Ch1−Ch2 (clock differential), F2 = Ch3−Ch4 (lane 0 differential).""" + print("SETTING UP MATH CHANNELS...") + scope.write("*CLS") + time.sleep(0.2) + for cmd in [ + ":FUNCtion1:DISPlay ON", ":FUNCtion1:SUBTract CHANnel1,CHANnel2", + ":FUNCtion1:RANGe 0.8", ":FUNCtion1:OFFSet 0.0", + ":FUNCtion2:DISPlay ON", ":FUNCtion2:SUBTract CHANnel3,CHANnel4", + ":FUNCtion2:RANGe 0.8", ":FUNCtion2:OFFSet 0.0", + ]: + scope.write(cmd) + time.sleep(0.2) + try: + time.sleep(1.0) + opc = scope.ask("*OPC?") + print(f" SCOPE SYNC OK (OPC={opc.strip()})") + except Exception as e: + print(f" WARNING: OPC SYNC FAILED ({e})") + try: + err = scope.ask(":SYSTem:ERRor?") + if err.strip().startswith("0"): + print(" MATH COMMANDS ACCEPTED — NO SCPI ERRORS.") + print(" F1 = CLK DIFF (CH1-CH2), F2 = DAT DIFF (CH3-CH4)") + else: + print(f" SCPI ERROR: {err.strip()}") + except Exception as e: + print(f" COULD NOT READ ERROR QUEUE ({e})") + + +def _set_timebase(scale, points): + scope.write(f":TIMebase:SCALe {scale:.3E}") + scope.write(f":ACQuire:POINts {points}") + time.sleep(0.3) + + +def _arm_and_wait(timeout=20): + prev_timeout = scope.timeout + try: + scope.timeout = timeout + 5 + scope.write(":DIGitize") + return scope.ask("*OPC?").strip() == "1" + except Exception as e: + print(f" ACQUIRE ERROR: {e}") + return False + finally: + scope.timeout = prev_timeout + + +def _save_pass(tag, iteration, ts): + """Save F1 (CLK diff) and F2 (DAT diff) as CSV.""" + base = f"C:\\TEMP\\{ts}_{tag}_{iteration:04d}" + try: + scope.write(f':DISK:SAVE:WAVeform FUNCtion1,"{base}_clk.csv",CSV') + time.sleep(2.5) + scope.write(f':DISK:SAVE:WAVeform FUNCtion2,"{base}_dat.csv",CSV') + time.sleep(2.5) + print(f" SAVED: {base}_clk.csv {base}_dat.csv") + except Exception as e: + print(f" SAVE ERROR ({tag}): {e}") + + +def _save_pass_channels(tag, iteration, ts): + """Save Ch1 (CLK+) and Ch3 (DAT0+) single-ended for LP state analysis.""" + base = f"C:\\TEMP\\{ts}_{tag}_{iteration:04d}" + try: + scope.write(f':DISK:SAVE:WAVeform CHANnel1,"{base}_clk.csv",CSV') + time.sleep(2.5) + scope.write(f':DISK:SAVE:WAVeform CHANnel3,"{base}_dat.csv",CSV') + time.sleep(2.5) + print(f" SAVED: {base}_clk.csv {base}_dat.csv") + except Exception as e: + print(f" SAVE ERROR ({tag}): {e}") + + +def _configure_for_lp(): + """Widen vertical range for LP states and switch to falling-edge trigger.""" + 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 CHANnel3") + scope.write(":TRIGger:EDGE:SLOPe NEGative") + scope.write(f":TRIGger:EDGE:LEVel {LP_TRIG_LEVEL:.3f}") + time.sleep(0.1) + + +def _restore_hs_config(): + """Restore HS-mode channel scales and trigger after LP capture.""" + for ch in (1, 2, 3, 4): + scope.write(f":CHANnel{ch}:SCALe 0.1") + scope.write(f":CHANnel{ch}:OFFSet 0.0") + time.sleep(0.05) + scope.write(":TRIGger:EDGE:SOURce CHANnel1") + scope.write(":TRIGger:EDGE:SLOPe POSitive") + scope.write(":TRIGger:EDGE:LEVel 0.05") + time.sleep(0.1) + + +def _fetch_registers(ts: str, iteration: int) -> None: + """GET /registers from device server and save to data/ as JSON.""" + try: + resp = requests.get(f"{DEVICE_BASE}/registers", timeout=5) + resp.raise_for_status() + data = resp.json() + if data.get("errors"): + print(f" REGISTERS: device warnings — {data['errors']}") + DATA_DIR.mkdir(exist_ok=True) + reg_path = DATA_DIR / f"{ts}_reg_{iteration:04d}.json" + reg_path.write_text(json.dumps(data, indent=2)) + print(f" SAVED: {reg_path.name} ({len(data.get('registers', []))} registers)") + except requests.exceptions.RequestException as e: + print(f" REGISTERS: fetch failed — {e}") + except Exception as e: + print(f" REGISTERS: error — {e}") + + +def dual_capture(iteration: int) -> str: + """ + Three-pass capture per test iteration. + + Pass 1 — LP / SoT startup (no settle delay — fires immediately after display ON) + Pass 2 — signal quality (HS differential, rise/fall) + Pass 3 — frame structure (HS differential, jitter/freq) + + Returns the timestamp string used in all filenames for this iteration. + """ + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + print(f"CAPTURE #{iteration:04d} [{ts}]") + + # ── Pass 1: LP / SoT startup ─────────────────────────────────────────── + print(" PASS 1: LP STARTUP TRANSITION...") + _configure_for_lp() + _set_timebase(LP_SCALE, LP_POINTS) + if rigol_scope.is_connected(): + rigol_scope.arm() + if _arm_and_wait(timeout=30): + _save_pass_channels("lp", iteration, ts) + else: + print(" SKIPPING LP SAVE.") + if rigol_scope.is_connected(): + DATA_DIR.mkdir(exist_ok=True) + v18_path = DATA_DIR / f"{ts}_pwr_{iteration:04d}_1v8.csv" + n = rigol_scope.read_waveform_csv(v18_path) + if n: + print(f" SAVED: {v18_path.name} ({n} samples)") + else: + print(" RIGOL: Waveform read failed — check connection and probe.") + _restore_hs_config() + + # ── Pass 2: HS signal quality ────────────────────────────────────────── + print(" PASS 2: SIGNAL QUALITY...") + _set_timebase(SIG_SCALE, SIG_POINTS) + if _arm_and_wait(): + _save_pass("sig", iteration, ts) + else: + print(" SKIPPING SIG SAVE.") + + # ── Pass 3: frame/protocol structure ────────────────────────────────── + print(" PASS 3: FRAME STRUCTURE...") + _set_timebase(PROTO_SCALE, PROTO_POINTS) + if _arm_and_wait(): + _save_pass("proto", iteration, ts) + else: + print(" SKIPPING PROTO SAVE.") + + # ── DSI register snapshot ───────────────────────────────────────────── + _fetch_registers(ts, iteration) + + # ── Restore default timebase ────────────────────────────────────────── + _set_timebase(5e-9, 500_000) + scope.write(":RUN") + return ts + + +# --------------------------------------------------------------------------- +# Per-iteration LP analysis + Claude assessment +# --------------------------------------------------------------------------- + +def _build_system_prompt(config: dict | None = None) -> str: + """Build the Claude system prompt with correct bit-rate values for the active config.""" + if config and "timing" in config: + t = config["timing"] + bit_rate = t["bit_rate_mbps"] + byte_clk = t["byte_clock_mhz"] + byte_per = t["byte_period_ns"] + else: + bit_rate = 432.0 + byte_clk = 54.0 + byte_per = 18.518 + return ( + "You are an expert in MIPI D-PHY signal integrity analysis. " + "You will receive a pre-processed summary of a single LP capture from a MIPI DAT0 lane " + "(NXP i.MX 8M Mini Samsung DSIM IP driving a SN65DSI83 MIPI-to-LVDS bridge). " + f"HS bit rate: {bit_rate:.1f} Mbit/s. " + f"Byte clock: {byte_clk:.3f} MHz ({byte_per:.3f} ns/byte). " + "The LP-low plateau (LP-01/LP-00 SoT preamble) must be ≥ 50 ns for the SN65DSI83 " + "to detect start-of-transmission. A plateau shorter than 50 ns or absent means the " + "bridge missed the SoT and the display will flicker visibly. " + "Be decisive. Answer YES or NO on the first line." + ) + + +def _build_claude_prompt(ts: str, iteration: int, + lp_summaries: list[str], + suspects: list[LPMetrics], + config: dict | None = None) -> str: + """ + Build a concise prompt asking Claude to assess a single capture. + The rule-based pre-filter has already flagged at least one LP suspect. + """ + suspect_lines = "\n".join( + f" channel={m.channel} lp_low_plateau={m.lp_low_duration_ns} ns " + f"(spec ≥ 50 ns) lp11_to_hs={m.lp11_to_hs_ns} ns " + f"lp11_voltage={m.lp11_voltage_v} V " + f"hs_amplitude={m.hs_amplitude_mv} mV (normal 105–122 mV; absent <50 mV)" + for m in suspects + ) + summaries_text = "\n\n".join(lp_summaries) + + config_text = "" + if config and "timing" in config: + t = config["timing"] + config_text = ( + f"\n\nTest configuration: pixel clock {t['pixel_clock_mhz']} MHz, " + f"bit rate {t['bit_rate_mbps']:.1f} Mbit/s per lane, " + f"byte clock {t['byte_clock_mhz']:.3f} MHz " + f"({t['byte_period_ns']:.3f} ns/byte), UI {t['ui_ns']:.3f} ns." + ) + + return ( + f"SINGLE-CAPTURE FLICKER ASSESSMENT — capture {iteration:04d} [{ts}]\n\n" + f"The rule-based LP pre-processor has flagged the following measurements as " + f"potential flicker suspects because the LP-low plateau is absent or shorter " + f"than 50 ns:\n{suspect_lines}\n\n" + f"Full LP capture summaries:\n{summaries_text}" + f"{config_text}\n\n" + f"Based solely on these LP timing metrics, do you believe this capture " + f"represents a genuine screen flicker event — i.e., was the SoT sequence " + f"too brief for the SN65DSI83 bridge to detect start-of-transmission, " + f"likely causing visible display flicker?\n\n" + f"Start your response with YES or NO on the first line, then explain your " + f"reasoning briefly (2–4 sentences) referencing the specific metric values." + ) + + +def _append_flicker_log(ts: str, iteration: int, m: LPMetrics) -> None: + """Append a flicker suspect to the shared flicker_log.csv.""" + FLICKER_LOG.parent.mkdir(exist_ok=True) + write_header = not FLICKER_LOG.exists() + with open(FLICKER_LOG, "a", newline="", encoding="utf-8") as f: + w = _csv_mod.writer(f) + if write_header: + w.writerow(["logged_at", "capture_ts", "capture_num", "channel", + "lp_low_duration_ns", "lp11_to_hs_ns", "lp11_voltage_v"]) + w.writerow([ + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ts, f"{iteration:04d}", m.channel, + m.lp_low_duration_ns, m.lp11_to_hs_ns, m.lp11_voltage_v, + ]) + + +def analyze_lp_and_ask_claude( + ts: str, iteration: int, config: dict | None = None +) -> tuple[bool, str, list[LPMetrics]]: + """ + Analyse the LP files for this iteration. + + 1. Run csv_preprocessor on lp_clk and lp_dat. + 2. If any file is flagged as a flicker suspect by the rule-based detector, + call the Claude API for a focused single-capture assessment. + 3. Parse Claude's YES/NO response. + + Returns: + claude_says_flicker — True if Claude opened with YES + reasoning — Claude's full response text (or "" if not called) + suspects — list of LPMetrics objects that were flagged + """ + lp_summaries: list[str] = [] + suspects: list[LPMetrics] = [] + + for channel in ("clk", "dat"): + path = DATA_DIR / f"{ts}_lp_{iteration:04d}_{channel}.csv" + if not path.exists(): + print(f" LP ANALYSIS: {path.name} not found — skipping.") + continue + try: + m = analyze_lp_file(path) + lp_summaries.append(m.summary()) + if m.flicker_suspect: + suspects.append(m) + _append_flicker_log(ts, iteration, m) + if (m.hs_amplitude_mv is not None + and m.hs_amplitude_mv < HS_BURST_AMPLITUDE_MIN_MV + and (m.lp_low_duration_ns is None + or m.lp_low_duration_ns >= FLICKER_LP_LOW_MAX_NS)): + reason = f"HS burst absent ({m.hs_amplitude_mv} mV)" + else: + reason = f"lp_low={m.lp_low_duration_ns} ns" + print(f"\n *** FLICKER SUSPECT: capture {iteration:04d} " + f"channel={m.channel} {reason} ***\n") + except Exception as e: + print(f" LP ANALYSIS ERROR ({channel}): {e}") + + if not suspects: + return False, "", [] + + # ── Call Claude ──────────────────────────────────────────────────────── + print(" CALLING CLAUDE FOR ASSESSMENT...") + try: + client = anthropic.Anthropic() + message = client.messages.create( + model = CLAUDE_MODEL, + max_tokens = 512, + system = _build_system_prompt(config), + messages = [{"role": "user", "content": + _build_claude_prompt(ts, iteration, lp_summaries, + suspects, config)}], + ) + response = message.content[0].text.strip() + # Parse YES/NO from the first line + first_line = response.splitlines()[0].strip().upper() + claude_says_flicker = first_line.startswith("YES") + label = "FLICKER" if claude_says_flicker else "NOT FLICKER" + print(f" CLAUDE: {label} ({message.usage.input_tokens} in / " + f"{message.usage.output_tokens} out tokens)") + return claude_says_flicker, response, suspects + except Exception as e: + print(f" CLAUDE API ERROR: {e}") + # Fall back to the rule-based result so the operator still gets asked + fallback = (f"(Claude API unavailable: {e})\n" + f"Rule-based detector flagged LP-low plateau < 50 ns — " + f"treat as potential flicker suspect.") + return True, fallback, suspects + + +# --------------------------------------------------------------------------- +# Event logging and HTML report +# --------------------------------------------------------------------------- + +def _config_section_html(config: dict) -> str: + """Generate the D-PHY configuration section for the HTML report.""" + t = config["timing"] + fields = t["fields"] + regs = t["registers"] + + rows_html = "" + for name in _TIMING_FIELD_ORDER: + f = fields[name] + min_ns = f["min_ns"] + max_ns = f.get("max_ns") + actual = f["actual_ns"] + + ok = actual >= min_ns + if max_ns is not None and actual > max_ns: + ok = False + if name == "hs_zero": + comb = fields["hs_prepare"]["actual_ns"] + actual + ok = ok and (comb >= f.get("combined_min_ns", 0)) + if name == "clk_zero": + comb = fields["clk_prepare"]["actual_ns"] + actual + ok = ok and (comb >= f.get("combined_min_ns", 0)) + + spec_str = (f"{min_ns:.1f} – {max_ns:.1f}" if max_ns is not None + else f"≥ {min_ns:.1f}") + cell_style = "" if ok else ' style="color:#c62828;font-weight:bold"' + status_html = "✓" if ok else '✖ FAIL' + + rows_html += ( + f"" + f"{name}" + f"{spec_str}" + f"{f['round_best']}" + f"{f['round_up']}" + f"+{f['extra']}" + f"{f['final']}" + f"{actual:.2f}" + f"{status_html}" + f"\n" + ) + + if t["violations"]: + items_html = "".join(f"
  • {html.escape(v)}
  • " for v in t["violations"]) + viol_html = ( + f'

    Timing violations:

    ' + f"" + ) + else: + viol_html = ( + '

    ✓ All D-PHY v1.1 Table 14 ' + "constraints satisfied.

    " + ) + + fv = {name: fields[name]["final"] for name in _TIMING_FIELD_ORDER} + phy_t = regs["PHY_TIMING"]["value"] + phy_t1 = regs["PHY_TIMING1"]["value"] + phy_t2 = regs["PHY_TIMING2"]["value"] + uboot = html.escape(format_uboot_commands(t)) + + return f"""

    D-PHY Configuration

    +

    + Pixel clock: {t['pixel_clock_mhz']} MHz  |  + Bit rate: {t['bit_rate_mbps']:.1f} Mbit/s per lane  |  + Byte clock: {t['byte_clock_mhz']:.3f} MHz + ({t['byte_period_ns']:.3f} ns/byte)  |  + UI: {t['ui_ns']:.3f} ns +

    + + + + + + + {rows_html} +
    FieldSpec (ns)Rnd BestRnd UpExtraFinalActual (ns)Status
    + +{viol_html} + +

    Samsung DSIM Registers

    + + + + + + + + + + + + + + + + + +
    RegisterAddressValueField breakdown
    PHY_TIMING0xb40x{phy_t:08x}lpx={fv['lpx']}   hs_exit={fv['hs_exit']}
    PHY_TIMING10xb80x{phy_t1:08x}clk_prepare={fv['clk_prepare']}   clk_zero={fv['clk_zero']}   + clk_post={fv['clk_post']}   clk_trail={fv['clk_trail']}
    PHY_TIMING20xbc0x{phy_t2:08x}hs_prepare={fv['hs_prepare']}   hs_zero={fv['hs_zero']}   + hs_trail={fv['hs_trail']}
    + +

    u-boot Commands

    +
    {uboot}
    +""" + +def _log_interaction( + events: list, + ts: str, + iteration: int, + suspects: list[LPMetrics], + claude_said: bool, + user_confirmed: bool | None, + reasoning: str, +) -> None: + """ + Append an event to the in-memory list and to interactive_log.csv. + + user_confirmed values: + True — operator confirmed flicker + False — operator said no (false alarm) + None — operator was not asked (Claude said no) + """ + now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + events.append({ + "ts": ts, + "iteration": iteration, + "suspects": suspects, + "claude_said": claude_said, + "user_confirmed": user_confirmed, + "reasoning": reasoning, + "logged_at": now_str, + }) + + INTERACTIVE_LOG.parent.mkdir(exist_ok=True) + write_header = not INTERACTIVE_LOG.exists() + with open(INTERACTIVE_LOG, "a", newline="", encoding="utf-8") as f: + w = _csv_mod.writer(f) + if write_header: + w.writerow(["logged_at", "capture_ts", "capture_num", + "claude_said_flicker", "user_confirmed", + "lp_low_ns", "reasoning_summary"]) + for m in suspects: + w.writerow([ + now_str, ts, f"{iteration:04d}", + "YES" if claude_said else "NO", + ("YES" if user_confirmed is True else + "NO" if user_confirmed is False else + "NOT_ASKED"), + m.lp_low_duration_ns, + reasoning[:150].replace("\n", " "), + ]) + + +def save_report(events: list, stop_reason: str, + config: dict | None = None) -> Path: + """Write a timestamped HTML report summarising all flicker interactions.""" + REPORTS_DIR.mkdir(exist_ok=True) + now = datetime.now() + filename = now.strftime("%Y%m%d_%H%M%S_interactive.html") + path = REPORTS_DIR / filename + + confirmed_n = sum(1 for e in events if e["user_confirmed"] is True) + false_alarm_n = sum(1 for e in events if e["user_confirmed"] is False) + claude_no_n = sum(1 for e in events if e["user_confirmed"] is None) + + # ── Event table rows ─────────────────────────────────────────────────── + rows_html = "" + for e in events: + for m in e["suspects"]: + conf = e["user_confirmed"] + if conf is True: + badge = ('' + '✖ CONFIRMED FLICKER') + elif conf is False: + badge = ('' + '✓ FALSE ALARM') + else: + badge = ('' + 'Claude said NO — user not asked') + + lp_val = m.lp_low_duration_ns + lp_bad = lp_val is None or lp_val < 50 + lp_cell = (f'{lp_val} ns' if lp_bad + else f'{lp_val} ns') + + rows_html += ( + f"" + f"{e['iteration']:04d}" + f"{e['ts']}" + f"{m.channel}" + f"{lp_cell}" + f"{m.lp11_to_hs_ns} ns" + f"{m.lp11_voltage_v} V" + f"{'YES' if e['claude_said'] else 'NO'}" + f"{badge}" + f"" + ) + + table_html = ( + '

    No flicker suspects were detected during this test run.

    ' + if not rows_html else + f""" + + + + + + {rows_html} +
    CaptureTimestampChannelLP-low plateauLP exit→HSLP-11 voltageClaude: flicker?Outcome
    """ + ) + + # ── D-PHY config section ─────────────────────────────────────────────── + config_html = _config_section_html(config) if config else "" + + # ── Claude reasoning sections ────────────────────────────────────────── + reasoning_html = "" + for e in events: + if not e["reasoning"] or not e["claude_said"]: + continue + conf = e["user_confirmed"] + label = (" — CONFIRMED FLICKER" if conf is True else + " — FALSE ALARM" if conf is False else "") + reasoning_html += ( + f'

    Capture {e["iteration"]:04d} [{e["ts"]}]{html.escape(label)}

    ' + f'
    '
    +            f'{html.escape(e["reasoning"])}
    ' + ) + + html_content = f""" + + + +MIPI Interactive Flicker Test — {now.strftime('%Y-%m-%d %H:%M:%S')} + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: {now.strftime('%Y-%m-%d %H:%M:%S')}  |  + Model: {CLAUDE_MODEL} +

    + +
    + Stop reason: {html.escape(stop_reason)} +
    + +
    +
    {confirmed_n} confirmed flicker(s)
    +
    {false_alarm_n} false alarm(s)
    +
    {claude_no_n} Claude said no
    +
    + +{config_html} +

    Event Log

    +{table_html} + +{'

    Claude Assessments

    ' + reasoning_html if reasoning_html else ''} + + + +""" + path.write_text(html_content, encoding="utf-8") + return path + + +# --------------------------------------------------------------------------- +# Interactive test loop +# --------------------------------------------------------------------------- + +def run_interactive_test() -> None: + """ + Blocking interactive test loop (runs in the calling thread — no background threads). + + Flow per iteration: + 1. Display ON + 2. Three-pass dual_capture → saves LP/sig/proto CSVs to scope + 3. Transfer all scope CSVs to local data/ + 4. Analyse LP files for this iteration (rule-based + Claude if suspect) + 5. If Claude says flicker: + - Keep display ON so operator can observe + - Ask operator to confirm + - Confirmed → log event, save report, STOP + - False alarm → log event, continue + 6. Display OFF, 1 s pause, next iteration + + Press Ctrl+C to exit at any time. A report is always saved on exit. + """ + iteration = 1 + events: list = [] + stop_reason = "Test stopped by user" + + # ── Pixel clock configuration ────────────────────────────────────────── + config = prompt_for_config() + + print("\nINTERACTIVE FLICKER TEST STARTED.") + print("Each iteration: display ON → 3-pass capture → LP analysis → Claude check.") + print("The display stays ON while Claude and the operator assess the frame.") + print("Press Ctrl+C at any time to stop.\n") + + try: + while True: + # ── Display ON ───────────────────────────────────────────────── + try: + requests.put(URL, json={"state": "on"}, timeout=2) + except requests.exceptions.RequestException as e: + print(f" WARNING: display ON failed: {e}") + + # ── Three-pass capture ───────────────────────────────────────── + ts = dual_capture(iteration) + + # ── Transfer scope files to local data/ ──────────────────────── + print(" TRANSFERRING FILES FROM SCOPE...") + try: + copied, failed = ai_mgmt.transfer_csv_files() + print(f" TRANSFERRED {copied} FILE(S). {failed} FAILED.") + except Exception as e: + print(f" TRANSFER ERROR: {e}") + + # ── Analyse LP + ask Claude if suspect ───────────────────────── + claude_flicker, reasoning, suspects = analyze_lp_and_ask_claude( + ts, iteration, config) + + if claude_flicker: + # ── Keep display ON — ask operator ───────────────────────── + # Play alarm sound once to alert the operator + subprocess.run( + ["pw-play", "/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga"], + check=False, + ) + print("\n" + "=" * 64) + print(" CLAUDE SUSPECTS FLICKER — OBSERVE THE DISPLAY NOW") + print("=" * 64) + print(f"\n{reasoning}\n") + + confirmed: bool | None = None + while confirmed is None: + try: + ans = input("IS THE SCREEN ACTUALLY FLICKERING? (Y/N): ").strip().upper() + except EOFError: + ans = "N" + if ans in ("Y", "YES"): + confirmed = True + elif ans in ("N", "NO"): + confirmed = False + else: + print(" Please enter Y or N.") + + _log_interaction(events, ts, iteration, suspects, + claude_flicker, confirmed, reasoning) + + if confirmed: + print("\n FLICKER CONFIRMED BY OPERATOR. STOPPING TEST.") + stop_reason = (f"Flicker confirmed by operator at " + f"capture {iteration:04d} [{ts}]") + # Display OFF + try: + requests.put(URL, json={"state": "off"}, timeout=2) + except Exception: + pass + break # exit the while loop → save report below + + else: + print(" NOT FLICKERING — false alarm logged. Continuing.\n") + + elif suspects: + # Rule-based suspect but Claude said no — record for reference + _log_interaction(events, ts, iteration, suspects, + False, None, reasoning) + + # ── Display OFF, brief pause before next iteration ───────────── + try: + requests.put(URL, json={"state": "off"}, timeout=2) + except requests.exceptions.RequestException as e: + print(f" WARNING: display OFF failed: {e}") + + iteration += 1 + time.sleep(1.0) + + except KeyboardInterrupt: + print("\n\n TEST INTERRUPTED (Ctrl+C).") + stop_reason = "Test interrupted by operator (Ctrl+C)" + try: + requests.put(URL, json={"state": "off"}, timeout=2) + except Exception: + pass + + # ── Always save a report on exit ─────────────────────────────────────── + report_path = save_report(events, stop_reason, config) + print(f"\nREPORT SAVED: {report_path}") + if events: + confirmed_n = sum(1 for e in events if e["user_confirmed"] is True) + false_alarm_n = sum(1 for e in events if e["user_confirmed"] is False) + print(f" {confirmed_n} confirmed flicker(s), {false_alarm_n} false alarm(s) " + f"({len(events)} total suspect(s) assessed)") + + +# --------------------------------------------------------------------------- +# Menu +# --------------------------------------------------------------------------- + +def main_menu() -> None: + while True: + print("\n===== MIPI INTERACTIVE TEST CONTROL =====") + print("1. RUN IDN CHECK (PSU & SCOPE)") + print("2. SETUP SCOPE (RUN FIRST)") + print("3. CONFIGURE PSU (DEFAULT 24V / 1.5A)") + print("4. PSU OUTPUT ON/OFF (CH1)") + print("5. START INTERACTIVE FLICKER TEST") + print("6. EXIT") + + choice = input("\nSELECT OPTION (1-6): ").strip() + + if choice == '1': + print(f"PSU : {psu.ask('*IDN?').strip()}") + print(f"SCOPE: {scope.ask('*IDN?').strip()}") + if rigol_scope.is_connected(): + print(f"RIGOL: {rigol_scope.rigol.ask('*IDN?').strip()}") + else: + print("RIGOL: NOT CONNECTED") + + elif choice == '2': + setup_scope() + if rigol_scope.is_connected(): + rigol_scope.configure() + + elif choice == '3': + psu.write('CH1:VOLT 24.0') + psu.write('CH1:CURR 1.5') + print("PSU CONFIGURED: 24V / 1.5A") + + elif choice == '4': + state = input("TYPE 'ON' OR 'OFF': ").strip().upper() + if state in ('ON', 'OFF'): + psu.write(f'OUTP CH1,{state}') + print(f"PSU OUTPUT {state}.") + else: + print("INVALID — TYPE 'ON' OR 'OFF'.") + + elif choice == '5': + run_interactive_test() + + elif choice == '6': + psu.close() + scope.close() + rigol_scope.disconnect() + print("INSTRUMENTS CLOSED. BYE.") + break + + else: + print("INVALID ENTRY. PLEASE CHOOSE 1-6.") + + +if __name__ == "__main__": + main_menu() diff --git a/reports/20260416_074555_interactive.html b/reports/20260416_074555_interactive.html new file mode 100644 index 0000000..d96e04f --- /dev/null +++ b/reports/20260416_074555_interactive.html @@ -0,0 +1,51 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 07:45:55 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 07:45:55  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Test interrupted by operator (Ctrl+C) +
    + +
    +
    0 confirmed flicker(s)
    +
    0 false alarm(s)
    +
    0 Claude said no
    +
    + +

    Event Log

    +

    No flicker suspects were detected during this test run.

    + + + + + diff --git a/reports/20260416_080226_interactive.html b/reports/20260416_080226_interactive.html new file mode 100644 index 0000000..a0ad2a2 --- /dev/null +++ b/reports/20260416_080226_interactive.html @@ -0,0 +1,60 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 08:02:26 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 08:02:26  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Test interrupted by operator (Ctrl+C) +
    + +
    +
    0 confirmed flicker(s)
    +
    1 false alarm(s)
    +
    0 Claude said no
    +
    + +

    Event Log

    + + + + + + + +
    CaptureTimestampChannelLP-low plateauLP exit→HSLP-11 voltageClaude: flicker?Outcome
    003120260416_075857dat0.3 ns2.4 ns1.015 VYES✓ FALSE ALARM
    + +

    Claude Assessments

    Capture 0031 [20260416_075857] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at 0.3 ns is effectively absent — nearly three orders of magnitude below the SN65DSI83's required ≥ 50 ns minimum for SoT detection. The LP exit-to-HS transition of only 2.4 ns further confirms the LP-01/LP-00 preamble states were never properly established, meaning the bridge had no opportunity to recognize the start-of-transmission. Combined with the unusually low HS amplitude of 32 mV (suggesting the bridge may not have properly locked onto the HS data), this capture is a textbook flicker event where the SN65DSI83 missed the SoT entirely.
    + + + diff --git a/reports/20260416_081326_interactive.html b/reports/20260416_081326_interactive.html new file mode 100644 index 0000000..0649c73 --- /dev/null +++ b/reports/20260416_081326_interactive.html @@ -0,0 +1,62 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 08:13:26 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 08:13:26  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Flicker confirmed by operator at capture 0013 [20260416_081232] +
    + +
    +
    1 confirmed flicker(s)
    +
    1 false alarm(s)
    +
    0 Claude said no
    +
    + +

    Event Log

    + + + + + + + +
    CaptureTimestampChannelLP-low plateauLP exit→HSLP-11 voltageClaude: flicker?Outcome
    000620260416_080919dat0.3 ns2.4 ns1.014 VYES✓ FALSE ALARM
    001320260416_081232dat0.3 ns2.9 ns1.014 VYES✖ CONFIRMED FLICKER
    + +

    Claude Assessments

    Capture 0006 [20260416_080919] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at 0.3 ns is essentially absent, far below the 50 ns minimum required by the SN65DSI83 to detect the start-of-transmission. The LP exit-to-HS transition of only 2.4 ns confirms that the LP-01/LP-00 preamble states were either skipped or collapsed to a duration the bridge cannot resolve. Without a valid SoT detection, the bridge will fail to synchronize to the incoming HS burst, causing the display to miss that frame's data and produce visible flicker.

    Capture 0013 [20260416_081232] — CONFIRMED FLICKER

    YES
    +
    +The LP-low plateau is measured at effectively 0 ns (0.3 ns reported, rounded to 0 ns in the summary), which is drastically below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection. The LP exit-to-HS transition of only 3 ns confirms that the LP-01/LP-00 preamble states were either skipped entirely or collapsed to sub-nanosecond glitches, far too brief for the bridge's LP receiver to recognize the start-of-transmission sequence. With the bridge unable to lock onto the SoT, it will miss the subsequent HS burst (the single 5072 ns burst present), resulting in a dropped frame and visible flicker on the display.
    + + + diff --git a/reports/20260416_081559_interactive.html b/reports/20260416_081559_interactive.html new file mode 100644 index 0000000..e9a404a --- /dev/null +++ b/reports/20260416_081559_interactive.html @@ -0,0 +1,51 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 08:15:59 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 08:15:59  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Test interrupted by operator (Ctrl+C) +
    + +
    +
    0 confirmed flicker(s)
    +
    0 false alarm(s)
    +
    0 Claude said no
    +
    + +

    Event Log

    +

    No flicker suspects were detected during this test run.

    + + + + + diff --git a/reports/20260416_082218_interactive.html b/reports/20260416_082218_interactive.html new file mode 100644 index 0000000..1babd7d --- /dev/null +++ b/reports/20260416_082218_interactive.html @@ -0,0 +1,58 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 08:22:18 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 08:22:18  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Test interrupted by operator (Ctrl+C) +
    + +
    +
    0 confirmed flicker(s)
    +
    0 false alarm(s)
    +
    4 Claude said no
    +
    + +

    Event Log

    + + + + + + + +
    CaptureTimestampChannelLP-low plateauLP exit→HSLP-11 voltageClaude: flicker?Outcome
    000120260416_082018dat108.0 ns3.1 ns1.015 VNOClaude said NO — user not asked
    000220260416_082047dat108.3 ns3.5 ns1.015 VNOClaude said NO — user not asked
    000320260416_082117dat342.7 ns3.6 ns1.015 VNOClaude said NO — user not asked
    000420260416_082147dat342.7 ns4.0 ns1.015 VNOClaude said NO — user not asked
    + + + + + diff --git a/reports/20260416_083300_interactive.html b/reports/20260416_083300_interactive.html new file mode 100644 index 0000000..1dd51c9 --- /dev/null +++ b/reports/20260416_083300_interactive.html @@ -0,0 +1,62 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 08:33:00 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 08:33:00  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Test interrupted by operator (Ctrl+C) +
    + +
    +
    0 confirmed flicker(s)
    +
    2 false alarm(s)
    +
    0 Claude said no
    +
    + +

    Event Log

    + + + + + + + +
    CaptureTimestampChannelLP-low plateauLP exit→HSLP-11 voltageClaude: flicker?Outcome
    000520260416_082936dat0.3 ns2.8 ns1.015 VYES✓ FALSE ALARM
    001020260416_083201dat0.3 ns348.0 ns1.015 VYES✓ FALSE ALARM
    + +

    Claude Assessments

    Capture 0005 [20260416_082936] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at effectively 0 ns (reported as 0.3 ns by the flag, 0 ns in the full summary) is drastically below the 50 ns minimum required by the SN65DSI83 to detect the SoT preamble. The LP exit-to-HS transition of only 3 ns confirms that the LP-01/LP-00 states were essentially skipped entirely, meaning the bridge had no opportunity to recognize the start-of-transmission sequence. With the SoT undetectable, the bridge would fail to synchronize to the incoming HS burst, resulting in a missed video frame and visible flicker.

    Capture 0010 [20260416_083201] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured only 0.3 ns, which is effectively absent and far below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection. Without a properly formed LP-01/LP-00 preamble, the bridge cannot recognize the start-of-transmission, causing it to miss the incoming HS burst entirely. Although the LP-11 voltage (1.015 V) and overall LP-exit-to-HS timing (348 ns) are within spec, the critical SoT signaling is fundamentally broken in this capture, making visible display flicker virtually certain.
    + + + diff --git a/reports/20260416_084133_interactive.html b/reports/20260416_084133_interactive.html new file mode 100644 index 0000000..39baaf8 --- /dev/null +++ b/reports/20260416_084133_interactive.html @@ -0,0 +1,60 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 08:41:33 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 08:41:33  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Flicker confirmed by operator at capture 0009 [20260416_084055] +
    + +
    +
    1 confirmed flicker(s)
    +
    0 false alarm(s)
    +
    0 Claude said no
    +
    + +

    Event Log

    + + + + + + + +
    CaptureTimestampChannelLP-low plateauLP exit→HSLP-11 voltageClaude: flicker?Outcome
    000920260416_084055dat0.2 ns3.5 ns1.016 VYES✖ CONFIRMED FLICKER
    + +

    Claude Assessments

    Capture 0009 [20260416_084055] — CONFIRMED FLICKER

    YES
    +
    +The LP-low plateau is measured at effectively 0 ns (reported as 0.2 ns by the pre-processor, 0 ns in the full summary), far below the 50 ns minimum required by the SN65DSI83 to detect the SoT preamble. The LP exit-to-HS transition of only 3.5–4 ns confirms that the LP-01/LP-00 states were either skipped or collapsed to a duration undetectable by the bridge's LP receiver. Without a valid SoT detection, the bridge will fail to synchronize to the incoming HS burst, causing the display to miss that video frame and produce visible flicker.
    + + + diff --git a/reports/20260416_102800_interactive.html b/reports/20260416_102800_interactive.html new file mode 100644 index 0000000..5fea268 --- /dev/null +++ b/reports/20260416_102800_interactive.html @@ -0,0 +1,154 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 10:28:00 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 10:28:00  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Test interrupted by operator (Ctrl+C) +
    + +
    +
    0 confirmed flicker(s)
    +
    16 false alarm(s)
    +
    0 Claude said no
    +
    + +

    D-PHY Configuration

    +

    + Pixel clock: 72.0 MHz  |  + Bit rate: 432.0 Mbit/s per lane  |  + Byte clock: 54.000 MHz + (18.519 ns/byte)  |  + UI: 2.315 ns +

    + + + + + + + + + + + + + + + + +
    FieldSpec (ns)Rnd BestRnd UpExtraFinalActual (ns)Status
    lpx≥ 50.033+0355.56
    hs_prepare49.3 – 98.933+0355.56
    hs_zero≥ 112.667+07129.63
    hs_trail≥ 69.344+0474.07
    hs_exit≥ 100.056+06111.11
    clk_prepare38.0 – 95.023+0355.56
    clk_zero≥ 244.41314+014259.26
    clk_post≥ 180.41010+010185.19
    clk_trail≥ 60.034+0474.07
    + +

    ✓ All D-PHY v1.1 Table 14 constraints satisfied.

    + +

    Samsung DSIM Registers

    + + + + + + + + + + + + + + + + + +
    RegisterAddressValueField breakdown
    PHY_TIMING0xb40x00000306lpx=3   hs_exit=6
    PHY_TIMING10xb80x030e0a04clk_prepare=3   clk_zero=14   + clk_post=10   clk_trail=4
    PHY_TIMING20xbc0x00030704hs_prepare=3   hs_zero=7   + hs_trail=4
    + +

    u-boot Commands

    +
    # D-PHY PHY timing registers (pixel clock 72.0 MHz, 432.0 Mbit/s, byte clock 54.000 MHz)
    +#
    +# PHY_TIMING  (0xb4) = 0x00000306   lpx=3  hs_exit=6
    +# PHY_TIMING1 (0xb8) = 0x030e0a04   clk_prepare=3  clk_zero=14  clk_post=10  clk_trail=4
    +# PHY_TIMING2 (0xbc) = 0x00030704   hs_prepare=3  hs_zero=7  hs_trail=4
    +
    +# Enable Round-Up rounding (dsi-tweak bit 2)
    +setenv flb_dtovar "${flb_dtovar} dsi-tweak=4"
    +
    +saveenv
    +boot
    + +

    Event Log

    + + + + + + + +
    CaptureTimestampChannelLP-low plateauLP exit→HSLP-11 voltageClaude: flicker?Outcome
    000220260416_091714dat0.3 ns1.4 ns1.016 VYES✓ FALSE ALARM
    002920260416_092745dat0.3 ns1.8 ns1.017 VYES✓ FALSE ALARM
    004120260416_093239dat0.3 ns1.9 ns1.015 VYES✓ FALSE ALARM
    005220260416_093705dat0.3 ns2.6 ns1.015 VYES✓ FALSE ALARM
    009120260416_095213dat0.2 ns1.9 ns1.015 VYES✓ FALSE ALARM
    009320260416_095313dat0.2 ns0.6 ns1.015 VYES✓ FALSE ALARM
    009520260416_095412dat0.3 ns1.3 ns1.016 VYES✓ FALSE ALARM
    010520260416_095814dat0.9 ns0.8 ns1.015 VYES✓ FALSE ALARM
    012420260416_100542dat0.3 ns3.5 ns1.015 VYES✓ FALSE ALARM
    013520260416_101007dat23.1 ns1.2 ns1.016 VYES✓ FALSE ALARM
    013920260416_101154dat0.2 ns0.1 ns1.015 VYES✓ FALSE ALARM
    014420260416_101402dat0.2 ns0.1 ns1.015 VYES✓ FALSE ALARM
    014520260416_101439dat0.2 ns3.6 ns1.016 VYES✓ FALSE ALARM
    016020260416_102036dat39.8 ns0.1 ns1.016 VYES✓ FALSE ALARM
    017020260416_102440dat0.3 ns0.8 ns1.015 VYES✓ FALSE ALARM
    017620260416_102713dat0.9 ns0.1 ns1.016 VYES✓ FALSE ALARM
    + +

    Claude Assessments

    Capture 0002 [20260416_091714] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at effectively 0 ns (reported 0.3 ns) is drastically below the SN65DSI83's required ≥ 50 ns minimum for SoT detection. The LP exit-to-HS transition of only 1 ns confirms that the LP-01/LP-00 preamble states are essentially absent, meaning the bridge has no opportunity to recognize the start-of-transmission sequence. With these timing values, the SN65DSI83 will almost certainly miss the HS entry, resulting in a lost or corrupted video frame and visible display flicker.

    Capture 0029 [20260416_092745] — FALSE ALARM

    YES
    +
    +The LP-low plateau of 0.3 ns is essentially absent—two orders of magnitude below the SN65DSI83's required ≥ 50 ns minimum for SoT detection. The LP exit-to-HS transition of only 2 ns (also far below the 50 ns spec) confirms that the LP-01/LP-00 preamble states were either skipped or collapsed into a sub-UI glitch, making it impossible for the bridge's LP receiver to recognize the start-of-transmission. Additionally, the HS single-ended amplitude of 31 mV is anomalously low, suggesting the bridge likely failed to lock onto the HS data burst entirely, which would produce a dropped or corrupted frame and visible flicker.

    Capture 0041 [20260416_093239] — FALSE ALARM

    YES
    +
    +The LP-low plateau is measured at effectively 0 ns (flagged as 0.3 ns by the pre-processor), far below the SN65DSI83's 50 ns minimum requirement for SoT detection. The LP exit-to-HS transition of only 2 ns confirms that the LP-01/LP-00 preamble states were essentially skipped, meaning the bridge had no opportunity to recognize the start-of-transmission. With the HS amplitude also anomalously low at 32 mV (suggesting the bridge may not have properly locked onto the HS data), this capture is a clear flicker event where the SN65DSI83 missed the SoT and failed to decode the subsequent HS burst.

    Capture 0052 [20260416_093705] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at 0.3 ns is effectively absent — it is over two orders of magnitude below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection. The total LP exit-to-HS transition of only 3 ns (versus the ≥ 50 ns spec) confirms that the LP-01/LP-00 preamble states were essentially skipped, meaning the bridge almost certainly failed to recognize the start-of-transmission. This is a textbook flicker-inducing condition: without a properly timed SoT sequence, the SN65DSI83 cannot synchronize to the incoming HS burst, resulting in a missed or corrupted video frame and visible display flicker.

    Capture 0091 [20260416_095213] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at 0.2 ns is essentially absent — nearly three orders of magnitude below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection. The LP exit-to-HS transition of only 2 ns (vs. the 50 ns spec minimum) confirms that the LP-01/LP-00 preamble states were either skipped or collapsed to sub-UI durations, meaning the bridge almost certainly missed the start-of-transmission. With the SoT undetected, the bridge would fail to deserialize the subsequent HS burst (~5012 ns), resulting in a dropped or corrupted video line and visible flicker on the display.

    Capture 0093 [20260416_095313] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at effectively 0 ns (reported 0.2 ns) is vastly below the 50 ns minimum required by the SN65DSI83 to detect the Start-of-Transmission sequence. The LP exit-to-HS transition of only 1 ns (vs. the ≥50 ns spec) confirms that the LP-01/LP-00 preamble states were essentially absent, meaning the bridge had no opportunity to recognize the SoT entry. With the receiver unable to lock onto the HS burst, this capture almost certainly resulted in a missed packet and visible display flicker.

    Capture 0095 [20260416_095412] — FALSE ALARM

    YES
    +
    +The LP-low plateau is measured at effectively 0 ns (reported as 0.3 ns by the flag, 0 ns in the full summary), which is drastically below the SN65DSI83's required ≥ 50 ns minimum for SoT detection. The LP exit-to-HS transition of only 1–1.3 ns confirms that the LP-01/LP-00 preamble states are essentially absent, meaning the bridge has no opportunity to recognize the start-of-transmission sequence. Additionally, the HS single-ended amplitude of only 26 mV is abnormally low (typical is ~100–200 mV), further suggesting the bridge would fail to lock onto the HS data, compounding the flicker risk. This capture is a textbook flicker event: the transmitter is skipping or compressing the LP-to-HS entry sequence far below what the SN65DSI83 requires.

    Capture 0105 [20260416_095814] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at only ~1 ns, which is drastically below the SN65DSI83's required ≥50 ns minimum for reliable SoT detection — falling short by nearly two orders of magnitude. The LP exit-to-HS transition of just 1 ns confirms that the LP-01/LP-00 preamble states were essentially absent or collapsed into a sub-UI glitch, making it impossible for the bridge's LP receiver to recognize the start-of-transmission sequence. With the bridge unable to synchronize to the incoming HS burst, the corresponding video data would be lost, producing visible flicker on the display.

    Capture 0124 [20260416_100542] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at effectively 0 ns (reported as 0.3 ns by the pre-processor, 0 ns in the full summary) is drastically below the 50 ns minimum required by the SN65DSI83 to detect the Start-of-Transmission sequence. The LP exit-to-HS transition of only 4 ns confirms that the LP-01/LP-00 preamble states were essentially skipped, giving the bridge no opportunity to recognize the SoT and synchronize to the incoming HS burst. With the receiver unable to lock onto the data, this capture almost certainly resulted in a missed frame and visible display flicker.

    Capture 0135 [20260416_101007] — FALSE ALARM

    YES
    +
    +The LP-low plateau of 23.1 ns is less than half the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection, and the LP exit-to-HS transition of only 1.2 ns is drastically below the 50 ns spec minimum, indicating the LP-01/LP-00 preamble states were essentially skipped. With these timing violations, the bridge almost certainly failed to recognize the start-of-transmission, causing it to miss the subsequent HS burst entirely. This is a textbook flicker-inducing condition for the SN65DSI83, which is known to be strict about LP timing compliance.

    Capture 0139 [20260416_101154] — FALSE ALARM

    YES
    +
    +The LP-low plateau is measured at effectively 0 ns (reported as 0.2 ns by the flag, 0 ns in the full summary), far below the 50 ns minimum required by the SN65DSI83 to detect the Start-of-Transmission sequence. The LP exit-to-HS transition time of 0 ns confirms that the LP-01/LP-00 preamble states are essentially absent, meaning the bridge has no opportunity to recognize the SoT and synchronize to the incoming HS data burst. Despite the LP-11 voltage being within spec (1.015 V) and a valid HS burst being present, the missing LP-low plateau will cause the SN65DSI83 to miss this HS packet, resulting in visible display flicker.

    Capture 0144 [20260416_101402] — FALSE ALARM

    YES
    +
    +The LP-low plateau is measured at effectively 0 ns (reported as 0.2 ns by the pre-processor, 0 ns in the full summary), far below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection. The LP exit-to-HS transition time is also 0 ns, indicating the LP-01/LP-00 preamble states are essentially absent — the transmitter appears to jump from LP-11 directly into HS mode without dwelling in the required low states. Without a valid SoT preamble the bridge cannot synchronize to the incoming HS burst, which will cause it to miss the video packet and produce visible flicker on the display.

    Capture 0145 [20260416_101439] — FALSE ALARM

    YES
    +
    +The LP-low plateau is effectively absent at 0.2 ns (rounded to 0 ns in the full summary), which is drastically below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection. The LP exit-to-HS transition of only 3.6–4 ns confirms that the LP-01/LP-00 preamble states were either skipped or collapsed to sub-UI durations, far too brief for the bridge's LP receiver to recognize the start-of-transmission sequence. With the bridge unable to synchronize to the incoming HS burst, this capture almost certainly represents a missed SoT event resulting in visible display flicker.

    Capture 0160 [20260416_102036] — FALSE ALARM

    YES
    +
    +The LP-low plateau of 39.8 ns is clearly below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection, falling short by over 10 ns (approximately 20% under spec). Additionally, the LP exit → HS transition time of 0.1 ns is essentially instantaneous, indicating the LP-01/LP-00 preamble states were either absent or too brief for the bridge's input comparators to properly recognize the start-of-transmission sequence. These two violations together — a truncated LP-low plateau and a missing LP exit interval — make it highly likely the SN65DSI83 failed to detect this SoT, resulting in a dropped or corrupted HS burst and visible display flicker.

    Capture 0170 [20260416_102440] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at 0.3 ns is effectively absent and falls catastrophically short of the SN65DSI83's required ≥ 50 ns minimum for SoT detection. The LP exit-to-HS transition of only 1 ns (versus the 50 ns spec minimum) confirms that the LP-01/LP-00 preamble states were essentially skipped, meaning the bridge had no opportunity to recognize the start-of-transmission. With these timing violations — roughly two orders of magnitude below specification — the SN65DSI83 would almost certainly miss the SoT, fail to synchronize to the incoming HS burst, and produce a visible flicker event on the display.

    Capture 0176 [20260416_102713] — FALSE ALARM

    YES
    +
    +The LP-low plateau measured at only 0.9 ns is drastically below the SN65DSI83's required ≥ 50 ns minimum for SoT detection — it is essentially absent at less than 2% of the required duration. The LP exit-to-HS transition time of 0 ns further confirms that the LP-01/LP-00 preamble states were either skipped or too brief to be resolved, meaning the bridge almost certainly failed to recognize the start-of-transmission. With the LP→HS entry sequence this severely truncated, the SN65DSI83 would miss the HS sync, causing a dropped or corrupted video line/frame and resulting in visible flicker.
    + + + diff --git a/reports/20260416_111629_interactive.html b/reports/20260416_111629_interactive.html new file mode 100644 index 0000000..e789207 --- /dev/null +++ b/reports/20260416_111629_interactive.html @@ -0,0 +1,124 @@ + + + + +MIPI Interactive Flicker Test — 2026-04-16 11:16:29 + + + + +

    MIPI Interactive Flicker Test Report

    +

    + Generated: 2026-04-16 11:16:29  |  + Model: claude-opus-4-6 +

    + +
    + Stop reason: Test interrupted by operator (Ctrl+C) +
    + +
    +
    0 confirmed flicker(s)
    +
    1 false alarm(s)
    +
    0 Claude said no
    +
    + +

    D-PHY Configuration

    +

    + Pixel clock: 72.0 MHz  |  + Bit rate: 432.0 Mbit/s per lane  |  + Byte clock: 54.000 MHz + (18.519 ns/byte)  |  + UI: 2.315 ns +

    + + + + + + + + + + + + + + + + +
    FieldSpec (ns)Rnd BestRnd UpExtraFinalActual (ns)Status
    lpx≥ 50.033+0355.56
    hs_prepare49.3 – 98.933+0355.56
    hs_zero≥ 112.667+07129.63
    hs_trail≥ 69.344+0474.07
    hs_exit≥ 100.056+06111.11
    clk_prepare38.0 – 95.023+0355.56
    clk_zero≥ 244.41314+014259.26
    clk_post≥ 180.41010+010185.19
    clk_trail≥ 60.034+0474.07
    + +

    ✓ All D-PHY v1.1 Table 14 constraints satisfied.

    + +

    Samsung DSIM Registers

    + + + + + + + + + + + + + + + + + +
    RegisterAddressValueField breakdown
    PHY_TIMING0xb40x00000306lpx=3   hs_exit=6
    PHY_TIMING10xb80x030e0a04clk_prepare=3   clk_zero=14   + clk_post=10   clk_trail=4
    PHY_TIMING20xbc0x00030704hs_prepare=3   hs_zero=7   + hs_trail=4
    + +

    u-boot Commands

    +
    # D-PHY PHY timing registers (pixel clock 72.0 MHz, 432.0 Mbit/s, byte clock 54.000 MHz)
    +#
    +# PHY_TIMING  (0xb4) = 0x00000306   lpx=3  hs_exit=6
    +# PHY_TIMING1 (0xb8) = 0x030e0a04   clk_prepare=3  clk_zero=14  clk_post=10  clk_trail=4
    +# PHY_TIMING2 (0xbc) = 0x00030704   hs_prepare=3  hs_zero=7  hs_trail=4
    +
    +# Enable Round-Up rounding (dsi-tweak bit 2)
    +setenv flb_dtovar "${flb_dtovar} dsi-tweak=4"
    +
    +saveenv
    +boot
    + +

    Event Log

    + + + + + + + +
    CaptureTimestampChannelLP-low plateauLP exit→HSLP-11 voltageClaude: flicker?Outcome
    006520260416_111206dat26.7 ns4.0 ns1.016 VYES✓ FALSE ALARM
    + +

    Claude Assessments

    Capture 0065 [20260416_111206] — FALSE ALARM

    YES
    +
    +The LP-low plateau of 26.7 ns is barely half the SN65DSI83's required ≥ 50 ns minimum, and the LP exit-to-HS transition of only 4 ns is drastically below the 50 ns spec minimum. Together these indicate the LP-01/LP-00 SoT preamble states are far too brief for the bridge's LP receiver to reliably detect start-of-transmission. The HS amplitude of 32 mV single-ended is also suspiciously low, suggesting the bridge may not have locked onto the HS burst at all, reinforcing that this capture represents a genuine flicker event.
    + + + diff --git a/reports/flicker_log.csv b/reports/flicker_log.csv index e6d5e7b..b659ba9 100644 --- a/reports/flicker_log.csv +++ b/reports/flicker_log.csv @@ -69,3 +69,30 @@ logged_at,capture_ts,capture_num,channel,lp_low_duration_ns,lp11_to_hs_ns,lp11_v 2026-04-15 15:29:10,20260415_151921,1309,dat,0.2,2.3,1.014 2026-04-15 15:29:17,20260415_152132,1315,dat,0.3,2.5,1.015 2026-04-15 15:29:29,20260415_152447,1324,dat,0.3,3.5,1.016 +2026-04-16 07:59:18,20260416_075857,0031,dat,0.3,2.4,1.015 +2026-04-16 08:09:40,20260416_080919,0006,dat,0.3,2.4,1.014 +2026-04-16 08:12:53,20260416_081232,0013,dat,0.3,2.9,1.014 +2026-04-16 08:20:40,20260416_082018,0001,dat,108.0,3.1,1.015 +2026-04-16 08:21:09,20260416_082047,0002,dat,108.3,3.5,1.015 +2026-04-16 08:21:39,20260416_082117,0003,dat,342.7,3.6,1.015 +2026-04-16 08:22:09,20260416_082147,0004,dat,342.7,4.0,1.015 +2026-04-16 08:29:58,20260416_082936,0005,dat,0.3,2.8,1.015 +2026-04-16 08:32:23,20260416_083201,0010,dat,0.3,348.0,1.015 +2026-04-16 08:41:16,20260416_084055,0009,dat,0.2,3.5,1.016 +2026-04-16 09:17:35,20260416_091714,0002,dat,0.3,1.4,1.016 +2026-04-16 09:28:07,20260416_092745,0029,dat,0.3,1.8,1.017 +2026-04-16 09:33:00,20260416_093239,0041,dat,0.3,1.9,1.015 +2026-04-16 09:37:26,20260416_093705,0052,dat,0.3,2.6,1.015 +2026-04-16 09:52:35,20260416_095213,0091,dat,0.2,1.9,1.015 +2026-04-16 09:53:35,20260416_095313,0093,dat,0.2,0.6,1.015 +2026-04-16 09:54:33,20260416_095412,0095,dat,0.3,1.3,1.016 +2026-04-16 09:58:36,20260416_095814,0105,dat,0.9,0.8,1.015 +2026-04-16 10:06:04,20260416_100542,0124,dat,0.3,3.5,1.015 +2026-04-16 10:10:28,20260416_101007,0135,dat,23.1,1.2,1.016 +2026-04-16 10:12:15,20260416_101154,0139,dat,0.2,0.1,1.015 +2026-04-16 10:14:23,20260416_101402,0144,dat,0.2,0.1,1.015 +2026-04-16 10:15:01,20260416_101439,0145,dat,0.2,3.6,1.016 +2026-04-16 10:20:57,20260416_102036,0160,dat,39.8,0.1,1.016 +2026-04-16 10:25:01,20260416_102440,0170,dat,0.3,0.8,1.015 +2026-04-16 10:27:34,20260416_102713,0176,dat,0.9,0.1,1.016 +2026-04-16 11:12:28,20260416_111206,0065,dat,26.7,4.0,1.016 diff --git a/reports/interactive_log.csv b/reports/interactive_log.csv new file mode 100644 index 0000000..a01e8f5 --- /dev/null +++ b/reports/interactive_log.csv @@ -0,0 +1,28 @@ +logged_at,capture_ts,capture_num,claude_said_flicker,user_confirmed,lp_low_ns,reasoning_summary +2026-04-16 07:59:52,20260416_075857,0031,YES,NO,0.3,YES The LP-low plateau measured at 0.3 ns is effectively absent — nearly three orders of magnitude below the SN65DSI83's required ≥ 50 ns minimum for +2026-04-16 08:10:15,20260416_080919,0006,YES,NO,0.3,"YES The LP-low plateau measured at 0.3 ns is essentially absent, far below the 50 ns minimum required by the SN65DSI83 to detect the start-of-transmi" +2026-04-16 08:13:26,20260416_081232,0013,YES,YES,0.3,"YES The LP-low plateau is measured at effectively 0 ns (0.3 ns reported, rounded to 0 ns in the summary), which is drastically below the SN65DSI83's " +2026-04-16 08:20:46,20260416_082018,0001,NO,NOT_ASKED,108.0,"NO The LP-low plateau measures 108.0 ns, which comfortably exceeds the SN65DSI83's 50 ns minimum requirement for SoT detection. This is the critical " +2026-04-16 08:21:16,20260416_082047,0002,NO,NOT_ASKED,108.3,"NO The LP-low plateau measures 108.3 ns, which comfortably exceeds the SN65DSI83's 50 ns minimum requirement for SoT detection. The flagged concern a" +2026-04-16 08:21:46,20260416_082117,0003,NO,NOT_ASKED,342.7,"NO The LP-low plateau measures 342.7 ns, which is well above the 50 ns minimum required by the SN65DSI83 for SoT detection. The flag appears to be a " +2026-04-16 08:22:14,20260416_082147,0004,NO,NOT_ASKED,342.7,"NO The LP-low plateau measures 342.7 ns, which comfortably exceeds the 50 ns minimum required by the SN65DSI83 for SoT detection. The flag appears to" +2026-04-16 08:30:29,20260416_082936,0005,YES,NO,0.3,"YES The LP-low plateau measured at effectively 0 ns (reported as 0.3 ns by the flag, 0 ns in the full summary) is drastically below the 50 ns minimum" +2026-04-16 08:32:52,20260416_083201,0010,YES,NO,0.3,"YES The LP-low plateau measured only 0.3 ns, which is effectively absent and far below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT dete" +2026-04-16 08:41:32,20260416_084055,0009,YES,YES,0.2,"YES The LP-low plateau is measured at effectively 0 ns (reported as 0.2 ns by the pre-processor, 0 ns in the full summary), far below the 50 ns minim" +2026-04-16 09:17:52,20260416_091714,0002,YES,NO,0.3,YES The LP-low plateau measured at effectively 0 ns (reported 0.3 ns) is drastically below the SN65DSI83's required ≥ 50 ns minimum for SoT detection +2026-04-16 09:28:27,20260416_092745,0029,YES,NO,0.3,YES The LP-low plateau of 0.3 ns is essentially absent—two orders of magnitude below the SN65DSI83's required ≥ 50 ns minimum for SoT detection. The +2026-04-16 09:33:17,20260416_093239,0041,YES,NO,0.3,"YES The LP-low plateau is measured at effectively 0 ns (flagged as 0.3 ns by the pre-processor), far below the SN65DSI83's 50 ns minimum requirement " +2026-04-16 09:37:46,20260416_093705,0052,YES,NO,0.3,YES The LP-low plateau measured at 0.3 ns is effectively absent — it is over two orders of magnitude below the SN65DSI83's required ≥ 50 ns minimum f +2026-04-16 09:52:50,20260416_095213,0091,YES,NO,0.2,YES The LP-low plateau measured at 0.2 ns is essentially absent — nearly three orders of magnitude below the SN65DSI83's required ≥ 50 ns minimum for +2026-04-16 09:53:48,20260416_095313,0093,YES,NO,0.2,YES The LP-low plateau measured at effectively 0 ns (reported 0.2 ns) is vastly below the 50 ns minimum required by the SN65DSI83 to detect the Start +2026-04-16 09:54:48,20260416_095412,0095,YES,NO,0.3,"YES The LP-low plateau is measured at effectively 0 ns (reported as 0.3 ns by the flag, 0 ns in the full summary), which is drastically below the SN6" +2026-04-16 09:58:51,20260416_095814,0105,YES,NO,0.9,"YES The LP-low plateau measured at only ~1 ns, which is drastically below the SN65DSI83's required ≥50 ns minimum for reliable SoT detection — fallin" +2026-04-16 10:06:18,20260416_100542,0124,YES,NO,0.3,"YES The LP-low plateau measured at effectively 0 ns (reported as 0.3 ns by the pre-processor, 0 ns in the full summary) is drastically below the 50 n" +2026-04-16 10:10:44,20260416_101007,0135,YES,NO,23.1,"YES The LP-low plateau of 23.1 ns is less than half the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection, and the LP exit-to-HS transi" +2026-04-16 10:12:30,20260416_101154,0139,YES,NO,0.2,"YES The LP-low plateau is measured at effectively 0 ns (reported as 0.2 ns by the flag, 0 ns in the full summary), far below the 50 ns minimum requir" +2026-04-16 10:14:38,20260416_101402,0144,YES,NO,0.2,"YES The LP-low plateau is measured at effectively 0 ns (reported as 0.2 ns by the pre-processor, 0 ns in the full summary), far below the SN65DSI83's" +2026-04-16 10:15:16,20260416_101439,0145,YES,NO,0.2,"YES The LP-low plateau is effectively absent at 0.2 ns (rounded to 0 ns in the full summary), which is drastically below the SN65DSI83's required ≥ 5" +2026-04-16 10:21:14,20260416_102036,0160,YES,NO,39.8,"YES The LP-low plateau of 39.8 ns is clearly below the SN65DSI83's required ≥ 50 ns minimum for reliable SoT detection, falling short by over 10 ns (" +2026-04-16 10:25:18,20260416_102440,0170,YES,NO,0.3,YES The LP-low plateau measured at 0.3 ns is effectively absent and falls catastrophically short of the SN65DSI83's required ≥ 50 ns minimum for SoT +2026-04-16 10:27:54,20260416_102713,0176,YES,NO,0.9,YES The LP-low plateau measured at only 0.9 ns is drastically below the SN65DSI83's required ≥ 50 ns minimum for SoT detection — it is essentially ab +2026-04-16 11:12:48,20260416_111206,0065,YES,NO,26.7,"YES The LP-low plateau of 26.7 ns is barely half the SN65DSI83's required ≥ 50 ns minimum, and the LP exit-to-HS transition of only 4 ns is drastical"