Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 35 of 56

TX1812: addressable LEDs from RMT symbols

A single-wire LED family driven by generating its pulse train in hardware — and a strip whose whole memory footprint is fixed at elaboration.

Timing as data

The TX1812 — like the WS2812 "NeoPixel" family — is a single-wire, daisy-chainable RGB LED. Twenty-four bits of colour are clocked in MSB-first as a precisely timed pulse train: a 1 is long-high/short-low, a 0 is short-high/long-low, and a low period over 80 µs latches the frame.

That is exactly the job RMT exists for. The driver generates the waveform as one RMT symbol per data bit, so the timing is produced by hardware rather than by a delay loop that an interrupt could disturb.

A strip is sized at elaboration

type Color is record ... end record;
type Strip (Count : Positive) is limited private;

The Count discriminant fixes the whole footprint: a Strip carries both the Count-pixel colour buffer and the Count * 24 RMT-symbol frame buffer. Declaring Panel : Strip (64) reserves all of it at elaboration — no heap — and the linker verifies it fits. You find out at link time that a strip is too big for your RAM, not at run time.

procedure Acquire (...; Channel : ...; Pin : ...; Blocks : ...);
function  Is_Valid (S : Strip) return Boolean;
procedure Set     (S : in out Strip; Index : Positive; C : Color);   --  buffered
procedure Set_All (S : in out Strip; C : Color);                     --  buffered
procedure Show    (S : in out Strip);                                --  clock out + latch
procedure Release (S : in out Strip);

A Strip is a claimed handle in the usual style: it takes an RMT transmit channel on Acquire and releases it on scope exit. Set and Set_All only write the buffer; nothing reaches the LEDs until Show.

The limitation, stated plainly

This driver currently drives a single LED. The underlying RMT.Transmit sends at most what fits in the channel's symbol RAM, so the practical case is Strip (Count => 1); passing Blocks (1 .. 4) borrows extra RMT RAM for roughly Blocks * 2 LEDs — and borrowing blocks costs you the higher-numbered RMT channels, as step 20 explains.

A longer string needs RMT wrap/refill support, which is a later step. The API is already shaped for it — Count, per-pixel Set — so only Show's transport changes when it lands. Worth knowing before you design a panel around it.