ParallelLedDriver
Source:
ParallelLedDriver.h
ParallelLedDriver¶
src/light/drivers/ParallelLedDriver.h:34Inherits:
DriverBase
The registered parallel WS2812B driver: up to 16 strands clocking out at once, one GPIO lane each, fed consecutive slices of the source buffer over whichever peripheral the control selects.
Backends: [I80Peripheral.h], [MoonI80Peripheral.h], [ParlioPeripheral.h].
The whole frame is encoded up front and shipped as one autonomous transfer. So there is no CPU deadline while it is on the wire. The encode is a fused correct and transpose, per row ([ParallelSlots.h]). Vocabulary: strand, lane, slot, row, under More info → Terminology.

Public Attributes¶
char pins = ""
: Comma-separated GPIO list, one parallel lane per pin, each fed a slice of this driver's window.
char ledsPerPin = ""
: Comma-separated lights per lane; the remainder splits evenly over the rest. Empty splits all.
bool doubleBuffer = true
: Encode the next frame while the current clocks out, so a tick costs max(encode, wire).
bool ringSnapshot = true
: Freeze the source each frame so the ring's off-thread refill reads an immutable copy.
bool loopbackTest = false
: On-device loopback self-test: transmit a known pattern and bit-verify the capture.
int8_t loopbackTxPin = -1
: TX override for the self-test; unset (-1) falls back to lane 0.
uint8_t loopbackStrand = 0
: Which strand carries the test pattern, shift mode only; direct mode always uses lane 0.
bool loopbackIntrusive = false
: Loopback mode: ON rides the live pipeline, OFF rebuilds a private bus for the test.
int8_t loopbackRxPin = -1
: Jumper this to the TX lane for the self-test; unset (-1) by default.
bool pinExpander = false
: Is a 74HCT595 shift-register expander fitted? Each data pin then drives 8 strands.
int8_t latchPin = -1
: The 74HCT595 latch (RCLK), pulsed once the shifted byte is in. Unset (-1) until wired.
Public Methods¶
inline void setPeripheralForTest(LedPeripheral * p)
: Test-only: borrow a mock backend, dropping any existing one. The caller keeps ownership.
inline void publishHeapBytesForTest()
: Recompute the heap total for the module readout; a test with no alloc site asks for it.
inline ParallelLedDriver()
: A fresh driver takes the GRB preset (WS2812 wiring) and the first backend this chip supports.
inline ~ParallelLedDriver() override
: Free the owned backend; a test-borrowed mock is left alone.
inline bool pinExpanderMode() const
: Is the shift-register expander engaged, after the peripheral's own capability?
inline uint8_t outputsPerPin() const
: Strands per physical data pin: 1 direct, or the '595's width through the expander.
virtual inline void defineDriverControls() override
: Bind the controls: the invariant block, the peripheral selector, then that backend's own.
virtual inline bool affectsPrepare(const char * name) const override
: Which control changes re-parse and re-init the bus live, through the prepare sweep.
virtual inline void onControlChanged(const char * name) override
: React to a control change off the render loop; loopbackTest re-runs while it is on.
virtual inline void setup() override
: One-time wiring: parse the lane lists. The bus acquire lives in [prepare()].
virtual inline void release() override
: Deinit the bus, then clear the shared fail and config-error state.
inline void freeSnapshot()
: Free the ring snapshot buffer, clear the encode source, refresh the memory readout.
virtual inline void prepare() override
: Pure build: re-parse the lanes and re-init the bus, off the hot path.
virtual inline void onCorrectionChanged() override
: Re-init when a channel-count change resizes the frame, and only then.
virtual inline void setSourceBuffer(Buffer * buf) override
: Point the driver at the source frame buffer and re-parse the lane config.
virtual inline void tick() override
: Per-tick output: correct and transpose each row into the DMA buffer, then ship one transfer.
inline void tickSync(uint8_t outCh)
: Blocking path: encode, send, wait out the wire, so a tick costs encode plus wire.
inline void tickAsync(uint8_t outCh)
: Deferred-wait path: encode N+1 while N clocks out, so a tick costs max(encode, wire).
inline void tickRing(uint8_t)
: Streaming-ring path: wait, arm the next frame, and return with the wire still running.
virtual inline void tick1s() override
: Refresh the frameTime KPI once a second: the measured wire time and its fps ceiling.
inline bool busWaitIfBusy(uint8_t i)
: Wait for buffer i if a transfer is in flight; false means it wedged. A no-op when idle.
inline void reportOverCapacity(uint8_t outCh, size_t cap)
: How long a transfer gets before it counts as dead: derived from the frame, never a constant.
inline bool busGaveUp()
: Whether the bus has failed for long enough to stop trying, reported once.
inline uint32_t waitBudgetMs() const
: How long a transfer gets before it counts as dead, derived from the frame, never a constant.
inline void drainInFlight()
: Block until nothing reads the DMA buffers or the snapshot: the barrier a live resize needs.
inline void prefillShiftConstantsIfNeeded()
: Prefill both DMA buffers' shift-mode constants; a no-op in direct mode.
template<class Slot> inline void prefillShiftFrame(uint8_t outCh, uint8_t * dst)
: Write the shift-mode frame constants into every DMA buffer, once, off the hot path.
template<class Slot> inline void MM_RAMFUNC prefillShiftRows(uint8_t outCh, uint8_t * dst, nrOfLightsType firstRow, nrOfLightsType rowCount)
: Prefill the shift constants for a row range; rowCount == 0 means to the end.
template<class Slot> inline void MM_RAMFUNC encodeRows(uint8_t outCh, uint8_t * dst, nrOfLightsType firstRow = 0, nrOfLightsType rowCount = 0, bool closeFrame = true)
: Encode a row range into dst, dst-relative; only the last slice may set closeFrame.
inline void MM_RAMFUNC encodeFrameClose(uint8_t * dst)
: Write only the frame-closing latch word, which presents the register's final slot.
template<class Slot> inline void encodeLoopbackFrame(uint8_t * frame, const uint8_t * wire, uint8_t outCh, nrOfLightsType lights)
: Build the direct-mode self-test frame: the known pattern on lane 0, every other lane idle.
template<class Slot> inline void encodeLoopbackFrameShift(uint8_t * frame, const uint8_t * wire, uint8_t outCh, nrOfLightsType lights)
: Build the expander self-test frame, latch and all, so the capture proves the real wire.
inline uint8_t laneCount() const
: Test-only accessors: pin the lane slicing and frame-size arithmetic on the host.
inline uint8_t activeForTest() const
: Which DMA buffer this tick encodes into, 0 or 1. Test-only.
inline bool inFlightForTest(uint8_t i) const
: Is buffer i's DMA transfer outstanding (awaiting its wait)? Test-only.
inline nrOfLightsType laneLightCount(uint8_t i) const
: Lights on lane i (0 if out of range). Test-only.
inline bool MM_RAMFUNC uniformLaneCounts() const
: Are all POPULATED strands the same length? Gates the ring's prefill-skip.
inline nrOfLightsType laneStart(uint8_t i) const
: First light index of lane i's slice (0 if out of range). Test-only.
inline nrOfLightsType maxLaneLights() const
: Length of the longest lane: the frame's row count. Test-only.
inline size_t frameBytes() const
: Total DMA frame size in bytes (rows + latch pad). Test-only.
inline uint8_t latchBit() const
: Bus-bit index of the latch line, shift mode only; a backend reads it through the owner.
inline const uint16_t * laneList() const
: The parsed physical data GPIOs, bus-width bound, for a backend's encode trampoline.
inline const Correction & correction() const
: The live output Correction, which a backend's encode and prefill helpers read through.
inline bool & ringSnapshotRef()
: Mutable reference to the ring-snapshot knob, for a backend's addRingControls to bind.
inline const uint8_t * snapshotBuf() const
: The ring snapshot buffer, null off the ring path; a backend's KPI reads its residency.
inline const Buffer * sourceBuffer() const
: The wired source buffer (null before setSourceBuffer): same KPI-residency use as [snapshotBuf()].
virtual inline LedHwBlock hwBlock() const override
: The hardware block this driver is DRIVING, for the sibling claim guard.
inline const uint16_t * busPinList()
: The bus-geometry accessors a backend builds its bus from.
inline uint8_t busPinCount() const
: How many lanes the PERIPHERAL is handed.
inline uint8_t busClockMultiplier() const
: How much faster the bus clocks per slot: a '595 needs 8 shift cycles for the same 375 ns.
inline uint8_t slotBytes() const
: Bytes per bus slot, keyed on the PHYSICAL pin count rather than the lane count.
Public Static Attributes¶
constexpr uint8_t kMaxPeripherals = 4
: How many backends can register, sized to the most any one chip links in.
PeripheralEntry peripheralRegistry_ = {}
uint8_t peripheralRegistryCount_ = 0
: How many of the registry's slots are filled, so registration stops at the cap.
constexpr uint8_t kMaxLanes = 16
: Max parallel lanes: the peripheral's 16 data lines. Strands can exceed it, see kMaxStrands.
constexpr uint8_t kMaxStrands = 64
: Max strands: every data line fanned out through its '595 chain. Direct mode uses kMaxLanes.
constexpr nrOfLightsType kLoopbackTestLights = 256
: Light count the loopback self-test drives, or the strand length when that is smaller.
volatile uint32_t dbgTickWaitUs = 0
: Microseconds the ring tick spent waiting for the peripheral, for ringDbg.
volatile uint32_t dbgTickSnapUs = 0
: Microseconds the ring tick spent snapshotting the frame, for ringDbg.
volatile uint32_t dbgTickPrimeUs = 0
: Microseconds the ring tick spent priming buffers, for ringDbg.
uint32_t dbgSegGatherCy = 0
: Cycles spent gathering a row, accumulated for ringDbg.
uint32_t dbgSegEmitCy = 0
: Cycles spent transposing and emitting a row, accumulated for ringDbg.
uint32_t dbgSegRows = 0
: How many rows the two counters above cover, so ringDbg can average them.
Public Static Methods¶
static inline bool registerPeripheral(const char * label, PeripheralFactory make)
: Register a backend factory under a UI label, once per linked backend at static-init.
static inline bool isTestParamControl(const char * name)
: Loopback parameters: no bus rebuild, but a change re-runs a running test.
Public Types¶
using PeripheralFactory = LedPeripheral *(*)()
More info¶
Why single-shot¶
Encoding the whole frame first removes the deadline: a driver refilling as the DMA drains must beat the clock every time, and a WiFi interrupt garbles the rest. MoonI80Peripheral's ring gives this up to hold a frame too big for memory.
Terminology¶
A strand is one chain of LEDs. A lane is one bus data line: in the normal case, one GPIO driving one strand. A slot is one WS2812 bit on the wire. A row is one light across every strand at once, so a frame is maxLaneLights rows.
The frame transpose (correct + transpose, per row)¶
The source holds each light's bytes together; the wire needs each bus WORD to carry one bit of EVERY strand at once. The encoder turns 8 lights on their side, an 8x8 bit matrix transpose, writing one word per slot fused with the correction. A Parlio bus word and an i80 bus word have the same meaning.