Updates
This commit is contained in:
@@ -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 = 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
|
||||
@@ -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 0–50 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:
|
||||
lines.append(f" LP-low plateau : {self.lp_low_duration_ns:.0f} ns")
|
||||
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 (~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.
|
||||
@@ -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
|
||||
# → 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).
|
||||
# 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,32 +1003,44 @@ 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 (
|
||||
(lp_low_duration_ns is None or lp_low_duration_ns < FLICKER_LP_LOW_MAX_NS)
|
||||
or hs_burst_absent
|
||||
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,
|
||||
capture_num = capture_num,
|
||||
channel = channel,
|
||||
sample_rate_gsps = round(sample_rate / 1e9, 1),
|
||||
duration_us = round(duration_us, 2),
|
||||
n_samples = len(times),
|
||||
lp11_voltage_v = lp11_voltage_v,
|
||||
lp11_duration_us = lp11_duration_us,
|
||||
lp11_to_hs_ns = lp11_to_hs_ns,
|
||||
lp_low_duration_ns = lp_low_duration_ns,
|
||||
n_hs_bursts = n_hs_bursts,
|
||||
hs_burst_dur_ns = hs_burst_dur_ns,
|
||||
hs_amplitude_mv = hs_amplitude_mv,
|
||||
lp_transition_valid = lp_transition_valid,
|
||||
flicker_suspect = flicker_suspect,
|
||||
warnings = warnings,
|
||||
timestamp = timestamp,
|
||||
capture_num = capture_num,
|
||||
channel = channel,
|
||||
sample_rate_gsps = round(sample_rate / 1e9, 1),
|
||||
duration_us = round(duration_us, 2),
|
||||
n_samples = len(times),
|
||||
lp11_voltage_v = lp11_voltage_v,
|
||||
lp11_duration_us = lp11_duration_us,
|
||||
lp11_to_hs_ns = lp11_to_hs_ns,
|
||||
lp_low_duration_ns = lp_low_duration_ns,
|
||||
n_hs_bursts = n_hs_bursts,
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user