Skip to content

MoonI80Peripheral

Source: MoonI80Peripheral.h

MoonI80Peripheral

class MoonI80Peripheral
src/light/drivers/MoonI80Peripheral.h:28

Inherits: LedPeripheral

A LedPeripheral backend: parallel WS2812B on the LCD_CAM peripheral, driven by our own DMA code.

Same peripheral, pins and wire contract as the i80 backend, differing underneath, and it buys a streamed frame and a 74HCT595 expander driving 8 strands per GPIO.

Prior art: hpwit's I2SClocklessVirtualLedDriver and his expander board, written fresh here. The wiring and the peripheral comparison are on the drivers page.

Public Attributes

int8_t clockPin = 10 : WR, the pixel clock, needed only by a 74HCT595 expander and unrouted otherwise.

bool useRing = true : Stream the frame as a ring of small internal buffers, or send it whole from one block.

bool ringAuto = true : Derive the ring geometry and write it into the controls, so the user sees real numbers.

uint8_t ringRows = : Lights per DMA buffer: the ring's grain, and the RAM against interrupt-rate lever.

uint8_t ringBufs = : How many buffers the DMA circulates; more depth costs more internal DMA RAM.

uint8_t ringPadUs = 0 : Inter-buffer zero-pad in µs, buying refill deadline at a linear frame-time cost.

bool shiftOverclock = false : Overclock the '595 shift clock from 20 MHz to 26.67 MHz, for short-wired rigs.

char ringDbgStr_ = "—" : Backing store for the read-only ringDbg control, the ring's one-line instrument.

Public Methods

virtual inline uint8_t lanesAvailable() const override : LCD_CAM lanes on this chip; 0 leaves the driver inert through the orchestrator's guards.

virtual inline bool powerOfTwoBus() const override : The i80 bus width is 8 or 16, which is a hardware fact of the peripheral.

virtual inline bool loopbackFullWidth() const override : The loopback carries its pattern on lane 0 of a full-width bus, not a private one.

virtual inline LedHwBlock hwBlock() const override : Claims the LcdCam block, which the esp_lcd i80 backend also uses, so only one runs.

virtual inline const char * initFailMsg() const override : Status text when the bus will not come up, so the cause is on screen not in a log.

virtual inline bool supportsPinExpander() const override : The expander needs a streaming backend, so it runs wherever this backend does.

virtual inline bool supportsDoubleBuffer() const override : No async double-buffer: the two-buffer completion handshake races and wedges the bus.

virtual inline uint16_t clockPinForBus() const override : The GPIO the orchestrator pads spare bus lanes with, which only shift mode routes.

virtual inline bool spareLanesNeedPad() const override : A spare lane needs no pad: this backend routes only the GPIOs it is handed.

virtual inline void addBusControls(ControlList & controls) override : Bind the bus controls; WR is bound always but shown only in expander mode.

virtual inline void addRingControls(ControlList & controls) override : Bind the output path, the ring's geometry and its instrument.

virtual inline void refreshBusKpi() override : Refresh the ringDbg diagnostic string once a second, leaving it alone off the ring.

virtual inline bool busControlTriggersBuild(const char * name) const override : Which controls need the bus rebuilt rather than re-encoded when they change.

virtual inline const char * validateBusFatal() const override : Refuse a bus configuration that cannot work, naming the reason.

virtual inline const char * validateBusPins(const uint16_t * lanes, uint8_t n) const override : Refuse a data lane that shares WR's GPIO, which would emit the clock as pixel data.

virtual inline bool busInit(size_t frameBytes, bool) override : Create the bus and its DMA buffers, scaling the pixel clock to the slot's shift width.

virtual inline void busDeinit() override : Tear the bus down, stopping this backend's own snapshot helper before it goes away.

virtual inline uint8_t * busBuffer(uint8_t i) override : The DMA buffer the encoder writes slice i into.

virtual inline size_t busCapacity() const override : How many bytes one DMA buffer holds.

virtual inline bool busTransmit(uint8_t i, size_t bytes) override : Start the DMA over buffer i, returning false when the bus refuses it.

virtual inline bool busWait(uint8_t i, uint32_t ms) override : Wait out buffer i's transmission, returning false on timeout.

virtual inline uint32_t busLastTransmitUs() const override : The measured wire time of the last frame, in µs.

virtual inline platform::RmtLoopbackResult busLoopback(const uint8_t * frame, size_t frameBytes, size_t dataBytes, uint8_t rowBits) override : Drive a frame and capture the wire back, so the self-test verifies what was emitted.

virtual inline bool wantsRing() const override : Whether reinit should build a ring for this config rather than the whole-frame path.

virtual inline bool busInitRing(size_t rowBytes, uint32_t totalRows) override : Bring the bus up as a streaming ring, so a frame too big for internal RAM never forms.

virtual inline bool busTransmitRing() override : Send one frame: prime the pool, fire the DMA, let the end-of-buffer ISR refill behind it.

virtual inline const char * busRingMode() const override : The ring's regime for the status suffix: primed before arming, or lapping behind the DMA.

virtual inline bool busIsRing() const override : Whether the bus came up as a ring, reporting what the platform built rather than asked.

virtual inline void recordBusPins() override : WR is part of the bus identity, so a change to it rebuilds the bus: not a data-pin edit.

virtual inline bool extraBusPinsCurrent() const override : Whether the extra bus pins still match the live bus, WR being bus identity here.

virtual inline bool snapHelperReady() const override : Whether the fork-join should engage: the helper is up and this caller runs on core 1.

inline void helperKick() : Publish this frame's prime job and wake the helper; always paired with helperJoin.

inline void helperJoin() : Block until the helper's half is primed; the fence that makes the fork-join safe.

inline void runHelperJob() : Prime the buffer range the kick published, on the helper or serially as the fallback.

inline void ensureSnapHelper() : Bring the helper task up on core 0, the ring's tick having core 1 under the split.

inline void stopSnapHelper() : Tear the helper down, so the worker cannot outlive the bus it primes into.

Public Static Methods

static inline void MM_RAMFUNC ringEncodeTrampoline(void * user, uint8_t * dst, uint32_t firstRow, uint32_t rowCount, bool closeFrame, bool needsPrefill) : The platform's encode seam: recover this and encode one slice into the ring buffer.

static inline void snapHelperTramp(void * user) : The worker's entry point: park, run the published job, mark done, repeat until stopped.

More info

Why our own DMA driver

esp_lcd re-arms the peripheral on every transaction, resetting it mid-stream, which suits an addressed LCD panel. WS2812 is one unbroken self-clocked bit stream, so a whole frame belongs in one contiguous DMA-reachable block, which is the cap this backend exists to lift.

The LCD peripheral clocks what the DMA feeds it and stops when the chain ends, so one gdma_start() over an arbitrarily long descriptor chain is a single gapless stream. Built on IDF's HAL and GDMA APIs, one level below esp_lcd rather than at the registers.

What streaming costs

The whole-frame path runs free once armed; the ring carries a deadline. Its refill runs from the end-of-buffer interrupt and must beat the wire at 28.8 µs per light at 20 MHz, which is why useRing is a switch.