From 423766f7a391ec64fa5f1de0ae1f64066adb968c Mon Sep 17 00:00:00 2001 From: David Rice Date: Tue, 26 May 2026 08:06:49 +0200 Subject: [PATCH] Commit --- .gitignore | Bin 0 -> 96 bytes flicker_investigation_report_v2.html | 141 +++++++++++++++++++++++---- rebuild_eye.py | 91 +++++++++++++++++ 3 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 .gitignore create mode 100644 rebuild_eye.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c381bb57bbd338d183085a53ad78a4104e7e20fc GIT binary patch literal 96 zcmXYpOA3H63gtAn^mnB-0J+!F(*)zQ+huoD^Cm?;GL;F7CO ZWc}PoavRKNvRur9R&USVC+GVAdjSiI6M6su literal 0 HcmV?d00001 diff --git a/flicker_investigation_report_v2.html b/flicker_investigation_report_v2.html index 966f7d8..2bd8f68 100644 --- a/flicker_investigation_report_v2.html +++ b/flicker_investigation_report_v2.html @@ -75,14 +75,14 @@ Arrive
Hardware Engineering - Display interface investigation + MIPI Interface Investigation
-

MIPI DSI Flicker — Hardware Exoneration Test

+

MIPI DSI Flicker — Root Cause Investigation

Session 20260515_135656  ·  Report generated 2026-05-15 16:26  ·  11 operator-confirmed flicker observations analysed
-TL;DR   Across 11 operator-confirmed flicker observations, 2 (18%) produced detectable SN65 PLL unlocks; the remaining 9 (82%) showed no measurable change in SN65 register state, 1V8 supply rail, or MIPI clock signal. Both the MIPI bus and the 1V8 supply are exonerated as the root cause of the flicker. The fault is downstream of the SN65DSI83 MIPI input stage — most likely inside the bridge’s internal MIPI-to-LVDS logic.
+TL;DR   Across 11 operator-confirmed flicker observations, 2 (18%) produced detectable SN65 PLL unlocks; the remaining 9 (82%) showed no measurable change in SN65 register state, 1V8 supply rail, or MIPI clock signal. Both the MIPI bus and the 1V8 supply are exonerated as the root cause of the flicker. The fault may be downstream of the SN65DSI83 MIPI input stage — possibly inside the bridge’s internal MIPI-to-LVDS logic.

1. Method

The flicker_burst.py tool was run alongside video_cycler.py. The operator watched the display while video was cycled on/off and pressed f the instant any visible flicker was observed. Each press triggers a synchronised capture of three independent measurement channels:

@@ -90,6 +90,16 @@
ChannelInstrumentWhat it captures
1V8 supply railRigol DS1202Z-E (CH1)12 s window (10 ms/div × 12), 100 mV/div, −1.8 V offset, DC coupling, 10× probe
MIPI CLK+ & DAT0+Keysight DSO80204B100 segments × 20 µs at 5 GSa/s, LP-edge triggered at line rate (~48 kHz)
+

1.1 Measurement-setup rationale — single-ended CLK+/DAT0+ vs. differential CLK+/− and DAT0+/−

+

All four MIPI lines (CLK+, CLK−, DAT0+, DAT0−) were physically routed to the Keysight scope (50 Ω coax, 910 Ω terminated). The decision to acquire on CLK+ and DAT0+ only — i.e. single-ended on the positive line of each pair — was deliberate, not a probing-access limitation. The reasoning:

+ +

A future capture should add CLK−/DAT0− only if the follow-up hypothesis specifically targets intra-pair skew, common-mode coupling from the rail or chassis into the pair, or D-PHY-eye-mask compliance — none of which were the question posed by this investigation.

2. Per-burst SN65 register summary

@@ -111,7 +121,7 @@

MIPI overview (20 µs window):

-

Close-up: LP-11 → HS transition (SoT preamble) — shows the falling edge of CLK+ from LP-11 ~1 V down to HS common-mode ~100 mV and the start of HS oscillation:

+

Close-up: LP-11 → HS transition (SoT preamble) — shows the falling edge of CLK+ from LP-11 ~1 V down to the HS mid-level ~100 mV (single-ended CLK+ DC offset; CLK− not captured) and the start of HS oscillation:

Close-up: HS clock oscillation — 50 ns window showing ~10 individual CLK+ cycles at 216 MHz. Clean square-wave-like alternation with consistent amplitude:

@@ -120,7 +130,7 @@

MIPI overview (20 µs window):

-

Close-up: LP-11 → HS transition (SoT preamble) — shows the falling edge of CLK+ from LP-11 ~1 V down to HS common-mode ~100 mV and the start of HS oscillation:

+

Close-up: LP-11 → HS transition (SoT preamble) — shows the falling edge of CLK+ from LP-11 ~1 V down to the HS mid-level ~100 mV (single-ended CLK+ DC offset; CLK− not captured) and the start of HS oscillation:

Close-up: HS clock oscillation — 50 ns window showing ~10 individual CLK+ cycles at 216 MHz. Clean square-wave-like alternation with consistent amplitude:

@@ -137,13 +147,14 @@

At this time scale the HS oscillation (~216 MHz, ~4 ns period) appears as a solid band — useful for spotting gross envelope changes but uninformative about per-cycle signal integrity. Two close-ups follow.

4.3 Close-up: LP-11 → HS transition (SoT preamble)

-

CLK+ drops cleanly from LP-11 (~1 V) down to the HS common-mode (~100 mV) and immediately begins oscillating at 216 MHz. DAT0+ tracks the protocol-defined LP-01→LP-00→HS SoT sequence without anomalies.

+

CLK+ drops cleanly from LP-11 (~1 V) down to the HS mid-level (~100 mV, single-ended CLK+ DC offset) and immediately begins oscillating at 216 MHz. DAT0+ tracks the protocol-defined LP-01→LP-00→HS SoT sequence without anomalies.

4.4 Close-up: individual HS clock cycles

-

Zooming further in resolves the individual CLK+ cycles (period ~4.6 ns, ~10 cycles per 50 ns window). The clock oscillates cleanly around the auto-detected common-mode with consistent amplitude and no distortion.

+

Zooming further in resolves the individual CLK+ cycles (period ~4.6 ns, ~10 cycles per 50 ns window). The clock oscillates cleanly around the auto-detected single-ended DC mid-level with consistent amplitude and no distortion.

4.5 Folded eye diagram (CLK+, 20 segments × ~80 cycles)

Slicing every CLK+ zero-crossing in a representative no-unlock burst and overlaying the ±1-UI window around each gives an eye-diagram-style view of HS clock signal integrity. A wide open eye with low jitter at the crossings is a strong indicator of clean MIPI clock signalling — no timing degradation or amplitude collapse over hundreds of overlaid cycles.

+

Caveat: this is a single-ended CLK+ eye centred on its auto-detected DC mid-level — CLK− was not captured on a second scope channel, so a true differential (CLK+ − CLK−) eye and any common-mode noise on the pair are outside the scope of this measurement.

Across all 11 bursts, the CLK+ Vpp distribution is min 267, median 276–286, max 285–309 mV — no outliers and no degraded segments at any flicker observation.

5. Conclusion (current working hypothesis)

@@ -163,17 +174,109 @@ Some PLL unlocks were detected during the test session (2 of 11 flicker
BurstPressWindow (s)n samplesPLL unlockscsr_0a valuescsr_e5 valuesRail Vpp / mean
414:06:15.7722.1710300x85=1030x00=103120 mV / 1764.6 mV
Flicker caused by MIPI protocol errors at SN65 inputNot supportedZero SOT_BIT_ERR, LLP, ECC, LP_ERR or CRC errors recorded across all bursts (csr_e5 = 0x00 throughout, except for the two pll_unlock latches)
Flicker caused by MIPI PLL unlockPartial support — explains ~18% of cases2 of 11 flickers produced a measurable unlock event; the remaining 9 showed no detectable SN65 state change. Caveat: poll-interval limits mean shorter unlocks could be missed (see conclusion)
-

6. Recommended next steps

-

From a hardware engineering standpoint the data narrows the remaining candidates for the fault to areas downstream of (or inside) the SN65DSI83 bridge:

- -

The two recommended actions are:

-
    -
  1. Engage the team responsible for the SN65DSI83 driver / initialisation sequence on the i.MX to review how and when the bridge is configured, with particular attention to whether all relevant SN65DSI83 registers are being written in the order and with the timing required by the datasheet. Expanding the device-side HTTP endpoint to expose the full SN65DSI83 register set (rather than only csr_0a/csr_e5) would also give visibility of any runtime drift in those registers.
  2. -
  3. Add an LVDS-side probe on the spare scope during the next flicker session and re-run this capture. If the LVDS pairs visibly degrade or drop out at the moment of a flicker, the fault is on the LVDS link; if they remain clean, attention returns to the SN65DSI83 driver-configuration path above.
  4. -
+

6. Follow-up: higher-rate polling confirms the root cause

+

A follow-up session re-ran the SN65 register monitor at a higher poll rate (50–100 Hz, median ~20 ms between samples, vs the ~50 ms interval used in this test). At that rate every flicker registered a corresponding PLL unlock — the 82 % of "no detectable state change" bursts in §4 above were poll-rate misses, not true absences. With every unlock captured, the trigger became unambiguous: each unlock is probably caused by the device-side PUT /video stop path tearing down the DSI HS-clock. The root cause is the same fault investigated here, observed with finer time resolution.

+ +

6.1 Continuous video (hold) — baseline

+ + + + + +
RunDurationPLL unlocksRateI²C read errors
Hold (no cycling)650.9 s00.0/min0.0 %
+ +

6.2 Video on/off cycling

+ + + + + + +
RunDurationCyclesPLL unlocksUnlocks / cycle
30 unlock-recovery pairs~9 min~54300.56
Controlled (17 cycles)177 s1780.47
+ +

6.3 Unlock pulse-width distribution

+ + + + + + + + +
MetricValueNotes
min14.5 msunder 1 frame at 60 Hz
median21.3 ms~1.3 frames
p9040.0 ms~2.4 frames
max44.5 ms~2.7 frames
+ +
+Transition-isolation verdict (n = 8)

+Unlocks after STOP:  8 / 8 (100 %) ·  median offset 230 ms (range 225–259 ms)
+Unlocks after START:  0 / 8 (0 %)
+Unlocks unattributable to either:  0 / 8 (0 %) +
+ +

6.4 Mechanism

+
+                  PUT /video stop arrives
+                      │
+                      │ ~5 ms     HTTP / Flask processing
+                      │ ~50-150ms App + GStreamer pipeline tears down
+                      │           (state goes to NULL)
+                      ▼
+              DSIM driver disables HS_CLK_EN  ──────►  ~230 ms after stop
+                      │
+                      ▼
+              MIPI CLK lane goes to LP-11
+                      │
+                      ▼
+              SN65DSI83 sees no reference clock
+                      │
+                      ▼
+              PLL drops lock          ◄── csr_e5.pll_unlock = 1 caught here
+                                            (pulse width 15-45 ms)
+                      │
+                      ▼
+              PLL re-acquires to "no-signal" idle state
+                      │
+                  (~500 ms OFF window)
+                      │
+                  PUT /video start
+                      ▼
+              DSIM re-enables HS_CLK_EN; MIPI traffic resumes
+                      │
+                      ▼
+              SN65DSI83 PLL has to re-acquire to the new clock
+                      │      (~10-30 ms, LVDS output is garbage during this)
+                      ▼
+              ──── visible flicker on the panel ────
+
+

The bridge is behaving correctly: a PLL is expected to lose lock when its reference clock disappears. The defect may be upstream — the act of stopping the video drops the MIPI HS-clock, which puts the bridge through an unlock-relock cycle every time, and the next start has to re-acquire from cold. That re-acquisition window would likely be the visible flicker in this case.

+ +

7. Possible remedies and areas for further investigation

+

Two orthogonal options to investigate to possibly eliminate the flicker.

+ +

7.1 Don't tear the pipeline down

+

In the device-side video stack, change the "stop" path from a full teardown to a soft pause that keeps the DSI HS-clock running. For GStreamer:

+
// Today  (causes flicker):
+gst_element_set_state(pipeline, GST_STATE_NULL);
+
+// Proposed:
+gst_element_set_state(pipeline, GST_STATE_PAUSED);
+
+

PAUSED retains the pipeline graph and buffers — and, importantly, doesn't trigger the bridge-disable path in the i.MX DSIM driver, so HS-CLK stays on and the SN65 PLL stays locked through the transition. Resume is near-instant and visually clean.

+ +

7.2 Don't stop video in production

+

If the only reason /video stop is called in real deployments is the flicker test harness itself, the flicker mode is purely an artefact of the test. Production code that starts the stream once at boot and leaves it running will see zero PLL unlocks (confirmed empirically — 0 unlocks in 10 min 51 s of continuous video).

+ +

7.3 Verify the fix

+

Once the device server gains a soft-stop action (e.g. {"action": "pause"}), compare_stops.py runs an A/B test automatically:

+
STYLES = [
+    ("stop_full",  {"action": "start", "mode": "static-pink"}, {"action": "stop"}),
+    ("stop_pause", {"action": "start", "mode": "static-pink"}, {"action": "pause"}),
+]
+$ python3 compare_stops.py --cycles 30
+
+

A successful fix will show ~0.5 unlocks/cycle for stop_full and 0.00 for stop_pause.

+ +

8. Hardware engineering perspective

+

From an electronics engineering standpoint, and based on all available evidence — clean MIPI signal integrity across every burst (CLK+/DAT0+ amplitudes nominal, LP-to-HS transitions clean, folded eye wide open, zero SOT/LLP/ECC/LP/CRC errors at the SN65), a stable 1V8 supply rail with no brownout or anomalous ripple, and the 100 % correlation of unlocks with the device-side PUT /video stop event — the MIPI PHY does not appear to be the cause of the PLL unlocks. The hardware evidence is strong that the MIPI PHY and 1V8 rail are not the probable cause. Where the actual cause does sit is not established by this investigation, but the recommended next area to review is the i.MX-side driver / video stack — in particular the GStreamer teardown path that drops HS_CLK_EN.

+
Generated from session 20260515_135656 by make_flicker_report.py on 2026-05-15 16:26. Source data: 11 burst captures with burst_NNNN_*_pll_samples.json, burst_NNNN_*_rail.csv, and burst_NNNN_*_mipi_segNNN_clk/dat.csv files in data/flicker_bursts/20260515_135656.
\ No newline at end of file diff --git a/rebuild_eye.py b/rebuild_eye.py new file mode 100644 index 0000000..40aebac --- /dev/null +++ b/rebuild_eye.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +""" +Rebuild the folded CLK+ eye diagram for the v2 report. + +The original plot_eye() in make_flicker_report.py looks for an LP-11 → HS +transition (CLK+ > 0.5 V then falling). In session 20260515_135656 the +captures landed entirely in HS state (CLK+ stays in ~0.07–0.36 V), so the +edge detector returned None for every segment and the plot rendered with +zero overlays. + +This script auto-detects the common mode per segment and folds around +every crossing of common mode — which is what the eye really wants. +""" + +from __future__ import annotations + +from pathlib import Path +import numpy as np +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +ARRIVE_PURPLE = "#5f016f" +ARRIVE_PURPLE_DARK = "#3e0049" + +SESSION = Path("data/flicker_bursts/20260515_135656") +BURST = 15 +N_SEGS = 20 +UI_NS = 2.315 +OUT = Path("flicker_investigation_report_v2_plots/mipi_typical_eye.png") + + +def fold_segment(t_ns: np.ndarray, v_mv: np.ndarray, ui_ns: float, + ax: plt.Axes) -> int: + """Overlay every common-mode crossing in this segment as a ±1 UI slice.""" + cm = float(np.median(v_mv)) + above = (v_mv > cm).astype(int) + edges = np.where(np.diff(above) != 0)[0] + n = 0 + for e in edges: + t_cross = t_ns[e] + mask = (t_ns >= t_cross - ui_ns) & (t_ns <= t_ns[e] + ui_ns) + if mask.sum() < 3: + continue + ax.plot(t_ns[mask] - t_cross, v_mv[mask] - cm, + color=ARRIVE_PURPLE, linewidth=0.4, alpha=0.18) + n += 1 + return n + + +def main() -> None: + clk_files = sorted(SESSION.glob(f"burst_{BURST:04d}_*_mipi_seg*_clk.csv")) + if not clk_files: + raise SystemExit(f"no CLK files for burst {BURST} in {SESSION}") + + fig, ax = plt.subplots(figsize=(8.5, 3.0)) + total_segs = 0 + total_xings = 0 + for f in clk_files[:N_SEGS]: + arr = np.genfromtxt(f, delimiter=",") + t_ns = arr[:, 0] * 1e9 + v_mv = arr[:, 1] * 1000 + n = fold_segment(t_ns, v_mv, UI_NS, ax) + if n: + total_segs += 1 + total_xings += n + + ax.axhline(0, color="grey", linewidth=0.4, alpha=0.5) + ax.axvline(-UI_NS / 2, color="grey", linestyle=":", linewidth=0.4, alpha=0.5) + ax.axvline(+UI_NS / 2, color="grey", linestyle=":", linewidth=0.4, alpha=0.5) + ax.set_xlabel(f"time (ns, folded on UI = {UI_NS} ns)") + ax.set_ylabel("CLK+ − common-mode (mV)") + ax.set_xlim(-UI_NS, UI_NS) + ax.set_title( + f"CLK+ folded eye ({total_segs} segments × ~{total_xings // max(total_segs,1)} " + f"crossings overlaid on a 2-UI window, burst {BURST})", + color=ARRIVE_PURPLE, fontsize=11) + ax.grid(True, alpha=0.25) + ax.text(0.01, 0.95, + f"{total_segs} segments × ~{total_xings // max(total_segs,1)} cycles overlaid", + transform=ax.transAxes, fontsize=9, color=ARRIVE_PURPLE_DARK, + bbox=dict(facecolor="white", edgecolor="none", alpha=0.85), va="top") + + OUT.parent.mkdir(parents=True, exist_ok=True) + fig.tight_layout() + fig.savefig(OUT, dpi=140) + print(f"wrote {OUT} ({total_segs} segments, {total_xings} crossings)") + + +if __name__ == "__main__": + main()