MoonI80Peripheral
Source:
MoonI80Peripheral.h
MoonI80Peripheral¶
src/light/drivers/MoonI80Peripheral.h:28Inherits:
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.