LedPeripheral
Source:
LedPeripheral.h
LedPeripheral¶
src/light/drivers/LedPeripheral.h:39A parallel-WS2812 output peripheral, behind a runtime strategy interface.
ParallelLedDriver (the one MoonModule) owns the controls, lifecycle, tick, and the shared slice/encode/double-buffer/expander/loopback machinery, and drives ONE LedPeripheral chosen at runtime (Parlio, esp_lcd i80, or MoonI80 own-GDMA). The peripheral supplies only the variant operations — bring the bus up, hand back its DMA buffer, transmit a frame, tear down — plus a few static descriptors (lane count, expander support, bus-width rounding) as virtuals, so the orchestrator reads them through the one LedPeripheral* rather than a compile-time type.
Not a hot-path virtual boundary. Every method here is called per-FRAME or per-reinit, never per-light: the per-light encode operates on the raw uint8_t*``[busBuffer()] hands back and never calls into the peripheral. One vcall per frame against ~thousands of µs of frame work is free — the dispatch that mattered (per-light) stays a direct call inside the shared encode.
Shared state via a back-pointer. A backend reaches the orchestrator's parsed lane list, latch bit, correction, and loopback pin through owner() (set once by attach()), replacing what CRTP inheritance gave for free. The orchestrator exposes exactly those as public const accessors.
Prior art: the Strategy / pluggable-backend pattern; the projectMM ListSource / DevicePlugin adapter shape (a generic owner + a variant object that travels with its own state).
Public Methods¶
inline void attach(ParallelLedDriver * owner)
: Bind the peripheral to its orchestrator.
virtual uint8_t lanesAvailable() const
: Parallel lanes this peripheral's silicon provides on the current chip (0 = not this chip).
virtual bool supportsPinExpander() const
: Can this peripheral host the 74HCT595 pin expander? (Needs a DMA that reaches PSRAM.)
virtual inline bool supportsDoubleBuffer() const
: Can this peripheral run the async double-buffer (a second whole-frame buffer, encode overlaps wire)? Default yes — i80/Parlio route through a real transaction queue (esp_lcd / the Parlio driver), so a second in-flight transfer is handled for us.
virtual bool powerOfTwoBus() const
: Does the bus width round up to a power of two (8/16), or is it the exact pin count?
virtual const char * initFailMsg() const
: The status message when bus init fails on this peripheral.
virtual inline bool busContentionCleared() const
: True when a PREVIOUS init failed only because another module held a peripheral this backend needs, and that peripheral is now free: the driver then rebuilds itself, so the loser of a contended claim recovers without the user touching anything.
virtual bool loopbackFullWidth() const
: Must the loopback self-test build a full-width bus (true) or can it run on a private 1-lane unit (false)? esp_lcd i80 / MoonI80 need the full width; Parlio can do a single lane.
virtual LedHwBlock hwBlock() const
: The physical peripheral block this backend drives — the orchestrator's claim guard refuses two live drivers on the same block.
virtual bool busInit(size_t frameBytes, bool wantSecondBuffer)
: Create the bus + its DMA buffer(s) sized for frameBytes; wantSecondBuffer requests the async double-buffer's second frame (allocated only if it fits).
virtual void busDeinit()
: Tear down the bus and its DMA buffer(s).
virtual uint8_t * busBuffer(uint8_t i)
: DMA buffer i (0/1) the encoder writes into; buffer 1 is null in single-buffer mode.
virtual size_t busCapacity() const
: Per-buffer byte capacity (fixed at bus creation; both buffers equal).
virtual bool busTransmit(uint8_t i, size_t bytes)
: Kick off the autonomous transfer of the first bytes of DMA buffer i; returns whether it started.
virtual bool busWait(uint8_t i, uint32_t ms)
: Block up to ms for buffer i's in-flight transfer to complete.
virtual uint32_t busLastTransmitUs() const
: The most recent DMA transfer's wire time (µs) — the WS2812 output floor.
virtual platform::RmtLoopbackResult busLoopback(const uint8_t * frame, size_t frameBytes, size_t dataBytes, uint8_t rowBits)
: Run the loopback self-test on this peripheral (each builds its own bus, per loopbackFullWidth).
virtual inline bool busInitRing(size_t, uint32_t)
: Bring the bus up as a streaming ring for totalRows rows of rowBytes; false if it won't fit.
virtual inline bool busTransmitRing()
: Send one frame on the ring (prime + arm + ISR refill). False if the ring isn't up.
virtual inline bool busIsRing() const
: Is the live bus a streaming ring?
virtual inline bool wantsRing() const
: Should reinit build a ring for the current config instead of the whole-frame path?
virtual inline const char * busRingMode() const
: The ring's active-mode label for the status line, or nullptr for the whole-frame path.
virtual inline void addRingControls(ControlList &)
: Append this peripheral's ring controls into the shared list. Default: none.
virtual inline void refreshBusKpi()
: Refresh any peripheral-specific read-only KPIs (the ring diagnostic). Default: none.
virtual inline bool snapHelperReady() const
: Is the core-0 fork-join snapshot helper up (dual-core prime)? Default: no helper.
virtual inline void addBusControls(ControlList &)
: Append this peripheral's bus-pin controls (WR/DC clock pins) into the shared list. Default: none.
virtual inline bool busControlTriggersBuild(const char *) const
: Does a change to control name require a bus rebuild (a bus pin changed)? Default: no.
virtual inline void recordBusPins()
: Snapshot the current bus pins so extraBusPinsCurrent can detect a later change. Default: none.
virtual inline bool extraBusPinsCurrent() const
: Are the recorded bus pins still current (no un-applied change)? Default: always current.
virtual inline const char * validateBusFatal() const
: A per-peripheral fatal validation (returns a status message) run before bus init. Default: ok.
virtual inline const char * validateBusPins(const uint16_t *, uint8_t) const
: A per-peripheral lane-pin validation (returns a warning) — e.g. a data pin colliding with WR.
virtual inline uint16_t clockPinForBus() const
: The GPIO the bus parks spare (unused) lanes on (only reached when powerOfTwoBus rounds the bus wider than the data-pin count — i80/MoonI80, which both override this to their WR pin).
virtual inline bool spareLanesNeedPad() const
: Must a spare (unused) bus lane be parked on a REAL GPIO? esp_lcd rejects an NC data pin, so the i80 backend has to give every lane a pad and parks the spares on WR: a ghost claim that drives a pin the board never wired.
virtual inline size_t dmaBudgetBytes() const
: The whole-frame DMA byte budget: 0 = "no bound" (PSRAM-capable).