diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c381bb5
Binary files /dev/null and b/.gitignore differ
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 @@
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:
| Channel | Instrument | What it captures |
|---|---|---|
| 1V8 supply rail | Rigol 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 DSO80204B | 100 segments × 20 µs at 5 GSa/s, LP-edge triggered at line rate (~48 kHz) |
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:
+CLK+ − CLK− view would collapse LP-11 and LP-00 to ~0 V and erase the ~1 V absolute level needed to verify the SoT preamble. For any test that touches LP→HS transitions, single-ended is mandatory.SOT_BIT_ERR, LP_ERR, ECC or CRC flags — all of which were polled across the full test and remained zero. The receiver itself acts as an in-circuit differential integrity monitor; the scope didn't need to duplicate it.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.
| Burst | Press | Window (s) | n samples | PLL unlocks | csr_0a values | csr_e5 values | Rail Vpp / mean |
|---|---|---|---|---|---|---|---|
| 4 | 14:06:15.772 | 2.17 | 103 | 0 | 0x85=103 | 0x00=103 | 120 mV / 1764.6 mV |
| Flicker caused by MIPI protocol errors at SN65 input | Not supported | Zero 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 unlock | Partial support — explains ~18% of cases | 2 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) |
From a hardware engineering standpoint the data narrows the remaining candidates for the fault to areas downstream of (or inside) the SN65DSI83 bridge:
-csr_0a and csr_e5) are exposed by the current device-side HTTP endpoint, so the bulk of the bridge's state during a flicker event is not directly observable here. Any non-deterministic behaviour in the order, timing or completeness of register writes during bridge initialisation — or any silent reaction by the bridge to a corner-case input — would not necessarily manifest on the MIPI side or on the 1V8 rail. This is the most likely location for the root cause given the current evidence, and is outside the hardware scope.The two recommended actions are:
-csr_0a/csr_e5) would also give visibility of any runtime drift in those registers.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.
| Run | Duration | PLL unlocks | Rate | I²C read errors |
|---|---|---|---|---|
| Hold (no cycling) | 650.9 s | 0 | 0.0/min | 0.0 % |
| Run | Duration | Cycles | PLL unlocks | Unlocks / cycle |
|---|---|---|---|---|
| 30 unlock-recovery pairs | ~9 min | ~54 | 30 | 0.56 |
| Controlled (17 cycles) | 177 s | 17 | 8 | 0.47 |
| Metric | Value | Notes |
|---|---|---|
| min | 14.5 ms | under 1 frame at 60 Hz |
| median | 21.3 ms | ~1.3 frames |
| p90 | 40.0 ms | ~2.4 frames |
| max | 44.5 ms | ~2.7 frames |
+ 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.
+ +Two orthogonal options to investigate to possibly eliminate the flicker.
+ +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.
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).
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.
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.
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.