Complete: 24 back-to-back ~11s captures over ~18 minutes, ~70% wire-time coverage. Every frame decoded, every pulse width checked. A flip is a single-bit pixel corruption (the RGB sparkle signature). Totals per lane below; iteration 22 excluded — its millisecond-scale “pulses” with lanes disagreeing are probe-handling artifacts (a shared-engine fault would hit all lanes identically, and the device heartbeat never blinked).
| Lane | Status | Frames (23 valid its) | Sparkle flips | Out-of-spec pulses | Median interval | Late |
|---|---|---|---|---|---|---|
| D1 | CLEAN | ~15,700 | 0 | benign only* | 16.65 ms | ~0.5% |
| D2 | CLEAN | ~15,700 | 0 | benign only* | 16.65 ms | ~0.5% |
| D3 | CLEAN | ~15,700 | 0 | benign only* | 16.65 ms | ~0.5% |
| D5 | CLEAN | ~15,700 | 0 | benign only* | 16.65 ms | ~0.5% |
| D6 | CLEAN | ~15,700 | 0 | benign only* | 16.65 ms | ~0.5% |
| — | NO SIGNAL | 6th strip — probe never made contact during the soak (D0/D4/D7 idle); shared-engine result makes it representative, but probe it for completeness | ||||
A real 80 µs window from the multi-lane capture. All six outputs are shifted out by one I2S peripheral, one time-slice per DMA word — lanes start within 125 ns of each other. Short high = ‘0’ bit (250 ns), long high = ‘1’ bit (750 ns).
Before: I2S FIFO underruns (DMA losing memory-bus arbitration to Ethernet bursts) held the line high out of spec — 0.5–2.5 µs pulses the LEDs read as flipped ‘1’ bits. After halving DMA bandwidth (250 ns slices): clean. Log scale.
This morning the render loop overran its 16.67 ms budget and skipped in bursts — the second mode at ~33 ms is a missed frame. Now the distribution collapses onto the frame period.
Each fix measured on the wire before moving on. Sparkle rate wandered over time with all builds before the granularity fix — it was a longstanding bus-contention bug, not introduced today.
| Build | Change | FPS | Skipped | Sparkles/frame | |
|---|---|---|---|---|---|
| 07:xx | baseline — stock WS2813 timing, 2 DMA buffers | 42.5 | 41% | 2.22 | |
| #1 | WS2812 timing + 4 DMA buffers | 52.0 | 14.5% | 0.76 | |
| #2 | + end-of-frame stop countdown (fixes tail truncation) | 49.5 | 21% | 0.82 | |
| #3 | + lwIP pinned to core 0 (commit 0e527da) | 56.0 | 7% | 1.14 | |
| #4 | pixel map + gamma moved into UDP task (render = memcpy, 1.19 ms → 24 µs) | 60.0 | 2–4% | 0.03–2.5 | |
| #5 | 250 ns pulse granularity — I2S DMA bandwidth halved (commit a3c0109) | 60.08 | 0.2% | 0.00 |
Measured on the Pi’s eth0 with a raw-socket sniffer (multicast loopback is disabled, so you can’t just join the group).
| Send rate | 60.2 fps · median 16.67 ms |
| Frame payload | 8,640 B = 6 pkts × 1,440 B |
| Groups | 239.192.0.1 + .63 :6868 |
| Framenum skips | 0 |
| Hiccups (13 min watch) | 188 / 46,800 intervals >25 ms (0.4%) watch |
| Device heartbeat | 0 gaps >1.5 s in 13 min STABLE |
| Analyzer | FX2 clone (Saleae-compatible) · sigrok fx2lafw |
| Sampling | 8 MHz × 8 ch · 125 ns resolution |
| Bit decode | high > 500 ns ⇒ ‘1’ · low > 50 µs ⇒ frame gap |
| Sparkle detector | single-channel jump ≥32 vs both neighbors, ≤2 bits set |
| Soak coverage | 22 × ~11 s captures, ~70% duty, 5–6 lanes |
| LED timing | 250/500/500 ns · 1.25 µs/bit · T0H 250 · T1H 750 |
Reading the pulse-width chart: in-spec ‘0’ bits sample as 250/375 ns and ‘1’ bits as 750/875–1000 ns; anything between or beyond is a generation glitch. The before-series bars from 1.1–2.6 µs are the FIFO underruns — the output holding its last level while the DMA starved. Validation details live in VALIDATION_PLAN.md (status log) in the pfx-firmware repo; captures and analysis scripts in the session scratchpad.