Skip to content

RmtLedDriver

Source: RmtLedDriver.h

RmtLedDriver

class RmtLedDriver
src/light/drivers/RmtLedDriver.h:39

Inherits: DriverBase

Output driver: WS2812B-class addressable LEDs over the ESP32 RMT (Remote Control Transceiver) peripheral — one GPIO and one RMT TX channel per strand, fed consecutive slices of the source buffer (8-bit, GRB).

The default LED driver for classic-ESP32 and S3 board entries, and the readable EXAMPLE future LED drivers copy: a sibling of NetworkSendDriver (same DriverBase hooks, same per-light correction_.apply() guard, same once-allocated owned buffer sized off the hot path); only the emit differs — this fuses the correction + WS2812 symbol-encode into one pass (the encode is RmtSymbol.h, host-tested) then hands per-pin slices to the platform.

Wire contract — WS2812B: 1-wire NRZ at 800 kHz, no clock line. Each bit is a 1.25 µs cell that starts HIGH then drops LOW; the HIGH duration encodes the bit (0 = 350 ns high, 1 = 700 ns high), MSB-first per byte. Channel order (GRB, GRBW, …) is applied by Correction before the encode, so the encoder is order-agnostic. Frames latch on ≥300 µs idle-LOW. Timings live in LedDriverConfig, converted to RMT ticks from the granted resolution (~40 MHz), never hard-coded to one clock. The platform owns only the peripheral (platform::rmtWs2812*); on a chip without RMT TX channels those calls are inert stubs, so it compiles everywhere. The peripheral half uses the modern RMT driver (ESP-IDF 5.x "RMT v2": rmt_new_tx_channel / a copy encoder / rmt_transmit), not the legacy channel-numbered API — not a preference: the legacy driver was removed entirely in ESP-IDF v6 (the build IDF), so v2 is the only API that exists. On chips whose RMT has a DMA backend (SOC_RMT_SUPPORT_DMA — the P4; the classic ESP32 has none) the whole-frame loopback capture uses it.

Flicker on LEDs that should be off is almost always a data-line signal-integrity problem (3.3 V drive into a 5 V strip), not firmware — the "LED signal integrity" use-case guide has the confirm-firmware-innocent playbook (loopbackFrame, the TX-power sweep) and the electrical fixes.

RmtLedDriver card

Public Attributes

char pins = "" : Comma-separated GPIO list, one RMT TX channel per pin ("18,17,16").

char ledsPerPin = "" : Comma-separated lights-per-pin ("100,100,50"), matched to pins by position — each pin takes the next consecutive slice of the source buffer, in list order (pin 1 = [0,n₁), pin 2 = [n₁,n₁+n₂), …).

uint8_t timing = 0 : Wire timing.

uint16_t t0hNs = 350 : The three numbers behind timing, shown only when it is custom.

uint16_t t1hNs = 700

uint16_t periodNs = 1250

bool loopbackTest = false : On-device loopback self-test — RMT is a transceiver, so the driver verifies its own output on real silicon (replaces the old standalone test firmware).

int8_t loopbackTxPin = -1 : Optional TX override for the test: when set (>= 0), the loopback transmits on THIS pin in place of pins[0], so the test can run on a dedicated jumper without re-typing the operational pins.

int8_t loopbackRxPin = -1 : Jumper this to the TX pin for the test (unset = -1 by default; bench used pin 5).

bool loopbackFrame = false : Whole-frame stress variant: instead of a 24-bit burst, transmit a real frame the size of the first pin's slice, back to back, and bit-verify the WHOLE capture.

Public Methods

inline RmtLedDriver() : WS2812/SK6812 strips are physically GRB-wired, so a fresh RMT driver references the "GRB" preset by default (a strip attached to a freshly-flashed board shows correct colors).

virtual inline void defineDriverControls() override : Bind the driver's controls: the window (start/count), the pins and ledsPerPin text lists, and the loopback self-test controls (the TX/RX pin overrides and frame-stress flag are always bound but shown only in test mode).

virtual inline bool affectsPrepare(const char * name) const override : Changing the pin list or the per-pin counts re-parses and re-inits the RMT channels (live, not reboot-to-apply), so the pipeline-wide prepare sweep runs and parseConfig()/reinit() pick up the new lists.

virtual inline void onControlChanged(const char * name) override : React to a control change (runs off the render loop, in the HTTP/API handler context — a blocking self-test here is fine).

virtual inline void setup() override : Parse the config and (re)init the RMT channels.

virtual inline void release() override : Release the RMT channels and free the symbol buffer, then clear the shared fail/config-error state (DriverBase::release()).

virtual inline void prepare() override : Pure build (see MoonModule::prepare): re-parse, resize the symbol buffer, and (re)init the RMT channels off the hot path ([tick()] never allocates).

virtual inline void onCorrectionChanged() override : Preset toggle (RGB↔RGBW) changes outChannels without a structural rebuild — the per-pin symbol offsets scale with outChannels, so re-derive them too.

virtual inline void setSourceBuffer(Buffer * buf) override : Point the driver at the source frame buffer; re-parse (counts derive from its light count) and resize the symbol buffer to match.

virtual inline void tick() override : Per-tick output: fuse the correction and WS2812 symbol-encode in one pass over this driver's window, then start every pin's transmit before waiting on any, so the tick costs the longest strand rather than the sum.

inline bool waitForPins() : Wait on every pin that actually started, and report whether they all finished.

inline const uint32_t * symbolBuffer() const : Test-only accessors.

inline size_t symbolCapacity() const : Words allocated in the symbol buffer. Test-only.

inline uint8_t pinCount() const : Number of parsed output pins (0 = idle). Test-only.

inline nrOfLightsType pinLightCount(uint8_t i) const : Lights on pin i (0 if out of range). Test-only.

inline size_t pinSymbolOffsetWords(uint8_t i) const : Word offset of pin i's slice in the symbol buffer (0 if out of range). Test-only.

inline const LedDriverConfig & wireTimingForTest() const : The wire timing the timing control resolved to.

Public Static Attributes

constexpr uint8_t kMaxPins = 8 : Hard cap on the pin arrays: the largest RMT TX group of any supported chip (8 on classic ESP32; the S3 has 4 — enforced per target via maxPinsForTarget()).

constexpr uint8_t kTimingCustom = 3 : The index of custom in the table below: the one option that reads the three fields.

constexpr const char * kTimingOptions = { "800kHz WS2812B/SK6812", "400kHz WS2811", "800kHz WS2811 fast", "custom", } : The presets, in the order the select lists them.

constexpr uint32_t kResolutionHz = 40'000'000