Skip to content

ParallelLedDriver

Source: ParallelLedDriver.h

ParallelLedDriver

class ParallelLedDriver
src/light/drivers/ParallelLedDriver.h:34

Inherits: 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.

ParallelLedDriver card

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.