This commit is contained in:
david rice
2026-04-20 10:34:42 +01:00
parent 118d8ad380
commit e718a93667
8 changed files with 735 additions and 77 deletions

View File

@@ -52,6 +52,9 @@ HS_OSC_STD_V = 0.045 # V — rolling-std threshold above which a region i
# LP-low plateau below this → SoT sequence too brief for receiver to detect → flicker risk
FLICKER_LP_LOW_MAX_NS = 50.0 # ns
# CLK lane LP-00 minimum for SN65DSI83 CLK lane lock (TCLK_PREPARE + TCLK_ZERO ≥ 300 ns)
CLK_LP_LOW_MIN_NS = 300.0
# HS burst amplitude below this (single-ended p-p / 2, mV) → HS burst absent after LP transition.
# On this hardware normal HS = 105122 mV; confirmed flicker = 1432 mV (DC / LP-11 recovery).
# Captures where LP-01/LP-00 completed normally but the bridge never entered HS mode show
@@ -576,6 +579,98 @@ def analyze_reg_file(path: Path) -> "RegDump":
)
# ---------------------------------------------------------------------------
# SN65DSI83 IRQ pin analysis (Rigol CH2 — CMOS output, active HIGH)
# ---------------------------------------------------------------------------
# IRQ is a CMOS output (Table 5-1). Default state (IRQ_EN=0): high-impedance → reads ~0 V.
# When IRQ_EN=1 (CSR 0xE0.0): driven LOW (~0 V) when no error, HIGH (≥1.25 V) on error.
# No pull-up required. 0 V is normal. Assertion requires IRQ_EN=1 + error bits in CSR 0xE1.
INT_ASSERTED_HIGH_V = 1.0 # V — IRQ considered asserted (error) above this
@dataclass
class INTMetrics:
timestamp: str
capture_num: int
sample_rate_mhz: float
duration_us: float
n_samples: int
mean_v: float
min_v: float
max_v: float
int_asserted: bool # True if IRQ went above INT_ASSERTED_HIGH_V
asserted_duration_us: Optional[float] # total assertion time, or None if not asserted
warnings: list = field(default_factory=list)
def summary(self) -> str:
ok = lambda c: "" if c else ""
lines = [
f"Capture {self.capture_num:04d} {self.timestamp} [int/irq]",
f" IRQ mean/min/max : {self.mean_v:.3f} V / {self.min_v:.3f} V / {self.max_v:.3f} V",
]
if self.int_asserted:
dur_str = (f" ({self.asserted_duration_us:.2f} µs)"
if self.asserted_duration_us else "")
lines.append(
f" IRQ status : *** ASSERTED HIGH — bridge flagged error{dur_str} *** ✗"
)
else:
lines.append(f" IRQ status : not asserted (no bridge error) ✓")
for w in self.warnings:
lines.append(f" WARNING: {w}")
return "\n".join(lines)
def analyze_int_file(path: Path) -> "INTMetrics":
"""Analyse a Rigol CH2 IRQ pin CSV file."""
m = re.match(r"(\d{8}_\d{6})_int_(\d+)\.csv", path.name, re.IGNORECASE)
if not m:
raise ValueError(f"Filename does not match int pattern: {path.name}")
timestamp, cap_str = m.groups()
capture_num = int(cap_str)
times, volts = _read_csv(path)
dt = float(np.diff(times).mean())
sample_rate = 1.0 / dt
duration_us = (float(times[-1]) - float(times[0])) * 1e6
mean_v = float(volts.mean())
min_v = float(volts.min())
max_v = float(volts.max())
asserted_mask = volts > INT_ASSERTED_HIGH_V
int_asserted = bool(asserted_mask.any())
asserted_duration_us = None
if int_asserted:
asserted_duration_us = round(float(asserted_mask.sum()) * dt * 1e6, 3)
warnings = []
if max_v < 0.1 and mean_v < 0.1:
warnings.append(
f"IRQ pin reads ~0 V throughout — likely high-impedance (IRQ_EN=0, default). "
f"Set CSR 0xE0.0=1 and enable error bits in CSR 0xE1 to activate IRQ output."
)
return INTMetrics(
timestamp = timestamp,
capture_num = capture_num,
sample_rate_mhz = round(sample_rate / 1e6, 1),
duration_us = round(duration_us, 2),
n_samples = len(times),
mean_v = round(mean_v, 3),
min_v = round(min_v, 3),
max_v = round(max_v, 3),
int_asserted = int_asserted,
asserted_duration_us = asserted_duration_us,
warnings = warnings,
)
def group_captures(data_dir: Path) -> dict[tuple[str, int], dict[str, Path]]:
"""
Scan data_dir and group CSV files by (timestamp, capture_number).
@@ -639,6 +734,12 @@ class LPMetrics:
lp_transition_valid: bool # LP-11 → LP-low → HS sequence present
# CLK lane startup check (only set when CLK LP-11 is captured — i.e. startup was caught)
# None = CLK was in continuous HS when triggered (startup not visible in this capture)
# True = CLK LP-00 duration ≥ 300 ns (SN65DSI83 CLK lock spec met)
# False = CLK LP-00 too short → bridge may fail to lock CLK lane
clk_lp_startup_ok: Optional[bool] = None
# Flicker detection
# A capture is flagged when the LP-low plateau is absent or shorter than
# FLICKER_LP_LOW_MAX_NS. Normal captures show ~340 ns; flicker shows 050 ns.
@@ -666,7 +767,19 @@ class LPMetrics:
f"(spec ≥{LP_LOW_DUR_MIN_NS:.0f} ns) {ok(ok_exit)}"
)
if self.lp_low_duration_ns is not None:
if self.channel == "clk":
ok_clk = self.lp_low_duration_ns >= CLK_LP_LOW_MIN_NS
lines.append(
f" LP-00 (CLK) : {self.lp_low_duration_ns:.0f} ns "
f"(spec ≥{CLK_LP_LOW_MIN_NS:.0f} ns for bridge CLK lock) "
f"{'' if ok_clk else ''}"
)
else:
lines.append(f" LP-low plateau : {self.lp_low_duration_ns:.0f} ns")
if self.clk_lp_startup_ok is not None:
lines.append(
f" CLK startup : {'ok ✓' if self.clk_lp_startup_ok else '*** SHORT — bridge may not lock CLK ✗'}"
)
lines.append(
f" LP→HS sequence : {'valid ✓' if self.lp_transition_valid else 'NOT DETECTED ✗'}"
)
@@ -676,7 +789,11 @@ 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:
if (self.hs_amplitude_mv is not None
if not self.lp_transition_valid and not self.lp11_voltage_v:
lines.append(
f" *** FLICKER SUSPECT: MIPI link silent — no LP-11, LP-low, or HS detected ***"
)
elif (self.hs_amplitude_mv is not None
and self.hs_amplitude_mv < HS_BURST_AMPLITUDE_MIN_MV
and self.lp11_to_hs_ns is not None
and self.lp11_to_hs_ns >= LP_LOW_DUR_MIN_NS):
@@ -842,8 +959,17 @@ def analyze_lp_file(path: Path) -> "LPMetrics":
if n_hs_bursts == 0:
warnings.append("No HS bursts detected after LP transition")
# Flicker suspect: either the LP-low plateau is absent/short, OR the HS burst
# amplitude is too low. Two confirmed failure modes on this hardware:
# CLK lane startup check — only relevant when CLK LP-11 was captured (startup visible)
clk_lp_startup_ok: Optional[bool] = None
if channel == "clk" and lp11_regions and lp_low_duration_ns is not None:
clk_lp_startup_ok = lp_low_duration_ns >= CLK_LP_LOW_MIN_NS
if not clk_lp_startup_ok:
warnings.append(
f"CLK LP-00 {lp_low_duration_ns:.0f} ns < {CLK_LP_LOW_MIN_NS:.0f} ns "
f"(TCLK_PREPARE+TCLK_ZERO minimum) — SN65DSI83 may fail to lock CLK lane"
)
# Flicker suspect: three confirmed failure modes on this hardware:
#
# A) Normal LP-low (~342380 ns) → bridge misses SoT → returns to LP-11
# Signature: lp11_to_hs fires at real LP-low end (~347 ns), hs_amplitude ≈ 1530 mV.
@@ -853,10 +979,15 @@ def analyze_lp_file(path: Path) -> "LPMetrics":
# B) Short LP-low (50200 ns, vs nominal ~342380 ns) → marginal SoT timing
# → HS burst starts but is weak, hs_amplitude ≈ 4060 mV (vs normal 100122 mV).
# Signature: lp_low anomalously short, lp11_to_hs fires at noise spike (~3 ns).
# The lp11_to_hs guard cannot be used here (noise spike looks the same as mode A
# false positives), so LP-low duration itself gates the amplitude check.
# The lp11_to_hs guard cannot be used here, so LP-low duration gates the check.
# Confirmed example: capture 0120 (lp_low=108 ns, lp11_to_hs=1.7 ns, amp=49 mV).
#
# C) No LP-11 detected at all → MIPI link silent or stuck
# Signature: no LP-11 region found, lp_transition_valid=False, no LP or HS seen.
# This is the most severe failure: the bridge or DSI IP has stopped outputting.
# Confirmed: follow-up of capture 13 (no LP-11, no HS) while display was flickering.
# Guard: only flag DAT lane (not CLK which is normally in continuous HS mode).
#
# Only flag DAT lane (CLK is continuous HS — LP states not expected).
_lp_low_short = (
lp_low_duration_ns is not None
@@ -872,14 +1003,25 @@ def analyze_lp_file(path: Path) -> "LPMetrics":
or _lp_low_short
)
)
# Mode C: no LP-11 at all → link silent (but exclude CLK which is always HS)
link_silent = (
channel == "dat"
and not continuous_hs_clk
and not lp11_regions
)
flicker_suspect = (
channel == "dat"
and lp_transition_valid
and (
link_silent
or (
lp_transition_valid
and (
(lp_low_duration_ns is None or lp_low_duration_ns < FLICKER_LP_LOW_MAX_NS)
or hs_burst_absent
)
)
)
)
return LPMetrics(
timestamp = timestamp,
@@ -896,6 +1038,7 @@ def analyze_lp_file(path: Path) -> "LPMetrics":
hs_burst_dur_ns = hs_burst_dur_ns,
hs_amplitude_mv = hs_amplitude_mv,
lp_transition_valid = lp_transition_valid,
clk_lp_startup_ok = clk_lp_startup_ok,
flicker_suspect = flicker_suspect,
warnings = warnings,
)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
ni w#!/usr/bin/env python3
"""
MIPI TEST APPLICATION - MIPI_TEST_INTERACTIVE.PY
Interactive flicker confirmation test.
@@ -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")
@@ -555,6 +556,85 @@ def _restore_hs_config():
time.sleep(0.1)
def _arm_scope_for_clk_startup() -> None:
"""
Configure scope for CLK lane LP startup capture and arm in single-shot mode.
Must be called BEFORE display ON — the CLK+ LP-11 falling edge (start of CLK
lane SoT preamble) triggers it. The DAT0+-triggered LP pass would miss this
because CLK is already in continuous HS by the time DAT0+ falls.
"""
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") # CLK+ — fires before DAT0+
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") # arm without blocking — display ON happens next
time.sleep(0.1)
def _collect_clk_startup(ts: str, iteration: int,
timeout: float = 10.0) -> list[str]:
"""
Wait for the CLK startup trigger fired by _arm_scope_for_clk_startup(),
save CLK+ and DAT0+ channels, and return LP analysis summaries.
Restores HS config before returning.
"""
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 LP startup not captured.")
_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 channel == "clk":
if m.clk_lp_startup_ok is False:
print(f"\n *** CLK STARTUP SUSPECT: capture {iteration:04d} "
f"CLK LP-00={m.lp_low_duration_ns:.0f} ns "
f"< {CLK_LP_LOW_MIN_NS:.0f} ns — bridge may not lock CLK ***\n")
elif m.clk_lp_startup_ok is True:
print(f" CLK startup: LP-00={m.lp_low_duration_ns:.0f} ns ✓")
else:
print(" CLK startup: CLK already in continuous HS at trigger point.")
except Exception as e:
print(f" CLK STARTUP ANALYSIS ERROR ({channel}): {e}")
return summaries
def _fetch_registers(ts: str, iteration: int) -> None:
"""GET /registers from device server and save to data/ as JSON."""
try:
@@ -603,7 +683,14 @@ 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: 1V8 waveform read failed.")
# CH2 — INTB pin (read after CH1; scope already stopped)
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: INTB waveform read failed.")
_restore_hs_config()
# ── Pass 2: HS signal quality ──────────────────────────────────────────
@@ -662,10 +749,13 @@ def _build_system_prompt(config: dict | None = None) -> str:
def _build_claude_prompt(ts: str, iteration: int,
lp_summaries: list[str],
suspects: list[LPMetrics],
config: dict | None = None) -> str:
config: dict | None = None,
followup_summaries: list[str] | 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.
If followup_summaries is provided it contains the next-frame LP capture taken
immediately after the suspect — the frame the operator will actually observe.
"""
suspect_lines = "\n".join(
f" channel={m.channel} lp_low_plateau={m.lp_low_duration_ns} ns "
@@ -686,13 +776,26 @@ def _build_claude_prompt(ts: str, iteration: int,
f"({t['byte_period_ns']:.3f} ns/byte), UI {t['ui_ns']:.3f} ns."
)
followup_text = ""
if followup_summaries:
followup_text = (
f"\n\nFOLLOW-UP CAPTURE (next display frame — what the operator sees "
f"on screen while assessing):\n"
f"Note: due to the display pipeline lag, the visual flicker caused by "
f"the electrical event above appears one frame later. If the follow-up "
f"frame looks electrically normal, the flicker observed by the operator "
f"was caused by the preceding capture, not this one.\n\n"
+ "\n\n".join(followup_summaries)
)
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"{config_text}"
f"{followup_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, "
@@ -718,21 +821,13 @@ def _append_flicker_log(ts: str, iteration: int, m: LPMetrics) -> None:
])
def analyze_lp_and_ask_claude(
ts: str, iteration: int, config: dict | None = None
) -> tuple[bool, str, list[LPMetrics]]:
def _analyze_lp_files(
ts: str, iteration: int
) -> tuple[list[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
Run rule-based LP analysis for one iteration.
Returns (lp_summaries, suspects). Logs suspects and prints alerts.
Does NOT call Claude.
"""
lp_summaries: list[str] = []
suspects: list[LPMetrics] = []
@@ -748,11 +843,14 @@ def analyze_lp_and_ask_claude(
if m.flicker_suspect:
suspects.append(m)
_append_flicker_log(ts, iteration, m)
if (m.hs_amplitude_mv is not None
if not m.lp_transition_valid and not m.lp11_voltage_v:
reason = "MIPI link silent (no LP-11/LP-low/HS detected)"
elif (m.hs_amplitude_mv is not None
and m.hs_amplitude_mv < HS_BURST_AMPLITUDE_MIN_MV
and m.lp11_to_hs_ns is not None
and m.lp11_to_hs_ns >= FLICKER_LP_LOW_MAX_NS):
reason = f"HS burst absent ({m.hs_amplitude_mv:.0f} mV, lp11_to_hs={m.lp11_to_hs_ns:.0f} ns)"
reason = (f"HS burst absent ({m.hs_amplitude_mv:.0f} mV, "
f"lp11_to_hs={m.lp11_to_hs_ns:.0f} ns)")
else:
reason = f"lp_low={m.lp_low_duration_ns} ns"
print(f"\n *** FLICKER SUSPECT: capture {iteration:04d} "
@@ -760,36 +858,119 @@ def analyze_lp_and_ask_claude(
except Exception as e:
print(f" LP ANALYSIS ERROR ({channel}): {e}")
if not suspects:
return False, "", []
return lp_summaries, suspects
# ── Call Claude ────────────────────────────────────────────────────────
def _analyze_int_file(ts: str, iteration: int) -> str | None:
"""
Analyse the INTB pin CSV for this iteration.
Returns a summary string, or None if the file is missing.
"""
path = DATA_DIR / f"{ts}_int_{iteration:04d}.csv"
if not path.exists():
return None
try:
m = analyze_int_file(path)
if m.int_asserted:
print(f"\n *** INTB ASSERTED: capture {iteration:04d} "
f"bridge flagged error ({m.asserted_duration_us:.2f} µs) ***\n")
return m.summary()
except Exception as e:
print(f" INTB ANALYSIS ERROR: {e}")
return None
def _lp_followup_capture(iteration: int) -> tuple[str, list[str], list[LPMetrics]]:
"""
LP-only follow-up capture taken immediately after a suspect is detected.
Captures the next display frame — the one the operator will actually see
when asked to assess whether the screen is flickering.
Returns (ts_followup, lp_summaries, suspects).
Returns ("", [], []) silently if the scope is not reachable.
"""
print(" FOLLOW-UP CAPTURE: acquiring next frame for display-lag context...")
try:
ts_fu = datetime.now().strftime("%Y%m%d_%H%M%S")
_configure_for_lp()
_set_timebase(LP_SCALE, LP_POINTS)
if rigol_scope.is_connected():
rigol_scope.arm()
if _arm_and_wait(timeout=10):
_save_pass_channels("lp", iteration, ts_fu)
else:
print(" FOLLOW-UP: trigger timeout — skipping.")
_restore_hs_config()
return "", [], []
_restore_hs_config()
# Transfer the new files from scope
try:
copied, _ = ai_mgmt.transfer_csv_files()
print(f" FOLLOW-UP: {copied} file(s) transferred.")
except Exception as e:
print(f" FOLLOW-UP TRANSFER ERROR: {e}")
summaries, suspects = _analyze_lp_files(ts_fu, iteration)
return ts_fu, summaries, suspects
except Exception as e:
print(f" FOLLOW-UP CAPTURE ERROR: {e}")
return "", [], []
def _call_claude(
ts: str, iteration: int,
lp_summaries: list[str],
suspects: list[LPMetrics],
config: dict | None = None,
followup_summaries: list[str] | None = None,
) -> tuple[bool, str]:
"""
Call the Claude API to assess whether the flagged capture is a flicker event.
Returns (claude_says_flicker, response_text).
"""
print(" CALLING CLAUDE FOR ASSESSMENT...")
try:
client = anthropic.Anthropic()
message = client.messages.create(
model = CLAUDE_MODEL,
max_tokens = 512,
max_tokens = 600,
system = _build_system_prompt(config),
messages = [{"role": "user", "content":
_build_claude_prompt(ts, iteration, lp_summaries,
suspects, config)}],
suspects, config,
followup_summaries)}],
)
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
return claude_says_flicker, response
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
return True, fallback
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 (rule-based + Claude if suspect).
Kept for backwards compatibility — used by report generation and tests.
Does not perform a follow-up capture; call the test loop directly for that.
"""
lp_summaries, suspects = _analyze_lp_files(ts, iteration)
if not suspects:
return False, "", []
claude_flicker, response = _call_claude(ts, iteration, lp_summaries,
suspects, config)
return claude_flicker, response, suspects
# ---------------------------------------------------------------------------
@@ -1118,12 +1299,22 @@ def run_interactive_test() -> None:
try:
while True:
# ── Arm scope for CLK lane startup (BEFORE display ON) ─────────
# CLK+ LP-11 falls before DAT0+, so we must arm here.
# The regular LP pass (triggered on DAT0+) would miss CLK startup
# because CLK is already in continuous HS by the time DAT0+ falls.
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 capture ────────────────────────────────
clk_startup_summaries = _collect_clk_startup(ts_startup, iteration)
# ── Three-pass capture ─────────────────────────────────────────
ts = dual_capture(iteration)
@@ -1135,9 +1326,28 @@ def run_interactive_test() -> None:
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)
# ── INTB pin analysis ──────────────────────────────────────────
_analyze_int_file(ts, iteration)
# ── Rule-based LP analysis ─────────────────────────────────────
lp_summaries, suspects = _analyze_lp_files(ts, iteration)
followup_summaries: list[str] = []
if suspects:
# Take an LP-only follow-up capture before calling Claude.
# The visual flicker caused by a missed SoT appears one display
# frame later (pipeline lag), so the operator observes flicker
# on the frame AFTER the electrical event. Including the next
# frame gives Claude — and the operator — the correct context.
_, followup_summaries, _ = _lp_followup_capture(iteration)
# ── Call Claude if any rule-based suspect was found ────────────
claude_flicker = False
reasoning = ""
if suspects:
claude_flicker, reasoning = _call_claude(
ts, iteration, lp_summaries, suspects, config,
followup_summaries or None)
if claude_flicker:
# ── Keep display ON — ask operator ─────────────────────────
@@ -1181,7 +1391,7 @@ def run_interactive_test() -> None:
else:
print(" NOT FLICKERING — false alarm logged. Continuing.\n")
elif suspects:
if suspects and not claude_flicker:
# Rule-based suspect but Claude said no — record for reference
_log_interaction(events, ts, iteration, suspects,
False, None, reasoning)

View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MIPI Interactive Flicker Test &mdash; 2026-04-20 07:46:57</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1020px; margin: 40px auto;
padding: 0 20px; color: #222; }
h1 { color: #1a3a5c; border-bottom: 2px solid #1a3a5c; padding-bottom: 8px; }
h2 { color: #1a3a5c; margin-top: 32px; }
h3 { color: #333; }
.meta { color: #555; font-size: 0.92em; margin-top: -6px; margin-bottom: 20px; }
.stop-box { background: #e8f4fd; border-left: 4px solid #1a3a5c;
padding: 10px 16px; margin: 16px 0 24px; border-radius: 3px; }
.stat { display: inline-block; margin: 0 16px 20px 0; padding: 12px 22px;
border-radius: 6px; font-size: 1.05em; font-weight: bold; }
.s-confirmed { background: #fdecea; border: 2px solid #c62828; color: #c62828; }
.s-false { background: #e8f5e9; border: 2px solid #2e7d32; color: #2e7d32; }
.s-claude-no { background: #fff8e1; border: 2px solid #f9a825; color: #795548; }
table { border-collapse: collapse; width: 100%; margin-top: 8px; }
th { background: #1a3a5c; color: white; padding: 7px 10px; text-align: left; }
td { border: 1px solid #ddd; padding: 5px 10px; }
tr:nth-child(even) { background: #fafafa; }
pre { margin: 0; }
</style>
</head>
<body>
<h1>MIPI Interactive Flicker Test Report</h1>
<p class="meta">
Generated: 2026-04-20 07:46:57 &nbsp;|&nbsp;
Model: claude-opus-4-6
</p>
<div class="stop-box">
<strong>Stop reason:</strong> Test interrupted by operator (Ctrl+C)
</div>
<div>
<div class="stat s-confirmed">0 confirmed flicker(s)</div>
<div class="stat s-false">2 false alarm(s)</div>
<div class="stat s-claude-no">0 Claude said no</div>
</div>
<h2>D-PHY Configuration</h2>
<p>
Pixel clock: <strong>72.0 MHz</strong> &nbsp;|&nbsp;
Bit rate: <strong>432.0 Mbit/s per lane</strong> &nbsp;|&nbsp;
Byte clock: <strong>54.000 MHz</strong>
(18.519&thinsp;ns/byte) &nbsp;|&nbsp;
UI: <strong>2.315 ns</strong>
</p>
<table>
<tr>
<th>Field</th><th>Spec (ns)</th><th>Rnd Best</th><th>Rnd Up</th>
<th>Extra</th><th>Final</th><th>Actual (ns)</th><th>Status</th>
</tr>
<tr><td><code>lpx</code></td><td>&ge; 50.0</td><td>3</td><td>3</td><td>+0</td><td><strong>3</strong></td><td>55.56</td><td>&#10003;</td></tr>
<tr><td><code>hs_prepare</code></td><td>49.3 &ndash; 98.9</td><td>3</td><td>3</td><td>+1</td><td><strong>4</strong></td><td>74.07</td><td>&#10003;</td></tr>
<tr><td><code>hs_zero</code></td><td>&ge; 94.1</td><td>5</td><td>6</td><td>+0</td><td><strong>6</strong></td><td>111.11</td><td>&#10003;</td></tr>
<tr><td><code>hs_trail</code></td><td>&ge; 69.3</td><td>4</td><td>4</td><td>+1</td><td><strong>5</strong></td><td>92.59</td><td>&#10003;</td></tr>
<tr><td><code>hs_exit</code></td><td>&ge; 100.0</td><td>5</td><td>6</td><td>+0</td><td><strong>6</strong></td><td>111.11</td><td>&#10003;</td></tr>
<tr><td><code>clk_prepare</code></td><td>38.0 &ndash; 95.0</td><td>2</td><td>3</td><td>+0</td><td><strong>3</strong></td><td>55.56</td><td>&#10003;</td></tr>
<tr><td><code>clk_zero</code></td><td>&ge; 244.4</td><td>13</td><td>14</td><td>+3</td><td><strong>17</strong></td><td>314.81</td><td>&#10003;</td></tr>
<tr><td><code>clk_post</code></td><td>&ge; 180.4</td><td>10</td><td>10</td><td>+0</td><td><strong>10</strong></td><td>185.19</td><td>&#10003;</td></tr>
<tr><td><code>clk_trail</code></td><td>&ge; 60.0</td><td>3</td><td>4</td><td>+0</td><td><strong>4</strong></td><td>74.07</td><td>&#10003;</td></tr>
</table>
<p style="color:#2e7d32">&#10003; All D-PHY v1.1 Table&nbsp;14 constraints satisfied.</p>
<h3>Samsung DSIM Registers</h3>
<table>
<tr><th>Register</th><th>Address</th><th>Value</th><th>Field breakdown</th></tr>
<tr>
<td>PHY_TIMING</td><td><code>0xb4</code></td>
<td><code>0x00000306</code></td>
<td>lpx=3 &nbsp; hs_exit=6</td>
</tr>
<tr>
<td>PHY_TIMING1</td><td><code>0xb8</code></td>
<td><code>0x03110a04</code></td>
<td>clk_prepare=3 &nbsp; clk_zero=17 &nbsp;
clk_post=10 &nbsp; clk_trail=4</td>
</tr>
<tr>
<td>PHY_TIMING2</td><td><code>0xbc</code></td>
<td><code>0x00040605</code></td>
<td>hs_prepare=4 &nbsp; hs_zero=6 &nbsp;
hs_trail=5</td>
</tr>
</table>
<h3>u-boot Commands</h3>
<pre style="background:#f5f5f5;padding:12px;border-radius:4px;
white-space:pre-wrap;font-size:0.88em"># 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) = 0x03110a04 clk_prepare=3 clk_zero=17 clk_post=10 clk_trail=4
# PHY_TIMING2 (0xbc) = 0x00040605 hs_prepare=4 hs_zero=6 hs_trail=5
# Enable Round-Up rounding (dsi-tweak bit 2)
setenv flb_dtovar &quot;${flb_dtovar} dsi-tweak=4&quot;
# Extra PHY cycles above Round-Up minimum
setenv flb_dtovar &quot;${flb_dtovar} dsi-phy-extra-hs-prepare=1&quot;
setenv flb_dtovar &quot;${flb_dtovar} dsi-phy-extra-hs-trail=1&quot;
setenv flb_dtovar &quot;${flb_dtovar} dsi-phy-extra-clk-zero=3&quot;
saveenv
boot</pre>
<h2>Event Log</h2>
<table>
<tr>
<th>Capture</th><th>Timestamp</th><th>Channel</th>
<th>LP-low plateau</th><th>LP exit&rarr;HS</th><th>LP-11 voltage</th>
<th>Claude: flicker?</th><th>Outcome</th>
</tr>
<tr><td>0002</td><td>20260420_074452</td><td>dat</td><td>107.8 ns</td><td>3.1 ns</td><td>1.017 V</td><td>YES</td><td><span style="color:#2e7d32;font-weight:bold">&#10003; FALSE ALARM</span></td></tr><tr><td>0004</td><td>20260420_074554</td><td>dat</td><td>107.4 ns</td><td>1.2 ns</td><td>1.016 V</td><td>YES</td><td><span style="color:#2e7d32;font-weight:bold">&#10003; FALSE ALARM</span></td></tr>
</table>
<h2>Claude Assessments</h2><h3>Capture 0002 [20260420_074452] — FALSE ALARM</h3><pre style="background:#f5f5f5;padding:12px;border-radius:4px;white-space:pre-wrap;font-size:0.88em">YES
The LP-low plateau at ~108 ns exceeds the 50 ns minimum, but the critical failure here is the **LP exit → HS transition of only 3 ns**, far below the 50 ns specification minimum. This means the LP-01 and LP-00 states that constitute the SoT preamble are essentially absent or too brief for the SN65DSI83 to reliably detect. Additionally, the **HS amplitude of 30 mV** is well below the normal 105122 mV range and falls under the 50 mV &quot;absent&quot; threshold, indicating the bridge likely never locked onto the HS data. Together, the collapsed LP-exit timing and effectively absent HS signaling strongly indicate a missed SoT event that would produce visible flicker.</pre><h3>Capture 0004 [20260420_074554] — FALSE ALARM</h3><pre style="background:#f5f5f5;padding:12px;border-radius:4px;white-space:pre-wrap;font-size:0.88em">YES
The HS amplitude of only 32 mV (well below the 50 mV &quot;absent&quot; threshold and far from the normal 105122 mV range) indicates the HS data burst was essentially not received by the SN65DSI83, even though the LP-low plateau at 107 ns nominally meets the ≥50 ns requirement. Critically, the LP exit → HS transition time of only 1 ns (spec ≥50 ns) means the LP-01/LP-00 states were not properly held long enough for the bridge to recognize the SoT preamble — the pre-processor itself flagged this as below spec. The combination of a collapsed LP-exit duration and an effectively absent HS swing strongly indicates the bridge missed start-of-transmission on this frame, which would produce visible flicker.</pre>
</body>
</html>

View File

@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MIPI Interactive Flicker Test &mdash; 2026-04-20 09:10:26</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1020px; margin: 40px auto;
padding: 0 20px; color: #222; }
h1 { color: #1a3a5c; border-bottom: 2px solid #1a3a5c; padding-bottom: 8px; }
h2 { color: #1a3a5c; margin-top: 32px; }
h3 { color: #333; }
.meta { color: #555; font-size: 0.92em; margin-top: -6px; margin-bottom: 20px; }
.stop-box { background: #e8f4fd; border-left: 4px solid #1a3a5c;
padding: 10px 16px; margin: 16px 0 24px; border-radius: 3px; }
.stat { display: inline-block; margin: 0 16px 20px 0; padding: 12px 22px;
border-radius: 6px; font-size: 1.05em; font-weight: bold; }
.s-confirmed { background: #fdecea; border: 2px solid #c62828; color: #c62828; }
.s-false { background: #e8f5e9; border: 2px solid #2e7d32; color: #2e7d32; }
.s-claude-no { background: #fff8e1; border: 2px solid #f9a825; color: #795548; }
table { border-collapse: collapse; width: 100%; margin-top: 8px; }
th { background: #1a3a5c; color: white; padding: 7px 10px; text-align: left; }
td { border: 1px solid #ddd; padding: 5px 10px; }
tr:nth-child(even) { background: #fafafa; }
pre { margin: 0; }
</style>
</head>
<body>
<h1>MIPI Interactive Flicker Test Report</h1>
<p class="meta">
Generated: 2026-04-20 09:10:26 &nbsp;|&nbsp;
Model: claude-opus-4-6
</p>
<div class="stop-box">
<strong>Stop reason:</strong> Test interrupted by operator (Ctrl+C)
</div>
<div>
<div class="stat s-confirmed">0 confirmed flicker(s)</div>
<div class="stat s-false">4 false alarm(s)</div>
<div class="stat s-claude-no">0 Claude said no</div>
</div>
<h2>D-PHY Configuration</h2>
<p>
Pixel clock: <strong>72.0 MHz</strong> &nbsp;|&nbsp;
Bit rate: <strong>432.0 Mbit/s per lane</strong> &nbsp;|&nbsp;
Byte clock: <strong>54.000 MHz</strong>
(18.519&thinsp;ns/byte) &nbsp;|&nbsp;
UI: <strong>2.315 ns</strong>
</p>
<table>
<tr>
<th>Field</th><th>Spec (ns)</th><th>Rnd Best</th><th>Rnd Up</th>
<th>Extra</th><th>Final</th><th>Actual (ns)</th><th>Status</th>
</tr>
<tr><td><code>lpx</code></td><td>&ge; 50.0</td><td>3</td><td>3</td><td>+0</td><td><strong>3</strong></td><td>55.56</td><td>&#10003;</td></tr>
<tr><td><code>hs_prepare</code></td><td>49.3 &ndash; 98.9</td><td>3</td><td>3</td><td>+1</td><td><strong>4</strong></td><td>74.07</td><td>&#10003;</td></tr>
<tr><td><code>hs_zero</code></td><td>&ge; 94.1</td><td>5</td><td>6</td><td>+0</td><td><strong>6</strong></td><td>111.11</td><td>&#10003;</td></tr>
<tr><td><code>hs_trail</code></td><td>&ge; 69.3</td><td>4</td><td>4</td><td>+1</td><td><strong>5</strong></td><td>92.59</td><td>&#10003;</td></tr>
<tr><td><code>hs_exit</code></td><td>&ge; 100.0</td><td>5</td><td>6</td><td>+0</td><td><strong>6</strong></td><td>111.11</td><td>&#10003;</td></tr>
<tr><td><code>clk_prepare</code></td><td>38.0 &ndash; 95.0</td><td>2</td><td>3</td><td>+0</td><td><strong>3</strong></td><td>55.56</td><td>&#10003;</td></tr>
<tr><td><code>clk_zero</code></td><td>&ge; 244.4</td><td>13</td><td>14</td><td>+3</td><td><strong>17</strong></td><td>314.81</td><td>&#10003;</td></tr>
<tr><td><code>clk_post</code></td><td>&ge; 180.4</td><td>10</td><td>10</td><td>+0</td><td><strong>10</strong></td><td>185.19</td><td>&#10003;</td></tr>
<tr><td><code>clk_trail</code></td><td>&ge; 60.0</td><td>3</td><td>4</td><td>+0</td><td><strong>4</strong></td><td>74.07</td><td>&#10003;</td></tr>
</table>
<p style="color:#2e7d32">&#10003; All D-PHY v1.1 Table&nbsp;14 constraints satisfied.</p>
<h3>Samsung DSIM Registers</h3>
<table>
<tr><th>Register</th><th>Address</th><th>Value</th><th>Field breakdown</th></tr>
<tr>
<td>PHY_TIMING</td><td><code>0xb4</code></td>
<td><code>0x00000306</code></td>
<td>lpx=3 &nbsp; hs_exit=6</td>
</tr>
<tr>
<td>PHY_TIMING1</td><td><code>0xb8</code></td>
<td><code>0x03110a04</code></td>
<td>clk_prepare=3 &nbsp; clk_zero=17 &nbsp;
clk_post=10 &nbsp; clk_trail=4</td>
</tr>
<tr>
<td>PHY_TIMING2</td><td><code>0xbc</code></td>
<td><code>0x00040605</code></td>
<td>hs_prepare=4 &nbsp; hs_zero=6 &nbsp;
hs_trail=5</td>
</tr>
</table>
<h3>u-boot Commands</h3>
<pre style="background:#f5f5f5;padding:12px;border-radius:4px;
white-space:pre-wrap;font-size:0.88em"># 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) = 0x03110a04 clk_prepare=3 clk_zero=17 clk_post=10 clk_trail=4
# PHY_TIMING2 (0xbc) = 0x00040605 hs_prepare=4 hs_zero=6 hs_trail=5
# Enable Round-Up rounding (dsi-tweak bit 2)
setenv flb_dtovar &quot;${flb_dtovar} dsi-tweak=4&quot;
# Extra PHY cycles above Round-Up minimum
setenv flb_dtovar &quot;${flb_dtovar} dsi-phy-extra-hs-prepare=1&quot;
setenv flb_dtovar &quot;${flb_dtovar} dsi-phy-extra-hs-trail=1&quot;
setenv flb_dtovar &quot;${flb_dtovar} dsi-phy-extra-clk-zero=3&quot;
saveenv
boot</pre>
<h2>Event Log</h2>
<table>
<tr>
<th>Capture</th><th>Timestamp</th><th>Channel</th>
<th>LP-low plateau</th><th>LP exit&rarr;HS</th><th>LP-11 voltage</th>
<th>Claude: flicker?</th><th>Outcome</th>
</tr>
<tr><td>0006</td><td>20260420_090522</td><td>dat</td><td style="color:red">6.4 ns</td><td>0.1 ns</td><td>1.016 V</td><td>YES</td><td><span style="color:#2e7d32;font-weight:bold">&#10003; FALSE ALARM</span></td></tr><tr><td>0007</td><td>20260420_090607</td><td>dat</td><td>108.0 ns</td><td>3.4 ns</td><td>1.016 V</td><td>YES</td><td><span style="color:#2e7d32;font-weight:bold">&#10003; FALSE ALARM</span></td></tr><tr><td>0011</td><td>20260420_090800</td><td>dat</td><td style="color:red">None ns</td><td>None ns</td><td>1.015 V</td><td>YES</td><td><span style="color:#2e7d32;font-weight:bold">&#10003; FALSE ALARM</span></td></tr><tr><td>0013</td><td>20260420_090915</td><td>dat</td><td style="color:red">None ns</td><td>0.3 ns</td><td>1.016 V</td><td>YES</td><td><span style="color:#2e7d32;font-weight:bold">&#10003; FALSE ALARM</span></td></tr>
</table>
<h2>Claude Assessments</h2><h3>Capture 0006 [20260420_090522] — FALSE ALARM</h3><pre style="background:#f5f5f5;padding:12px;border-radius:4px;white-space:pre-wrap;font-size:0.88em">YES
The LP-low plateau of 6.4 ns is drastically below the SN65DSI83&#x27;s required ≥ 50 ns minimum for SoT detection, making it virtually certain the bridge missed the start-of-transmission. This is further corroborated by the HS amplitude of only 46 mV—well below the normal 105122 mV range and below the 50 mV &quot;absent&quot; threshold—indicating the HS data burst was either never properly initiated or was not recognized by the receiver. The follow-up capture shows a recovery to a 108 ns LP-low plateau and a healthy 114 mV HS amplitude, consistent with the pattern where a single corrupted frame causes a visible flicker one frame later while the link re-establishes normal operation.</pre><h3>Capture 0007 [20260420_090607] — FALSE ALARM</h3><pre style="background:#f5f5f5;padding:12px;border-radius:4px;white-space:pre-wrap;font-size:0.88em">YES
Although the LP-low plateau itself measures 108 ns (above the 50 ns minimum), the HS amplitude of only 21 mV is far below the normal 105122 mV range and well under the 50 mV threshold for a valid HS signal. This means the SN65DSI83 bridge almost certainly cannot resolve the differential HS data even if SoT entry were detected. Additionally, the LP exit → HS transition of only 3 ns (spec ≥ 50 ns) indicates the LP-01/LP-00 states are effectively absent or too brief for reliable detection, compounding the problem. The combination of a virtually absent LP exit duration and critically low HS amplitude makes it highly likely the bridge missed or failed to lock onto the HS burst, producing a visible flicker event.</pre><h3>Capture 0011 [20260420_090800] — FALSE ALARM</h3><pre style="background:#f5f5f5;padding:12px;border-radius:4px;white-space:pre-wrap;font-size:0.88em">YES
The primary capture on the DAT0 lane shows an **absent LP-low plateau** (reported as `None`), meaning the transmitter never held LP-00/LP-01 long enough—or at all—for the SN65DSI83 to recognize a valid Start-of-Transmission preamble (≥ 50 ns required). Additionally, the HS amplitude is reported as `None`, confirming no usable HS burst was delivered in this frame. The follow-up capture corroborates the flicker scenario: although it does show an LP-low plateau of 380 ns (adequate timing), the HS amplitude is only **22 mV**—well below the 50 mV minimum detection threshold—meaning the bridge would fail to decode that burst as well. Taken together, the missing SoT preamble in the primary capture and the sub-threshold HS amplitude in the follow-up frame strongly indicate at least one (and likely two) consecutive frames were lost by the bridge, producing visible display flicker.</pre><h3>Capture 0013 [20260420_090915] — FALSE ALARM</h3><pre style="background:#f5f5f5;padding:12px;border-radius:4px;white-space:pre-wrap;font-size:0.88em">YES
The DAT0 lane shows an LP-low plateau of effectively 0 ns (flagged as absent/None), far below the SN65DSI83&#x27;s required ≥ 50 ns minimum for SoT detection. The LP exit → HS transition time of 0 ns confirms that the LP-01/LP-00 preamble states were either entirely skipped or too brief to be resolved, meaning the bridge almost certainly missed the start-of-transmission. The follow-up capture at 090936 corroborates this: no LP-11 state, no LP→HS transition, and no HS bursts were detected, consistent with the bridge having lost synchronization and the link being in a broken/stalled state — exactly the pattern that produces visible flicker (or a blank frame) on the display.</pre>
</body>
</html>

View File

@@ -211,3 +211,10 @@ logged_at,capture_ts,capture_num,channel,lp_low_duration_ns,lp11_to_hs_ns,lp11_v
2026-04-17 14:42:52,20260417_144230,0355,dat,108.0,2.0,1.015
2026-04-17 14:44:36,20260417_144415,0359,dat,379.6,384.6,1.015
2026-04-17 14:48:11,20260417_144749,0368,dat,107.8,2.0,1.016
2026-04-20 07:45:14,20260420_074452,0002,dat,107.8,3.1,1.017
2026-04-20 07:46:15,20260420_074554,0004,dat,107.4,1.2,1.016
2026-04-20 09:05:43,20260420_090522,0006,dat,6.4,0.1,1.016
2026-04-20 09:06:29,20260420_090607,0007,dat,108.0,3.4,1.016
2026-04-20 09:08:22,20260420_090800,0011,dat,,,1.015
2026-04-20 09:08:29,20260420_090822,0011,dat,379.6,384.8,1.015
2026-04-20 09:09:36,20260420_090915,0013,dat,,0.3,1.016
1 logged_at capture_ts capture_num channel lp_low_duration_ns lp11_to_hs_ns lp11_voltage_v
211 2026-04-17 14:42:52 20260417_144230 0355 dat 108.0 2.0 1.015
212 2026-04-17 14:44:36 20260417_144415 0359 dat 379.6 384.6 1.015
213 2026-04-17 14:48:11 20260417_144749 0368 dat 107.8 2.0 1.016
214 2026-04-20 07:45:14 20260420_074452 0002 dat 107.8 3.1 1.017
215 2026-04-20 07:46:15 20260420_074554 0004 dat 107.4 1.2 1.016
216 2026-04-20 09:05:43 20260420_090522 0006 dat 6.4 0.1 1.016
217 2026-04-20 09:06:29 20260420_090607 0007 dat 108.0 3.4 1.016
218 2026-04-20 09:08:22 20260420_090800 0011 dat 1.015
219 2026-04-20 09:08:29 20260420_090822 0011 dat 379.6 384.8 1.015
220 2026-04-20 09:09:36 20260420_090915 0013 dat 0.3 1.016

View File

@@ -141,3 +141,9 @@ logged_at,capture_ts,capture_num,claude_said_flicker,user_confirmed,lp_low_ns,re
2026-04-17 14:43:04,20260417_144230,0355,YES,NO,108.0,"YES The LP-low plateau of 108 ns meets the ≥50 ns requirement, but the LP exit-to-HS transition of only 2 ns is critically below the 50 ns spec minim"
2026-04-17 14:44:43,20260417_144415,0359,NO,NOT_ASKED,379.6,NO The LP-low plateau of 379.6 ns and the LP-11→HS transition time of 384.6 ns both comfortably exceed the SN65DSI83's 50 ns minimum requirement for
2026-04-17 14:48:47,20260417_144749,0368,YES,YES,107.8,YES The HS amplitude of 32 mV is critically low — well below the SN65DSI83's minimum differential detection threshold (typically ~70 mV single-ended
2026-04-20 07:45:30,20260420_074452,0002,YES,NO,107.8,"YES The LP-low plateau at ~108 ns exceeds the 50 ns minimum, but the critical failure here is the **LP exit → HS transition of only 3 ns**, far below"
2026-04-20 07:46:31,20260420_074554,0004,YES,NO,107.4,"YES The HS amplitude of only 32 mV (well below the 50 mV ""absent"" threshold and far from the normal 105122 mV range) indicates the HS data burst was"
2026-04-20 09:06:06,20260420_090522,0006,YES,NO,6.4,"YES The LP-low plateau of 6.4 ns is drastically below the SN65DSI83's required ≥ 50 ns minimum for SoT detection, making it virtually certain the bri"
2026-04-20 09:06:50,20260420_090607,0007,YES,NO,108.0,"YES Although the LP-low plateau itself measures 108 ns (above the 50 ns minimum), the HS amplitude of only 21 mV is far below the normal 105122 mV r"
2026-04-20 09:08:51,20260420_090800,0011,YES,NO,,"YES The primary capture on the DAT0 lane shows an **absent LP-low plateau** (reported as `None`), meaning the transmitter never held LP-00/LP-01 long"
2026-04-20 09:09:58,20260420_090915,0013,YES,NO,,"YES The DAT0 lane shows an LP-low plateau of effectively 0 ns (flagged as absent/None), far below the SN65DSI83's required ≥ 50 ns minimum for SoT de"
1 logged_at capture_ts capture_num claude_said_flicker user_confirmed lp_low_ns reasoning_summary
141 2026-04-17 14:43:04 20260417_144230 0355 YES NO 108.0 YES The LP-low plateau of 108 ns meets the ≥50 ns requirement, but the LP exit-to-HS transition of only 2 ns is critically below the 50 ns spec minim
142 2026-04-17 14:44:43 20260417_144415 0359 NO NOT_ASKED 379.6 NO The LP-low plateau of 379.6 ns and the LP-11→HS transition time of 384.6 ns both comfortably exceed the SN65DSI83's 50 ns minimum requirement for
143 2026-04-17 14:48:47 20260417_144749 0368 YES YES 107.8 YES The HS amplitude of 32 mV is critically low — well below the SN65DSI83's minimum differential detection threshold (typically ~70 mV single-ended
144 2026-04-20 07:45:30 20260420_074452 0002 YES NO 107.8 YES The LP-low plateau at ~108 ns exceeds the 50 ns minimum, but the critical failure here is the **LP exit → HS transition of only 3 ns**, far below
145 2026-04-20 07:46:31 20260420_074554 0004 YES NO 107.4 YES The HS amplitude of only 32 mV (well below the 50 mV "absent" threshold and far from the normal 105–122 mV range) indicates the HS data burst was
146 2026-04-20 09:06:06 20260420_090522 0006 YES NO 6.4 YES The LP-low plateau of 6.4 ns is drastically below the SN65DSI83's required ≥ 50 ns minimum for SoT detection, making it virtually certain the bri
147 2026-04-20 09:06:50 20260420_090607 0007 YES NO 108.0 YES Although the LP-low plateau itself measures 108 ns (above the 50 ns minimum), the HS amplitude of only 21 mV is far below the normal 105–122 mV r
148 2026-04-20 09:08:51 20260420_090800 0011 YES NO YES The primary capture on the DAT0 lane shows an **absent LP-low plateau** (reported as `None`), meaning the transmitter never held LP-00/LP-01 long
149 2026-04-20 09:09:58 20260420_090915 0013 YES NO YES The DAT0 lane shows an LP-low plateau of effectively 0 ns (flagged as absent/None), far below the SN65DSI83's required ≥ 50 ns minimum for SoT de

View File

@@ -21,6 +21,14 @@ V18_TIMEBASE = 1e-6 # s/div — 1 µs/div = 10 µs total window
V18_TRIG_LEVEL = 1.76 # V — falling-edge trigger on supply droop > 40 mV
TRIG_TIMEOUT_S = 15.0 # s — wait this long for Rigol to capture after arming
# CH2 — SN65DSI83 IRQ pin (CMOS output, active HIGH, high-impedance when IRQ_EN=0)
# CSR 0xE0.0 IRQ_EN=0 (default): pin is high-impedance → reads ~0 V (no pull on PCB, normal)
# IRQ_EN=1, no error: driven LOW (~0 V)
# IRQ_EN=1, error asserted: driven HIGH (~1.25 V min per VOH spec)
# No pull-up required — CMOS output drives both high and low.
INT_V_SCALE = 0.2 # V/div — shows 0~1.8 V range clearly
INT_V_OFFSET = -0.9 # V — centres display on 0.9 V midpoint
rigol: vxi11.Instrument | None = None
@@ -62,19 +70,27 @@ def is_connected() -> bool:
def configure():
"""
Configure Rigol for 1.8 V supply monitoring.
Configure Rigol CH1 for 1.8 V supply monitoring and CH2 for SN65DSI83 INTB pin.
AUTO trigger sweep: if no droop occurs, scope still captures on timeout
so we always get a supply snapshot even when the rail is healthy.
"""
rigol.write(":STOP")
time.sleep(0.2)
# CH1 — 1.8 V supply rail
rigol.write(":CHANnel1:DISPlay 1")
rigol.write(":CHANnel2:DISPlay 0")
rigol.write(":CHANnel1:COUPling DC")
rigol.write(":CHANnel1:PROBe 10")
rigol.write(f":CHANnel1:SCALe {V18_SCALE:.3f}")
rigol.write(f":CHANnel1:OFFSet {V18_OFFSET:.3f}")
# CH2 — SN65DSI83 INTB pin (active-low open-drain, external 10 kΩ pull-up to 1.8 V required)
rigol.write(":CHANnel2:DISPlay 1")
rigol.write(":CHANnel2:COUPling DC")
rigol.write(":CHANnel2:PROBe 1") # direct probe, no attenuation
rigol.write(f":CHANnel2:SCALe {INT_V_SCALE:.3f}")
rigol.write(f":CHANnel2:OFFSet {INT_V_OFFSET:.3f}")
rigol.write(f":TIMebase:MAIN:SCALe {V18_TIMEBASE:.2E}")
rigol.write(":TRIGger:MODE EDGE")
rigol.write(":TRIGger:EDGe:SOURce CHANnel1")
@@ -85,7 +101,7 @@ def configure():
rigol.write(":RUN") # start acquiring immediately after configure
time.sleep(0.2)
print(f"[RIGOL] Configured: 1.8 V rail, {int(V18_TIMEBASE*1e6)} µs/div, "
print(f"[RIGOL] Configured: CH1=1.8 V rail, CH2=INTB pin, {int(V18_TIMEBASE*1e6)} µs/div, "
f"trigger <{V18_TRIG_LEVEL} V falling (AUTO sweep, running)")
@@ -117,24 +133,21 @@ def wait_captured(timeout_s: float = TRIG_TIMEOUT_S) -> bool:
return False
def read_waveform_csv(path: Path) -> int:
def _read_channel_csv(channel: str, path: Path, stop_first: bool = True) -> int:
"""
Read Ch1 waveform from Rigol over SCPI and write to CSV.
Sends :STOP first to ensure acquisition is complete before reading —
this is reliable regardless of trigger/status state.
Read one Rigol channel waveform over SCPI and write to CSV.
stop_first=False skips :STOP when the scope was already stopped by a prior read.
Returns the number of samples written, or 0 on error.
"""
try:
if stop_first:
rigol.write(":STOP")
time.sleep(0.3)
rigol.write(":WAVeform:SOURce CHANnel1")
rigol.write(f":WAVeform:SOURce {channel}")
rigol.write(":WAVeform:FORMat ASC") # Rigol DS1000Z uses ASC not ASCII
time.sleep(0.1)
except Exception as e:
print(f"[RIGOL] Waveform setup error: {e}")
print(f"[RIGOL] {channel} waveform setup error: {e}")
return 0
try:
@@ -145,7 +158,7 @@ def read_waveform_csv(path: Path) -> int:
x_orig = float(preamble[5])
x_ref = float(preamble[6])
except Exception as e:
print(f"[RIGOL] Preamble error: {e}")
print(f"[RIGOL] {channel} preamble error: {e}")
return 0
try:
@@ -158,11 +171,11 @@ def read_waveform_csv(path: Path) -> int:
vals = [float(v) for v in raw.split(",") if v.strip()]
except Exception as e:
print(f"[RIGOL] Data read error: {e}")
print(f"[RIGOL] {channel} data read error: {e}")
return 0
if not vals:
print("[RIGOL] No samples parsed — check scope channel and format settings")
print(f"[RIGOL] {channel}: no samples parsed — check channel and format settings")
return 0
try:
@@ -175,5 +188,18 @@ def read_waveform_csv(path: Path) -> int:
writer.writerow([f"{t:.9f}", f"{v:.6f}"])
return len(vals)
except Exception as e:
print(f"[RIGOL] CSV write error: {e}")
print(f"[RIGOL] {channel} CSV write error: {e}")
return 0
def read_waveform_csv(path: Path) -> int:
"""Read CH1 (1.8 V supply) waveform from Rigol and write to CSV."""
return _read_channel_csv("CHANnel1", path, stop_first=True)
def read_int_csv(path: Path) -> int:
"""
Read CH2 (SN65DSI83 INTB pin) waveform from Rigol and write to CSV.
Must be called after read_waveform_csv() — scope is already stopped.
"""
return _read_channel_csv("CHANnel2", path, stop_first=False)