Updates
This commit is contained in:
Binary file not shown.
@@ -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
|
# LP-low plateau below this → SoT sequence too brief for receiver to detect → flicker risk
|
||||||
FLICKER_LP_LOW_MAX_NS = 50.0 # ns
|
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.
|
# 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).
|
# 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
|
# 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]]:
|
def group_captures(data_dir: Path) -> dict[tuple[str, int], dict[str, Path]]:
|
||||||
"""
|
"""
|
||||||
Scan data_dir and group CSV files by (timestamp, capture_number).
|
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
|
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
|
# Flicker detection
|
||||||
# A capture is flagged when the LP-low plateau is absent or shorter than
|
# 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 0–50 ns.
|
# FLICKER_LP_LOW_MAX_NS. Normal captures show ~340 ns; flicker shows 0–50 ns.
|
||||||
@@ -666,7 +767,19 @@ class LPMetrics:
|
|||||||
f"(spec ≥{LP_LOW_DUR_MIN_NS:.0f} ns) {ok(ok_exit)}"
|
f"(spec ≥{LP_LOW_DUR_MIN_NS:.0f} ns) {ok(ok_exit)}"
|
||||||
)
|
)
|
||||||
if self.lp_low_duration_ns is not None:
|
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")
|
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(
|
lines.append(
|
||||||
f" LP→HS sequence : {'valid ✓' if self.lp_transition_valid else 'NOT DETECTED ✗'}"
|
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:
|
if self.hs_amplitude_mv is not None:
|
||||||
lines.append(f" HS amplitude : {self.hs_amplitude_mv:.0f} mV (single-ended p-p/2)")
|
lines.append(f" HS amplitude : {self.hs_amplitude_mv:.0f} mV (single-ended p-p/2)")
|
||||||
if self.flicker_suspect:
|
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.hs_amplitude_mv < HS_BURST_AMPLITUDE_MIN_MV
|
||||||
and self.lp11_to_hs_ns is not None
|
and self.lp11_to_hs_ns is not None
|
||||||
and self.lp11_to_hs_ns >= LP_LOW_DUR_MIN_NS):
|
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:
|
if n_hs_bursts == 0:
|
||||||
warnings.append("No HS bursts detected after LP transition")
|
warnings.append("No HS bursts detected after LP transition")
|
||||||
|
|
||||||
# Flicker suspect: either the LP-low plateau is absent/short, OR the HS burst
|
# CLK lane startup check — only relevant when CLK LP-11 was captured (startup visible)
|
||||||
# amplitude is too low. Two confirmed failure modes on this hardware:
|
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 (~342–380 ns) → bridge misses SoT → returns to LP-11
|
# A) Normal LP-low (~342–380 ns) → bridge misses SoT → returns to LP-11
|
||||||
# Signature: lp11_to_hs fires at real LP-low end (~347 ns), hs_amplitude ≈ 15–30 mV.
|
# Signature: lp11_to_hs fires at real LP-low end (~347 ns), hs_amplitude ≈ 15–30 mV.
|
||||||
@@ -853,10 +979,15 @@ def analyze_lp_file(path: Path) -> "LPMetrics":
|
|||||||
# B) Short LP-low (50–200 ns, vs nominal ~342–380 ns) → marginal SoT timing
|
# B) Short LP-low (50–200 ns, vs nominal ~342–380 ns) → marginal SoT timing
|
||||||
# → HS burst starts but is weak, hs_amplitude ≈ 40–60 mV (vs normal 100–122 mV).
|
# → HS burst starts but is weak, hs_amplitude ≈ 40–60 mV (vs normal 100–122 mV).
|
||||||
# Signature: lp_low anomalously short, lp11_to_hs fires at noise spike (~3 ns).
|
# 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
|
# The lp11_to_hs guard cannot be used here, so LP-low duration gates the check.
|
||||||
# false positives), so LP-low duration itself gates the amplitude check.
|
|
||||||
# Confirmed example: capture 0120 (lp_low=108 ns, lp11_to_hs=1.7 ns, amp=49 mV).
|
# 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).
|
# Only flag DAT lane (CLK is continuous HS — LP states not expected).
|
||||||
_lp_low_short = (
|
_lp_low_short = (
|
||||||
lp_low_duration_ns is not None
|
lp_low_duration_ns is not None
|
||||||
@@ -872,14 +1003,25 @@ def analyze_lp_file(path: Path) -> "LPMetrics":
|
|||||||
or _lp_low_short
|
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 = (
|
flicker_suspect = (
|
||||||
channel == "dat"
|
channel == "dat"
|
||||||
and lp_transition_valid
|
and (
|
||||||
|
link_silent
|
||||||
|
or (
|
||||||
|
lp_transition_valid
|
||||||
and (
|
and (
|
||||||
(lp_low_duration_ns is None or lp_low_duration_ns < FLICKER_LP_LOW_MAX_NS)
|
(lp_low_duration_ns is None or lp_low_duration_ns < FLICKER_LP_LOW_MAX_NS)
|
||||||
or hs_burst_absent
|
or hs_burst_absent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return LPMetrics(
|
return LPMetrics(
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
@@ -896,6 +1038,7 @@ def analyze_lp_file(path: Path) -> "LPMetrics":
|
|||||||
hs_burst_dur_ns = hs_burst_dur_ns,
|
hs_burst_dur_ns = hs_burst_dur_ns,
|
||||||
hs_amplitude_mv = hs_amplitude_mv,
|
hs_amplitude_mv = hs_amplitude_mv,
|
||||||
lp_transition_valid = lp_transition_valid,
|
lp_transition_valid = lp_transition_valid,
|
||||||
|
clk_lp_startup_ok = clk_lp_startup_ok,
|
||||||
flicker_suspect = flicker_suspect,
|
flicker_suspect = flicker_suspect,
|
||||||
warnings = warnings,
|
warnings = warnings,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
ni w#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
MIPI TEST APPLICATION - MIPI_TEST_INTERACTIVE.PY
|
MIPI TEST APPLICATION - MIPI_TEST_INTERACTIVE.PY
|
||||||
Interactive flicker confirmation test.
|
Interactive flicker confirmation test.
|
||||||
@@ -37,7 +37,8 @@ from dotenv import load_dotenv
|
|||||||
import ai_mgmt
|
import ai_mgmt
|
||||||
import rigol_scope
|
import rigol_scope
|
||||||
from csv_preprocessor import (analyze_lp_file, LPMetrics,
|
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")
|
load_dotenv(Path(__file__).parent / ".env")
|
||||||
|
|
||||||
@@ -555,6 +556,85 @@ def _restore_hs_config():
|
|||||||
time.sleep(0.1)
|
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:
|
def _fetch_registers(ts: str, iteration: int) -> None:
|
||||||
"""GET /registers from device server and save to data/ as JSON."""
|
"""GET /registers from device server and save to data/ as JSON."""
|
||||||
try:
|
try:
|
||||||
@@ -603,7 +683,14 @@ def dual_capture(iteration: int) -> str:
|
|||||||
if n:
|
if n:
|
||||||
print(f" SAVED: {v18_path.name} ({n} samples)")
|
print(f" SAVED: {v18_path.name} ({n} samples)")
|
||||||
else:
|
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()
|
_restore_hs_config()
|
||||||
|
|
||||||
# ── Pass 2: HS signal quality ──────────────────────────────────────────
|
# ── 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,
|
def _build_claude_prompt(ts: str, iteration: int,
|
||||||
lp_summaries: list[str],
|
lp_summaries: list[str],
|
||||||
suspects: list[LPMetrics],
|
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.
|
Build a concise prompt asking Claude to assess a single capture.
|
||||||
The rule-based pre-filter has already flagged at least one LP suspect.
|
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(
|
suspect_lines = "\n".join(
|
||||||
f" channel={m.channel} lp_low_plateau={m.lp_low_duration_ns} ns "
|
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."
|
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 (
|
return (
|
||||||
f"SINGLE-CAPTURE FLICKER ASSESSMENT — capture {iteration:04d} [{ts}]\n\n"
|
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"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"potential flicker suspects because the LP-low plateau is absent or shorter "
|
||||||
f"than 50 ns:\n{suspect_lines}\n\n"
|
f"than 50 ns:\n{suspect_lines}\n\n"
|
||||||
f"Full LP capture summaries:\n{summaries_text}"
|
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"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"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"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(
|
def _analyze_lp_files(
|
||||||
ts: str, iteration: int, config: dict | None = None
|
ts: str, iteration: int
|
||||||
) -> tuple[bool, str, list[LPMetrics]]:
|
) -> tuple[list[str], list[LPMetrics]]:
|
||||||
"""
|
"""
|
||||||
Analyse the LP files for this iteration.
|
Run rule-based LP analysis for one iteration.
|
||||||
|
Returns (lp_summaries, suspects). Logs suspects and prints alerts.
|
||||||
1. Run csv_preprocessor on lp_clk and lp_dat.
|
Does NOT call Claude.
|
||||||
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] = []
|
lp_summaries: list[str] = []
|
||||||
suspects: list[LPMetrics] = []
|
suspects: list[LPMetrics] = []
|
||||||
@@ -748,11 +843,14 @@ def analyze_lp_and_ask_claude(
|
|||||||
if m.flicker_suspect:
|
if m.flicker_suspect:
|
||||||
suspects.append(m)
|
suspects.append(m)
|
||||||
_append_flicker_log(ts, iteration, 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.hs_amplitude_mv < HS_BURST_AMPLITUDE_MIN_MV
|
||||||
and m.lp11_to_hs_ns is not None
|
and m.lp11_to_hs_ns is not None
|
||||||
and m.lp11_to_hs_ns >= FLICKER_LP_LOW_MAX_NS):
|
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:
|
else:
|
||||||
reason = f"lp_low={m.lp_low_duration_ns} ns"
|
reason = f"lp_low={m.lp_low_duration_ns} ns"
|
||||||
print(f"\n *** FLICKER SUSPECT: capture {iteration:04d} "
|
print(f"\n *** FLICKER SUSPECT: capture {iteration:04d} "
|
||||||
@@ -760,36 +858,119 @@ def analyze_lp_and_ask_claude(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" LP ANALYSIS ERROR ({channel}): {e}")
|
print(f" LP ANALYSIS ERROR ({channel}): {e}")
|
||||||
|
|
||||||
if not suspects:
|
return lp_summaries, suspects
|
||||||
return False, "", []
|
|
||||||
|
|
||||||
# ── 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...")
|
print(" CALLING CLAUDE FOR ASSESSMENT...")
|
||||||
try:
|
try:
|
||||||
client = anthropic.Anthropic()
|
client = anthropic.Anthropic()
|
||||||
message = client.messages.create(
|
message = client.messages.create(
|
||||||
model = CLAUDE_MODEL,
|
model = CLAUDE_MODEL,
|
||||||
max_tokens = 512,
|
max_tokens = 600,
|
||||||
system = _build_system_prompt(config),
|
system = _build_system_prompt(config),
|
||||||
messages = [{"role": "user", "content":
|
messages = [{"role": "user", "content":
|
||||||
_build_claude_prompt(ts, iteration, lp_summaries,
|
_build_claude_prompt(ts, iteration, lp_summaries,
|
||||||
suspects, config)}],
|
suspects, config,
|
||||||
|
followup_summaries)}],
|
||||||
)
|
)
|
||||||
response = message.content[0].text.strip()
|
response = message.content[0].text.strip()
|
||||||
# Parse YES/NO from the first line
|
|
||||||
first_line = response.splitlines()[0].strip().upper()
|
first_line = response.splitlines()[0].strip().upper()
|
||||||
claude_says_flicker = first_line.startswith("YES")
|
claude_says_flicker = first_line.startswith("YES")
|
||||||
label = "FLICKER" if claude_says_flicker else "NOT FLICKER"
|
label = "FLICKER" if claude_says_flicker else "NOT FLICKER"
|
||||||
print(f" CLAUDE: {label} ({message.usage.input_tokens} in / "
|
print(f" CLAUDE: {label} ({message.usage.input_tokens} in / "
|
||||||
f"{message.usage.output_tokens} out tokens)")
|
f"{message.usage.output_tokens} out tokens)")
|
||||||
return claude_says_flicker, response, suspects
|
return claude_says_flicker, response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" CLAUDE API ERROR: {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"
|
fallback = (f"(Claude API unavailable: {e})\n"
|
||||||
f"Rule-based detector flagged LP-low plateau < 50 ns — "
|
f"Rule-based detector flagged LP-low plateau < 50 ns — "
|
||||||
f"treat as potential flicker suspect.")
|
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:
|
try:
|
||||||
while True:
|
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 ─────────────────────────────────────────────────
|
# ── Display ON ─────────────────────────────────────────────────
|
||||||
try:
|
try:
|
||||||
requests.put(URL, json={"state": "on"}, timeout=2)
|
requests.put(URL, json={"state": "on"}, timeout=2)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
print(f" WARNING: display ON failed: {e}")
|
print(f" WARNING: display ON failed: {e}")
|
||||||
|
|
||||||
|
# ── Collect CLK startup capture ────────────────────────────────
|
||||||
|
clk_startup_summaries = _collect_clk_startup(ts_startup, iteration)
|
||||||
|
|
||||||
# ── Three-pass capture ─────────────────────────────────────────
|
# ── Three-pass capture ─────────────────────────────────────────
|
||||||
ts = dual_capture(iteration)
|
ts = dual_capture(iteration)
|
||||||
|
|
||||||
@@ -1135,9 +1326,28 @@ def run_interactive_test() -> None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" TRANSFER ERROR: {e}")
|
print(f" TRANSFER ERROR: {e}")
|
||||||
|
|
||||||
# ── Analyse LP + ask Claude if suspect ─────────────────────────
|
# ── INTB pin analysis ──────────────────────────────────────────
|
||||||
claude_flicker, reasoning, suspects = analyze_lp_and_ask_claude(
|
_analyze_int_file(ts, iteration)
|
||||||
ts, iteration, config)
|
|
||||||
|
# ── 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:
|
if claude_flicker:
|
||||||
# ── Keep display ON — ask operator ─────────────────────────
|
# ── Keep display ON — ask operator ─────────────────────────
|
||||||
@@ -1181,7 +1391,7 @@ def run_interactive_test() -> None:
|
|||||||
else:
|
else:
|
||||||
print(" NOT FLICKERING — false alarm logged. Continuing.\n")
|
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
|
# Rule-based suspect but Claude said no — record for reference
|
||||||
_log_interaction(events, ts, iteration, suspects,
|
_log_interaction(events, ts, iteration, suspects,
|
||||||
False, None, reasoning)
|
False, None, reasoning)
|
||||||
|
|||||||
131
reports/20260420_074657_interactive.html
Normal file
131
reports/20260420_074657_interactive.html
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>MIPI Interactive Flicker Test — 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 |
|
||||||
|
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> |
|
||||||
|
Bit rate: <strong>432.0 Mbit/s per lane</strong> |
|
||||||
|
Byte clock: <strong>54.000 MHz</strong>
|
||||||
|
(18.519 ns/byte) |
|
||||||
|
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>≥ 50.0</td><td>3</td><td>3</td><td>+0</td><td><strong>3</strong></td><td>55.56</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>hs_prepare</code></td><td>49.3 – 98.9</td><td>3</td><td>3</td><td>+1</td><td><strong>4</strong></td><td>74.07</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>hs_zero</code></td><td>≥ 94.1</td><td>5</td><td>6</td><td>+0</td><td><strong>6</strong></td><td>111.11</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>hs_trail</code></td><td>≥ 69.3</td><td>4</td><td>4</td><td>+1</td><td><strong>5</strong></td><td>92.59</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>hs_exit</code></td><td>≥ 100.0</td><td>5</td><td>6</td><td>+0</td><td><strong>6</strong></td><td>111.11</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>clk_prepare</code></td><td>38.0 – 95.0</td><td>2</td><td>3</td><td>+0</td><td><strong>3</strong></td><td>55.56</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>clk_zero</code></td><td>≥ 244.4</td><td>13</td><td>14</td><td>+3</td><td><strong>17</strong></td><td>314.81</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>clk_post</code></td><td>≥ 180.4</td><td>10</td><td>10</td><td>+0</td><td><strong>10</strong></td><td>185.19</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>clk_trail</code></td><td>≥ 60.0</td><td>3</td><td>4</td><td>+0</td><td><strong>4</strong></td><td>74.07</td><td>✓</td></tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p style="color:#2e7d32">✓ All D-PHY v1.1 Table 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 hs_exit=6</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>PHY_TIMING1</td><td><code>0xb8</code></td>
|
||||||
|
<td><code>0x03110a04</code></td>
|
||||||
|
<td>clk_prepare=3 clk_zero=17
|
||||||
|
clk_post=10 clk_trail=4</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>PHY_TIMING2</td><td><code>0xbc</code></td>
|
||||||
|
<td><code>0x00040605</code></td>
|
||||||
|
<td>hs_prepare=4 hs_zero=6
|
||||||
|
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 "${flb_dtovar} dsi-tweak=4"
|
||||||
|
|
||||||
|
# Extra PHY cycles above Round-Up minimum
|
||||||
|
setenv flb_dtovar "${flb_dtovar} dsi-phy-extra-hs-prepare=1"
|
||||||
|
setenv flb_dtovar "${flb_dtovar} dsi-phy-extra-hs-trail=1"
|
||||||
|
setenv flb_dtovar "${flb_dtovar} dsi-phy-extra-clk-zero=3"
|
||||||
|
|
||||||
|
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→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">✓ 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">✓ 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 105–122 mV range and falls under the 50 mV "absent" 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 "absent" threshold and far from the normal 105–122 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>
|
||||||
135
reports/20260420_091026_interactive.html
Normal file
135
reports/20260420_091026_interactive.html
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>MIPI Interactive Flicker Test — 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 |
|
||||||
|
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> |
|
||||||
|
Bit rate: <strong>432.0 Mbit/s per lane</strong> |
|
||||||
|
Byte clock: <strong>54.000 MHz</strong>
|
||||||
|
(18.519 ns/byte) |
|
||||||
|
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>≥ 50.0</td><td>3</td><td>3</td><td>+0</td><td><strong>3</strong></td><td>55.56</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>hs_prepare</code></td><td>49.3 – 98.9</td><td>3</td><td>3</td><td>+1</td><td><strong>4</strong></td><td>74.07</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>hs_zero</code></td><td>≥ 94.1</td><td>5</td><td>6</td><td>+0</td><td><strong>6</strong></td><td>111.11</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>hs_trail</code></td><td>≥ 69.3</td><td>4</td><td>4</td><td>+1</td><td><strong>5</strong></td><td>92.59</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>hs_exit</code></td><td>≥ 100.0</td><td>5</td><td>6</td><td>+0</td><td><strong>6</strong></td><td>111.11</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>clk_prepare</code></td><td>38.0 – 95.0</td><td>2</td><td>3</td><td>+0</td><td><strong>3</strong></td><td>55.56</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>clk_zero</code></td><td>≥ 244.4</td><td>13</td><td>14</td><td>+3</td><td><strong>17</strong></td><td>314.81</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>clk_post</code></td><td>≥ 180.4</td><td>10</td><td>10</td><td>+0</td><td><strong>10</strong></td><td>185.19</td><td>✓</td></tr>
|
||||||
|
<tr><td><code>clk_trail</code></td><td>≥ 60.0</td><td>3</td><td>4</td><td>+0</td><td><strong>4</strong></td><td>74.07</td><td>✓</td></tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p style="color:#2e7d32">✓ All D-PHY v1.1 Table 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 hs_exit=6</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>PHY_TIMING1</td><td><code>0xb8</code></td>
|
||||||
|
<td><code>0x03110a04</code></td>
|
||||||
|
<td>clk_prepare=3 clk_zero=17
|
||||||
|
clk_post=10 clk_trail=4</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>PHY_TIMING2</td><td><code>0xbc</code></td>
|
||||||
|
<td><code>0x00040605</code></td>
|
||||||
|
<td>hs_prepare=4 hs_zero=6
|
||||||
|
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 "${flb_dtovar} dsi-tweak=4"
|
||||||
|
|
||||||
|
# Extra PHY cycles above Round-Up minimum
|
||||||
|
setenv flb_dtovar "${flb_dtovar} dsi-phy-extra-hs-prepare=1"
|
||||||
|
setenv flb_dtovar "${flb_dtovar} dsi-phy-extra-hs-trail=1"
|
||||||
|
setenv flb_dtovar "${flb_dtovar} dsi-phy-extra-clk-zero=3"
|
||||||
|
|
||||||
|
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→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">✓ 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">✓ 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">✓ 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">✓ 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'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 105–122 mV range and below the 50 mV "absent" 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 105–122 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'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>
|
||||||
@@ -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: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: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-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
|
||||||
|
|||||||
|
@@ -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: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: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-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 105–122 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 105–122 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"
|
||||||
|
|||||||
|
@@ -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
|
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
|
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
|
rigol: vxi11.Instrument | None = None
|
||||||
|
|
||||||
|
|
||||||
@@ -62,19 +70,27 @@ def is_connected() -> bool:
|
|||||||
|
|
||||||
def configure():
|
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
|
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.
|
so we always get a supply snapshot even when the rail is healthy.
|
||||||
"""
|
"""
|
||||||
rigol.write(":STOP")
|
rigol.write(":STOP")
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# CH1 — 1.8 V supply rail
|
||||||
rigol.write(":CHANnel1:DISPlay 1")
|
rigol.write(":CHANnel1:DISPlay 1")
|
||||||
rigol.write(":CHANnel2:DISPlay 0")
|
|
||||||
rigol.write(":CHANnel1:COUPling DC")
|
rigol.write(":CHANnel1:COUPling DC")
|
||||||
rigol.write(":CHANnel1:PROBe 10")
|
rigol.write(":CHANnel1:PROBe 10")
|
||||||
rigol.write(f":CHANnel1:SCALe {V18_SCALE:.3f}")
|
rigol.write(f":CHANnel1:SCALe {V18_SCALE:.3f}")
|
||||||
rigol.write(f":CHANnel1:OFFSet {V18_OFFSET:.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(f":TIMebase:MAIN:SCALe {V18_TIMEBASE:.2E}")
|
||||||
rigol.write(":TRIGger:MODE EDGE")
|
rigol.write(":TRIGger:MODE EDGE")
|
||||||
rigol.write(":TRIGger:EDGe:SOURce CHANnel1")
|
rigol.write(":TRIGger:EDGe:SOURce CHANnel1")
|
||||||
@@ -85,7 +101,7 @@ def configure():
|
|||||||
rigol.write(":RUN") # start acquiring immediately after configure
|
rigol.write(":RUN") # start acquiring immediately after configure
|
||||||
time.sleep(0.2)
|
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)")
|
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
|
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.
|
Read one Rigol channel waveform over SCPI and write to CSV.
|
||||||
Sends :STOP first to ensure acquisition is complete before reading —
|
stop_first=False skips :STOP when the scope was already stopped by a prior read.
|
||||||
this is reliable regardless of trigger/status state.
|
|
||||||
|
|
||||||
Returns the number of samples written, or 0 on error.
|
Returns the number of samples written, or 0 on error.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
if stop_first:
|
||||||
rigol.write(":STOP")
|
rigol.write(":STOP")
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
|
rigol.write(f":WAVeform:SOURce {channel}")
|
||||||
rigol.write(":WAVeform:SOURce CHANnel1")
|
|
||||||
rigol.write(":WAVeform:FORMat ASC") # Rigol DS1000Z uses ASC not ASCII
|
rigol.write(":WAVeform:FORMat ASC") # Rigol DS1000Z uses ASC not ASCII
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[RIGOL] Waveform setup error: {e}")
|
print(f"[RIGOL] {channel} waveform setup error: {e}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -145,7 +158,7 @@ def read_waveform_csv(path: Path) -> int:
|
|||||||
x_orig = float(preamble[5])
|
x_orig = float(preamble[5])
|
||||||
x_ref = float(preamble[6])
|
x_ref = float(preamble[6])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[RIGOL] Preamble error: {e}")
|
print(f"[RIGOL] {channel} preamble error: {e}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -158,11 +171,11 @@ def read_waveform_csv(path: Path) -> int:
|
|||||||
|
|
||||||
vals = [float(v) for v in raw.split(",") if v.strip()]
|
vals = [float(v) for v in raw.split(",") if v.strip()]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[RIGOL] Data read error: {e}")
|
print(f"[RIGOL] {channel} data read error: {e}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if not vals:
|
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
|
return 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -175,5 +188,18 @@ def read_waveform_csv(path: Path) -> int:
|
|||||||
writer.writerow([f"{t:.9f}", f"{v:.6f}"])
|
writer.writerow([f"{t:.9f}", f"{v:.6f}"])
|
||||||
return len(vals)
|
return len(vals)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[RIGOL] CSV write error: {e}")
|
print(f"[RIGOL] {channel} CSV write error: {e}")
|
||||||
return 0
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user