- Quick Start
- Colour Representation
- Driver Architecture
- Enumerations
- Structures
- Library API
- Advanced Features
- Utilities
- Performance Considerations
- Optimization Tips
- Troubleshooting
- Usage Examples
- Version History
Initialization Sequence
The correct calling sequence for initialization is:
- Create LiteLED object (constructor)
- Call a begin() method to initialize hardware
- Optionally set brightness
- Set pixel colours
- Call show() to update the LED strip
#include <LiteLED.h>
// Define LED strip parameters
#define LED_TYPE LED_STRIP_WS2812
#define LED_GPIO 14
#define LED_COUNT 30
#define LED_IS_RGBW 0
// Create LiteLED object
LiteLED strip(LED_TYPE, LED_IS_RGBW);
void setup() {
// Initialize the strip
strip.begin(LED_GPIO, LED_COUNT);
// Set brightness (0-255)
strip.brightness(50);
// Set all LEDs to red and show
strip.fill(rgb_from_code(0xFF0000), true);
}
void loop() {
// Your animation code here
}The intensity and colour of a LED is defined by setting a value for each of its red, blue and green channels. Values range between 0 and 255, where 0 is off and 255 is full on. By adjusting the values of each channel, different colours and intensities result.
With LiteLED, colours are defined in two ways:
As an RGB colour structure
In this way colours are defined as a structure of type rgb_t where a member of the structure represents the intensity of the red, blue and green channels for a particular LED. Members can be accessed using either .r, .b, .g or .red, .blue, .green notation.
Example:
Define a colour:
rgb_t myColour = { .r = 47, .g = 26, .b = 167 };
Set the green channel of a colour variable:
myColour.green = 76;
As an RGB colour code
In this way colours are defined as type crgb_t where the colour is represented by a 24-bit value within which eight bits are assigned for the intensity of the red, blue and green channels for a particular LED in the form 0xRRGGBB.
Example:
Define a colour:
crgb_t myOtherColour = 0xff0000; // pure red
crbg_t yetAnotherColour = 0xafafaf; // white-ish
Notes:
-
Though not required, hex notation is typically used when defining
crgb_tcolours as it makes the values for each of the channels easier to see. -
Once defined, a colour cannot be accessed as the other type. For example,
crgb_t myOtherColour = 0xff0000;
myOtherColour.blue = 123; // oops - no can dowill produce an error at line 2 as myOtherColour is defined as type crgb_t and the statement is attempting to change the blue channel using rgb_t notation.
See also the Kibbles and Bits section below.
LiteLED can drive RGBW strips like SK6812 RGBW types however there is no direct method for setting the value of the W channel. By default LiteLED will automatically set the value of the W channel based on some behind the scenes magic derived from the R, G, and B values for that LED. Thus by default the R, G, B, and W LED's will illuminate based on the values set.
This behaviour can be disabled when initializing the strip in the begin() method. When disabled, the value of the W channel is set to 0 and the white LED will not illuminate. Given that RGBW strips are available with many choices for the colour temperature of the W LED, give it a shot both ways and pick the one that looks good to you.
LiteLED does not support RGBWW (dual white channel) type strips.
LiteLED provides three driver classes that share a common pixel-manipulation API. Choose the driver that matches your hardware and concurrency requirements.
LiteLED drives LED strips through the ESP32 RMT (Remote Control Transceiver) peripheral. When show() is called, an ESP-IDF RMT encoder callback runs — converting the pixel colour buffer to precise timing waveforms on-the-fly — and loads them into RMT symbol memory or (where available) a DMA buffer. The RMT peripheral then autonomously clocks out the waveform with nanosecond timing accuracy.
Because encoding is performed inside a callback, there is no pre-allocated bitstream buffer and the RAM cost per strip is just the pixel colour buffer (3 or 4 bytes per LED). Multiple instances can run concurrently, each on its own RMT channel, with independently configurable DMA and interrupt priority.
- SoC support: All ESP32 variants with an RMT peripheral (ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2, …)
- Maximum instances: Up to 8 concurrent strips (one RMT TX channel each); the exact limit depends on the SoC
- DMA: Optional — available on ESP32-S2, S3, C3, H2; not available on original ESP32 or C6
- Interrupt priority: Configurable on arduino-esp32 core v3.2.0+
- Your target SoC does not have PARLIO (
SOC_PARLIO_SUPPORTEDis absent) - You need more than one independent strip and your SoC has only one PARLIO TX unit (e.g., ESP32-C6)
- You want to mix RMT strips with PARLIO strips in the same application
- You prefer the smallest possible pre-allocated heap footprint per strip
| ✅ | Available on all ESP32 SoCs with RMT |
| ✅ | Up to 8 concurrent independent strips |
| ✅ | Minimal heap: only the pixel colour buffer (3–4 B/LED) |
| ✅ | Optional DMA and configurable interrupt priority |
| Each strip consumes one RMT TX channel | |
| Interrupt-driven; priority conflicts possible with multiple strips at high update rates | |
| RMT DMA not available on all SoC variants |
class LiteLED {
public:
LiteLED(led_strip_type_t led_type, bool rgbw);
~LiteLED();
esp_err_t begin(uint8_t data_pin, size_t length, bool auto_w = true);
esp_err_t begin(uint8_t data_pin, size_t length, ll_psram_t psram_flag, bool auto_w = true);
esp_err_t begin(uint8_t data_pin, size_t length, ll_dma_t dma_flag,
ll_priority_t priority, ll_psram_t psram_flag, bool auto_w = true);
esp_err_t show();
esp_err_t clear(bool show = false);
esp_err_t brightness(uint8_t bright, bool show = false);
uint8_t getBrightness();
esp_err_t setPixel(size_t num, rgb_t color, bool show = false);
esp_err_t setPixel(size_t num, crgb_t color, bool show = false);
esp_err_t setPixels(size_t start, size_t len, rgb_t *data, bool show = false);
esp_err_t setPixels(size_t start, size_t len, crgb_t *data, bool show = false);
esp_err_t fill(rgb_t color, bool show = false);
esp_err_t fill(crgb_t color, bool show = false);
esp_err_t fillRandom(bool show = false);
rgb_t getPixel(size_t num);
crgb_t getPixelC(size_t num);
esp_err_t setOrder(color_order_t led_order = ORDER_GRB);
esp_err_t resetOrder();
bool isValid() const;
int getGpioPin() const;
static bool isGpioAvailable(uint8_t gpio_pin);
static uint8_t getActiveInstanceCount();
};If you're curious about how the library is structured internally, see LiteLED Architecture.md in the docs folder of the library repository.
LiteLEDpio drives LED strips through the ESP32 PARLIO (Parallel IO) TX peripheral with GDMA (General DMA). When show() is called, the entire LED waveform is pre-encoded into a DMA bitstream buffer — each input byte expands to 24 DMA bytes (8 bits × 3 sample bytes per bit), with a reset tail appended — and a single GDMA transfer streams that buffer to the PARLIO TX unit. The CPU is not involved once transmission starts; there is no interrupt service routine.
The API is deliberately identical to LiteLED. Switching between the RMT and PARLIO driver requires changing only the class name in the type declaration.
- SoC support: Only on SoCs where
SOC_PARLIO_SUPPORTEDis defined (ESP32-C6, ESP32-H2, ESP32-P4, and later parts). The class is conditionally compiled out on unsupported targets. - Maximum instances: 1 active
LiteLEDpio(orLiteLEDpioGroup) per PARLIO TX unit. On ESP32-C6 and ESP32-H2 there is one PARLIO TX unit, so only one PARLIO instance can be active at a time. Combine withLiteLEDRMT instances for additional independent strips. - DMA control: PARLIO always uses GDMA — there is no DMA on/off flag.
- Interrupt priority: No ISR during transmission — no priority parameter.
- RGBW support: Identical to
LiteLED: passtruefor thergbwconstructor argument and use an RGBW-capableled_strip_type_t.
- You need a single-strip PARLIO driver with the same API as
LiteLED - You want GDMA-driven transmission with zero CPU overhead during frame output
- You are migrating an existing
LiteLEDapplication to a PARLIO-capable SoC and want a one-line code change - You are driving one strip and want the simplest possible setup
| ✅ | GDMA transfer — zero CPU overhead during transmission |
| ✅ | No ISR — no interrupt priority configuration or conflicts |
| ✅ | API-identical to LiteLED; one-line driver swap |
| ✅ | PSRAM support for the pixel colour buffer |
SOC_PARLIO_SUPPORTED targets only |
|
| One active instance per PARLIO TX unit | |
| Large pre-encoded DMA bitstream buffer (~72 B/LED for RGB, always in internal RAM) |
class LiteLEDpio {
public:
LiteLEDpio(led_strip_type_t led_type, bool rgbw);
~LiteLEDpio();
// No DMA flag or interrupt priority (PARLIO always uses GDMA; no ISR)
esp_err_t begin(uint8_t data_pin, size_t length, bool auto_w = true);
esp_err_t begin(uint8_t data_pin, size_t length, ll_psram_t psram_flag, bool auto_w = true);
esp_err_t show();
esp_err_t clear(bool show = false);
esp_err_t brightness(uint8_t bright, bool show = false);
uint8_t getBrightness();
esp_err_t setPixel(size_t num, rgb_t color, bool show = false);
esp_err_t setPixel(size_t num, crgb_t color, bool show = false);
esp_err_t setPixels(size_t start, size_t len, rgb_t *data, bool show = false);
esp_err_t setPixels(size_t start, size_t len, crgb_t *data, bool show = false);
esp_err_t fill(rgb_t color, bool show = false);
esp_err_t fill(crgb_t color, bool show = false);
esp_err_t fillRandom(bool show = false);
rgb_t getPixel(size_t num);
crgb_t getPixelC(size_t num);
esp_err_t setOrder(color_order_t led_order = ORDER_GRB);
esp_err_t resetOrder();
bool isValid() const;
int getGpioPin() const;
static bool isGpioAvailable(uint8_t gpio_pin);
static uint8_t getActiveInstanceCount();
};Switching between drivers is a one-line change:
LiteLED myStrip(LED_STRIP_WS2812, false); // RMT driver
LiteLEDpio myStrip(LED_STRIP_WS2812, false); // PARLIO driver — identical APILiteLEDpioGroup drives up to 8 independent LED strips from a single PARLIO TX unit by multiplexing each strip onto one bit-lane of the PARLIO data bus. Each strip's colour data is stored in a private per-lane pixel buffer, accessible via a LiteLEDpioLane handle returned by addStrip().
When show() is called, LiteLED encodes all lanes' pixel buffers into a single shared DMA bitstream buffer — each lane's signal occupies one bit position in every DMA byte — then issues one GDMA transfer that updates all strips simultaneously.
The critical consequence: every show() transmits all lanes. There is no per-lane-only transmit. This guarantees frame-perfect synchronisation across all connected displays with minimal RAM overhead.
LiteLEDpioLane is a thin handle for one lane within a group. It is owned and managed by the group; user code obtains a reference from addStrip() or operator[]. Calling show() on a lane is identical to calling show() on the parent group — all lanes always transmit together.
- SoC support:
SOC_PARLIO_SUPPORTEDonly (same asLiteLEDpio) - Maximum lanes:
PARLIO_TX_UNIT_MAX_DATA_WIDTH— 8 on ESP32-C6 / ESP32-H2, 16 on ESP32-P4 - Shared constraints: All strips in a group must share the same
led_strip_type_t, strip length, and RGBW flag - One active group: Only one
LiteLEDpioGroup(orLiteLEDpio) can be active at a time on C6/H2 - DMA buffer: One shared buffer regardless of lane count; size depends on strip length only
- You need to drive 2–8 independent LED strips simultaneously from one PARLIO TX unit
- All your strips share the same LED type and length
- You want frame-perfect lock-step synchronisation across all displays at zero per-strip CPU cost
- You want to minimise internal DMA RAM (one shared DMA buffer vs. N separate buffers)
| ✅ | Up to 8 independent strips from one PARLIO TX unit |
| ✅ | Single shared DMA buffer — size does not grow with lane count |
| ✅ | Frame-perfect lock-step update across all strips |
| ✅ | Zero per-strip CPU overhead after setup |
| ✅ | Per-lane pixel colour buffers may reside in PSRAM |
SOC_PARLIO_SUPPORTED targets only |
|
| All strips must share the same LED type, length, and RGBW flag | |
No per-lane-only show — every show() transmits all lanes |
|
| One active group per PARLIO TX unit |
class LiteLEDpioGroup {
public:
LiteLEDpioGroup(led_strip_type_t led_type, size_t length, bool rgbw);
~LiteLEDpioGroup();
LiteLEDpioLane &addStrip(uint8_t gpio); // sequential lane assignment
template<uint8_t LANE>
LiteLEDpioLane &addStrip(uint8_t gpio); // explicit lane assignment
esp_err_t begin(ll_psram_t psram_flag = PSRAM_DISABLE);
esp_err_t show();
esp_err_t brightness(uint8_t bright, bool show = false);
uint8_t getBrightness();
LiteLEDpioLane &operator[](uint8_t lane);
bool isValid() const;
};
class LiteLEDpioLane {
public:
// show() always transmits all lanes in the parent group
esp_err_t show();
esp_err_t setPixel(size_t num, rgb_t color, bool show = false);
esp_err_t setPixel(size_t num, crgb_t color, bool show = false);
esp_err_t setPixels(size_t start, size_t len, rgb_t *data, bool show = false);
esp_err_t setPixels(size_t start, size_t len, crgb_t *data, bool show = false);
esp_err_t fill(rgb_t color, bool show = false);
esp_err_t fill(crgb_t color, bool show = false);
esp_err_t clear(bool show = false);
esp_err_t brightness(uint8_t bright, bool show = false);
uint8_t getBrightness();
rgb_t getPixel(size_t num);
crgb_t getPixelC(size_t num);
esp_err_t fillRandom(bool show = false);
esp_err_t setOrder(color_order_t led_order = ORDER_GRB);
esp_err_t resetOrder();
bool isValid() const;
};| Feature | LiteLED (RMT) |
LiteLEDpio (PARLIO) |
LiteLEDpioGroup (PARLIO) |
|---|---|---|---|
| SoC availability | All ESP32 with RMT | SOC_PARLIO_SUPPORTED |
SOC_PARLIO_SUPPORTED |
| Max concurrent strips | Up to 8 (one per RMT channel) | 1 per PARLIO TX unit | Up to 8 lanes per PARLIO TX unit |
| Strips share type / length? | No — each independent | No — single strip | Yes — all lanes must match |
| Encoding method | On-the-fly in RMT callback | Pre-encoded to DMA bitstream | Pre-encoded, all lanes merged |
| DMA buffer | None (optional RMT DMA) | Per-instance (~72 B/LED RGB) | One shared buffer (~72 B/LED RGB, any lane count) |
| CPU overhead during transmit | Interrupt callback | None (GDMA) | None (GDMA) |
| Interrupt priority config | Yes (core v3.2.0+) | Not applicable | Not applicable |
| DMA enable/disable | DMA_ON / DMA_OFF |
Always GDMA, no flag | Always GDMA, no flag |
Per-strip independent show() |
Yes | Yes | No — all lanes always together |
| PSRAM for pixel buffer | Yes | Yes | Yes (per-lane) |
| PSRAM for DMA buffer | No | No | No |
| RGBW support | Yes | Yes | Yes |
| API compatibility | — | Identical to LiteLED |
LiteLEDpioLane API identical to LiteLED |
Supported LED strip types.
LED_STRIP_WS2812 // WS2812/WS2812B (GRB colour order)
LED_STRIP_WS2812_RGB // WS2812 variant with RGB colour order
LED_STRIP_SK6812 // SK6812 (GRB colour order, with RGBW support)
LED_STRIP_APA106 // APA106 (RGB colour order)
LED_STRIP_SM16703 // SM16703 (RGB colour order)
Description:
Defines the LED strip type, which determines timing parameters and default colour order.
LED colour byte ordering.
ORDER_RGB. // Red, Green, Blue
ORDER_RBG // Red, Blue, Green
ORDER_GRB // Green, Red, Blue
ORDER_GBR // Green, Blue, Red
ORDER_BRG // Blue, Red, Green
ORDER_BGR // Blue, Green, RedDescription:
Specifies the byte order for transmitting colour data to LEDs. Most WS2812 strips use GRB order by default.
Default Colour Orders by LED Type:
- WS2812:
ORDER_GRB - WS2812_RGB:
ORDER_RGB - SK6812:
ORDER_GRB - APA106:
ORDER_RGB - SM16703:
ORDER_RGB
DMA_ON // Enable DMA for RMT transfers
DMA_OFF // Disable DMA (use internal RMT memory)
DMA_DEFAULT // Default behaviour - equivalent to DMA_OFF
Description:
Controls whether the RMT peripheral uses DMA for data transfers. DMA can improve performance for long LED strips.
Availability:
Only supported on ESP32 variants with RMT DMA support. The library will issue a compile-time warning if DMA is not available on the selected chip.
Note:
- Some ESP32 models do not support RMT DMA. Check the data sheet of the SoC.
- When using DMA, the total number of available RMT channels will be reduced.
RMT interrupt priority level
PRIORITY_DEFAULT // Default interrupt priority
PRIORITY_HIGH // High priority
PRIORITY_MED // Medium priority
PRIORITY_LOW // Low priorityDescription: Sets the interrupt priority for the RMT encoder callback. Higher priority ensures more precise timing for LED updates.
Availability: Only supported with arduino-esp32 core v3.0.2 or greater. The library will issue a compile-time warning if not available.
PSRAM buffer allocation preference.
PSRAM_DISABLE // Allocate LED buffer in internal RAM
PSRAM_ENABLE // Allocate LED buffer in PSRAM (if available)
PSRAM_AUTO // Automatically use PSRAM if availableDescription: Controls whether the LED buffer is allocated in internal RAM or external PSRAM. PSRAM is useful for large LED arrays (hundreds to thousands of LEDs).
Recommendations:
- Small arrays (<100 LEDs): Use
PSRAM_DISABLEfor best performance - Large arrays (>500 LEDs): Use
PSRAM_ENABLEorPSRAM_AUTO - Unknown requirements: Use
PSRAM_AUTOfor automatic selection
RGB colour representation (struct style).
typedef struct {
union {
uint8_t r;
uint8_t red;
};
union {
uint8_t g;
uint8_t green;
};
union {
uint8_t b;
uint8_t blue;
};
} rgb_t;Description: Represents an RGB colour with 8-bit values per channel (0-255).
Access Methods:
rgb_t color;
color.r = 255; // or color.red = 255;
color.g = 128; // or color.green = 128;
color.b = 0; // or color.blue = 0;RGB colour code (32-bit hex format).
typedef uint32_t crgb_t;Description: Represents an RGB colour as a 32-bit integer in format 0x00RRGGBB.
Examples:
crgb_t red = 0xFF0000;
crgb_t green = 0x00FF00;
crgb_t blue = 0x0000FF;
crgb_t white = 0xFFFFFF;This section documents every method in all three driver classes. Where a method exists in more than one class, all forms and their behavioural differences are listed together. An Applies to note at the start of each entry shows which classes provide the method.
Creates a new driver instance. No hardware is allocated until begin() is called. Default brightness is 255 (full).
LiteLED (RMT) and LiteLEDpio (PARLIO single-strip)
Both classes take identical constructor parameters.
LiteLED (led_strip_type_t led_type, bool rgbw);
LiteLEDpio(led_strip_type_t led_type, bool rgbw);| Parameter | Type | Description |
|---|---|---|
led_type |
led_strip_type_t |
LED strip protocol (see led_strip_type_t) |
rgbw |
bool |
true for RGBW strips (e.g., SK6812 RGBW); false for RGB strips |
LiteLEDpioGroup (PARLIO multi-strip)
The group constructor also takes the per-lane strip length because all lanes share the same length.
LiteLEDpioGroup(led_strip_type_t led_type, size_t length, bool rgbw);| Parameter | Type | Description |
|---|---|---|
led_type |
led_strip_type_t |
LED strip protocol — same for all lanes |
length |
size_t |
Number of LEDs per strip — same for all lanes |
rgbw |
bool |
true for RGBW strips; false for RGB |
Example:
// RMT driver — RGB WS2812
LiteLED strip(LED_STRIP_WS2812, false);
// PARLIO single-strip — same parameters, identical API
LiteLEDpio strip(LED_STRIP_WS2812, false);
// PARLIO multi-strip — strip length included in constructor
LiteLEDpioGroup panels(LED_STRIP_WS2812, 64, false);Releases all hardware resources and heap allocations. Safe to call even if begin() was never called or failed partially.
LiteLED
~LiteLED();Frees the LED colour buffer (RAM or PSRAM), releases the RMT channel and encoder, and unregisters the GPIO from the Peripheral Manager and the LiteLED registry.
LiteLEDpio
~LiteLEDpio();Waits for any in-progress GDMA transfer to complete, then disables and deletes the PARLIO TX unit, frees the pixel colour buffer and the DMA bitstream buffer, and unregisters the GPIO from the Peripheral Manager.
LiteLEDpioGroup
~LiteLEDpioGroup();Waits for any in-progress GDMA transfer, disables and deletes the PARLIO TX unit, frees all per-lane pixel colour buffers and the shared DMA bitstream buffer, and unregisters all lane GPIOs from the Peripheral Manager.
begin() allocates hardware resources, allocates heap buffers, and registers GPIOs. It must be called before any pixel operations or show().
All begin() overloads return ESP_OK on success, or an esp_err_t error code on failure.
Applies to: LiteLED
Three overloads are available.
esp_err_t begin(uint8_t data_pin, size_t length, bool auto_w = true);Allocates the pixel colour buffer in internal RAM and initialises the RMT channel with default settings (no DMA, default interrupt priority).
esp_err_t begin(uint8_t data_pin, size_t length, ll_psram_t psram_flag, bool auto_w = true);Allocates the pixel colour buffer in internal RAM or PSRAM according to psram_flag. Useful for large LED arrays (hundreds to thousands of LEDs).
esp_err_t begin(uint8_t data_pin, size_t length, ll_dma_t dma_flag,
ll_priority_t priority, ll_psram_t psram_flag, bool auto_w = true);Full control over RMT DMA, interrupt priority, and pixel buffer placement.
| Parameter | Type | Default | Description |
|---|---|---|---|
data_pin |
uint8_t |
— | GPIO pin connected to the strip DIN |
length |
size_t |
— | Number of LEDs in the strip |
dma_flag |
ll_dma_t |
DMA_DEFAULT |
RMT DMA mode (see ll_dma_t) |
priority |
ll_priority_t |
PRIORITY_DEFAULT |
RMT interrupt priority (see ll_priority_t) |
psram_flag |
ll_psram_t |
PSRAM_DISABLE |
Pixel colour buffer placement (see ll_psram_t) |
auto_w |
bool |
true |
RGBW strips only: true derives the W channel automatically from R/G/B values; false leaves W at 0 |
Notes:
- DMA availability and interrupt priority support vary by SoC and arduino-esp32 core version; see Compile-Time Warnings
- Use
DMA_ONonly after verifyingLiteLED_Utils::isDmaSupported(); the library will fall back gracefully but will log a warning
Example:
LiteLED strip(LED_STRIP_WS2812, false);
// Basic
strip.begin(14, 60);
// PSRAM for large array
strip.begin(14, 1000, PSRAM_AUTO);
// Full config — DMA + high priority + PSRAM
strip.begin(14, 500, DMA_ON, PRIORITY_HIGH, PSRAM_AUTO);Applies to: LiteLEDpio
Two overloads are available. There is no DMA flag or interrupt priority parameter — PARLIO always uses GDMA and has no ISR.
esp_err_t begin(uint8_t data_pin, size_t length, bool auto_w = true);Allocates the pixel colour buffer in internal RAM, allocates the DMA bitstream buffer in internal DMA-capable RAM, creates and enables the PARLIO TX unit.
esp_err_t begin(uint8_t data_pin, size_t length, ll_psram_t psram_flag, bool auto_w = true);| Parameter | Type | Default | Description |
|---|---|---|---|
data_pin |
uint8_t |
— | GPIO pin connected to the strip DIN |
length |
size_t |
— | Number of LEDs in the strip |
psram_flag |
ll_psram_t |
PSRAM_DISABLE |
PSRAM preference for the pixel colour buffer only |
auto_w |
bool |
true |
RGBW strips only: automatically derive W channel from R/G/B |
PSRAM and DMA buffer allocation:
The psram_flag controls the pixel colour buffer only. The DMA bitstream buffer is always allocated from internal DMA-capable RAM (MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL) — GDMA on C6/H2 cannot read PSRAM. The DMA buffer is approximately 72 bytes per LED (24 DMA bytes × 3 colour channels) plus a 1000-byte reset tail; for 64 LEDs this is ~5.6 KB.
Example:
LiteLEDpio strip(LED_STRIP_WS2812, false);
// Basic
strip.begin(21, 64);
// PSRAM for large pixel buffer
strip.begin(21, 300, PSRAM_AUTO);Applies to: LiteLEDpioGroup
One overload. GPIO pins are supplied per-lane via addStrip() before calling begin().
esp_err_t begin(ll_psram_t psram_flag = PSRAM_DISABLE);Allocates per-lane pixel colour buffers, allocates the shared DMA bitstream buffer in internal DMA-capable RAM, creates and enables the PARLIO TX unit, and registers all lane GPIOs with the Peripheral Manager.
| Parameter | Type | Default | Description |
|---|---|---|---|
psram_flag |
ll_psram_t |
PSRAM_DISABLE |
PSRAM preference for all per-lane pixel colour buffers. The shared DMA buffer is always internal RAM. |
Returns ESP_ERR_INVALID_STATE (with a log message) if called before any addStrip() has been registered.
Example:
LiteLEDpioGroup panels(LED_STRIP_WS2812, 64, false);
panels.addStrip(21); // lane 0 → GPIO 21
panels.addStrip(19); // lane 1 → GPIO 19
panels.begin(); // allocate & enable
// Large arrays — pixel buffers in PSRAM
panels.begin(PSRAM_ENABLE);Applies to: LiteLEDpioGroup
Registers a strip (GPIO pin → lane mapping) before begin() is called. Returns a LiteLEDpioLane reference for per-strip pixel manipulation. The two forms differ only in how the lane index (bit-lane position in the shared DMA buffer) is assigned.
LiteLEDpioLane &addStrip(uint8_t gpio);Auto-assigns the next available lane (0, 1, 2 …). This is the typical form when all strips are registered in a fixed order and no lane gaps are needed.
Returns a silent null lane (with an error log) if all lanes are already assigned.
template<uint8_t LANE>
LiteLEDpioLane &addStrip(uint8_t gpio);Assigns the strip to LANE specifically. LANE is verified against PARLIO_TX_UNIT_MAX_DATA_WIDTH at compile time via static_assert.
Both forms give equal freedom to choose any GPIO pin for the strip. The explicit form additionally guarantees:
- Sparse assignment — deliberate gaps in lane numbering for future expansion or reserved index positions
- External index storage — code that records lane numbers and retrieves strips via
operator[]later gets a guaranteed, init-order-independent index - Documentation of intent — making the lane-to-strip mapping explicit in the source
LiteLEDpioGroup strips(LED_STRIP_WS2812, 64, false);
// Sequential — lanes assigned 0, 1 in order
LiteLEDpioLane &left = strips.addStrip(21);
LiteLEDpioLane &right = strips.addStrip(19);
// Explicit — lanes 0 and 3; lanes 1 and 2 left empty for future use
LiteLEDpioLane &panelA = strips.addStrip<0>(21);
LiteLEDpioLane &panelB = strips.addStrip<3>(19);Applies to: LiteLED · LiteLEDpio · LiteLEDpioGroup · LiteLEDpioLane
esp_err_t show();Transmits the pixel buffer to the physical LED strip. This is a blocking call that waits until transmission (including the reset/latch period) is complete.
Returns:
ESP_OK— successLiteLED/LiteLEDpio: error codes from the RMT / PARLIO transmit operation
Behavioural differences by driver:
| Driver | What show() does |
|---|---|
LiteLED |
Runs the RMT encoder callback; clocks out the waveform via RMT |
LiteLEDpio |
Pre-encodes the pixel buffer into the DMA bitstream buffer, then issues a GDMA transfer to the PARLIO TX unit |
LiteLEDpioGroup |
Pre-encodes all lane pixel buffers into the shared DMA bitstream buffer (OR-merging each lane's bits), then issues one GDMA transfer — all strips update simultaneously |
LiteLEDpioLane |
Identical to calling show() on the parent group — all lanes always transmit together |
Example:
strip.setPixel(0, 0xFF0000);
strip.setPixel(1, 0x00FF00);
strip.show(); // update the stripApplies to: LiteLED · LiteLEDpio · LiteLEDpioLane
Note:
LiteLEDpioGroupdoes not have aclear()method directly; callclear()on eachLiteLEDpioLanereference individually.
esp_err_t clear(bool show = false);Sets all pixels in the buffer to black (off).
Parameters:
show— iftrue, callsshow()automatically after clearing
Returns: ESP_OK on success
Example:
strip.clear(); // clear buffer, don't update strip yet
strip.clear(true); // clear buffer and update strip immediately
// LiteLEDpioGroup — clear each lane, then transmit once
panelA.clear();
panelB.clear();
strips.show();Applies to: LiteLED · LiteLEDpio · LiteLEDpioGroup · LiteLEDpioLane
esp_err_t brightness(uint8_t bright, bool show = false);Sets the global brightness level. Brightness is applied non-destructively during show() encoding using scale8_video(); pixel buffer values are not modified.
Parameters:
bright— brightness level (0–255, where 0 = off, 255 = full)show— iftrue, callsshow()automatically after updating the brightness
Returns: ESP_OK on success
Behavioural differences:
| Driver | Scope |
|---|---|
LiteLED / LiteLEDpio |
Applies to the single strip |
LiteLEDpioGroup |
Sets a group-wide brightness; individual lanes can independently override it via their LiteLEDpioLane reference |
LiteLEDpioLane |
Sets the brightness for that lane only, independently of both the group setting and other lanes |
Example:
strip.brightness(128); // 50% — update on next show()
strip.brightness(51, true); // ~20% — update immediately
strips.brightness(20); // group: all lanes at 20%
panelA.brightness(100); // individual lane overrideApplies to: LiteLED · LiteLEDpio · LiteLEDpioGroup · LiteLEDpioLane
uint8_t getBrightness();Returns the current brightness value (0–255).
Notes:
- Returns the brightness value used the last time
show()was called. Ifbrightness()has been set since the lastshow(), the return value reflects the pending value (what will be applied on the nextshow()). - On
LiteLEDpioGroup, returns the group-wide brightness setting. Each lane can have its own independent brightness set via itsLiteLEDpioLanereference;LiteLEDpioLane::getBrightness()returns that lane's individual value.
Returns: Current brightness (0–255)
Example:
uint8_t b = strip.getBrightness();
Serial.printf("Brightness: %d
", b);Applies to: LiteLED · LiteLEDpio · LiteLEDpioLane
LiteLEDpioGroupdoes not expose pixel methods directly — access pixels through theLiteLEDpioLanereferences returned byaddStrip()oroperator[].
Set single LED colour using rgb_t structure.
esp_err_t setPixel(size_t num, rgb_t color, bool show = false);Parameters:
num— LED index (0-based)color— colour asrgb_tstructureshow— iftrue, immediately update the strip
Returns:
ESP_OK— successESP_ERR_INVALID_ARG— LED index out of bounds or strip not initialized
Example:
rgb_t red = rgb_from_values(255, 0, 0);
strip.setPixel(0, red);
strip.show();
// Set and show immediately
strip.setPixel(5, rgb_from_values(0, 255, 0), true);Set the colour of a single LED using a 32-bit colour code.
esp_err_t setPixel(size_t num, crgb_t color, bool show = false);Parameters:
num— LED index (0-based)color— colour ascrgb_t32-bit code (0x00RRGGBB)show— iftrue, immediately update the strip
Returns: Same as rgb_t version
Example:
strip.setPixel(0, 0xFF0000); // Red
strip.setPixel(1, 0x00FF00); // Green
strip.setPixel(2, 0x0000FF); // Blue
strip.show();Set multiple consecutive LEDs from an rgb_t array.
esp_err_t setPixels(size_t start, size_t len, rgb_t *data, bool show = false);Parameters:
start— first LED index (0-based)len— number of LEDs to setdata— pointer to array ofrgb_tcoloursshow— iftrue, immediately update the strip
Returns:
ESP_OK— successESP_ERR_INVALID_ARG— invalid parameters or out of bounds
Example:
rgb_t rainbow[] = {
rgb_from_code(0xFF0000), // Red
rgb_from_code(0xFF7F00), // Orange
rgb_from_code(0xFFFF00), // Yellow
rgb_from_code(0x00FF00), // Green
rgb_from_code(0x0000FF), // Blue
rgb_from_code(0x8B00FF) // Violet
};
strip.setPixels(0, 6, rainbow, true);Set multiple consecutive LEDs from a crgb_t array.
esp_err_t setPixels(size_t start, size_t len, crgb_t *data, bool show = false);Parameters:
start— first LED index (0-based)len— number of LEDs to setdata— pointer to array of 32-bit colour codesshow— iftrue, immediately update the strip
Returns: Same as rgb_t version
Example:
crgb_t colors[] = {0xFF0000, 0x00FF00, 0x0000FF};
strip.setPixels(10, 3, colors);
strip.show();Fill entire strip with a single colour.
esp_err_t fill(rgb_t color, bool show = false);Parameters:
color— colour asrgb_tstructureshow— iftrue, immediately update the strip
Returns: ESP_OK on success
Example:
strip.fill(rgb_from_values(0, 0, 255), true); // blue, show immediately
rgb_t purple = {128, 0, 128};
strip.fill(purple);
strip.show();Fill entire strip with a single colour code.
esp_err_t fill(crgb_t color, bool show = false);Parameters:
color— colour as 32-bit code (0x00RRGGBB)show— iftrue, immediately update the strip
Returns: ESP_OK on success
Example:
strip.fill(0xFF0000, true); // red, show immediatelyFill strip with random colours.
esp_err_t fillRandom(bool show = false);Parameters:
show— iftrue, immediately update the strip
Returns: ESP_OK on success
Description:
Fills each LED with a random RGB colour using the ESP32's hardware random number generator. Each colour channel is set independently to a random value between 5 and 255, so the result can be quite bright. Use brightness() beforehand to control overall intensity.
Example:
strip.fillRandom(true);
void loop() {
strip.fillRandom(true);
delay(1000);
}Applies to: LiteLED · LiteLEDpio · LiteLEDpioLane
Returns the colour value stored in the pixel buffer — not accounting for brightness scaling.
Get colour of a single LED as rgb_t structure.
rgb_t getPixel(size_t num);Parameters:
num— LED index (0-based)
Returns: rgb_t structure with LED colour, or {0, 0, 0} if the index is invalid
Example:
rgb_t color = strip.getPixel(5);
Serial.printf("LED 5: R=%d, G=%d, B=%d
", color.r, color.g, color.b);Get colour of a single LED as a 32-bit colour code.
crgb_t getPixelC(size_t num);Parameters:
num— LED index (0-based)
Returns: 32-bit colour code (0x00RRGGBB), or 0x000000 if the index is invalid
Example:
crgb_t color = strip.getPixelC(10);
if (color == 0xFF0000) {
Serial.println("LED 10 is red");
}Applies to: LiteLED · LiteLEDpio · LiteLEDpioLane
Set custom colour byte order for the LED strip.
esp_err_t setOrder(color_order_t led_order = ORDER_GRB);Parameters:
led_order— colour order (seecolor_order_t)
Returns: ESP_OK on success
Description:
Overrides the default colour order for the LED strip type. Useful for LED strips that don't match standard specifications. Takes effect after the next show() call and remains in effect until another setOrder() or resetOrder() is called. Can be called any time after the object is declared.
Example:
strip.setOrder(ORDER_RGB); // override default GRB
strip.setOrder(ORDER_BGR); // some non-standard strips need thisReset colour order to the driver default for the LED strip type.
esp_err_t resetOrder();Returns: ESP_OK on success
Takes effect after the next show() call and remains in effect until another setOrder() or resetOrder() is called. Can be called any time after the object is declared.
Example:
strip.resetOrder(); // restore default colour orderApplies to: LiteLED · LiteLEDpio · LiteLEDpioGroup · LiteLEDpioLane
bool isValid() const;Returns:
| Driver | Returns true when… |
|---|---|
LiteLED |
GPIO is still registered to this instance (checks registry and Peripheral Manager) |
LiteLEDpio |
Same as LiteLED |
LiteLEDpioGroup |
The group was successfully initialised via begin() |
LiteLEDpioLane |
The lane was registered via addStrip() (not a null-lane sentinel) |
Example:
if (!strip.isValid()) {
Serial.println("Strip instance is no longer valid!");
}
// LiteLEDpioGroup
if (!panelA.isValid()) {
// lane was never registered or group not yet initialised
}Applies to: LiteLED · LiteLEDpio
int getGpioPin() const;Returns:
- GPIO pin number if successfully initialised
-1if not initialised
Example:
int pin = strip.getGpioPin();
if (pin >= 0) {
Serial.printf("Strip using GPIO %d
", pin);
}Applies to: LiteLED · LiteLEDpio
static bool isGpioAvailable(uint8_t gpio_pin);Queries the ESP32 Peripheral Manager to check whether the given GPIO is free.
Parameters:
gpio_pin— GPIO pin number to check
Returns: true if the GPIO is available; false if already in use by another peripheral
Example:
if (LiteLED::isGpioAvailable(14)) {
strip.begin(14, 60);
} else {
Serial.println("GPIO 14 is already in use");
}Applies to: LiteLED · LiteLEDpio
static uint8_t getActiveInstanceCount();Returns the number of currently active instances for that driver class.
Example:
Serial.printf("Active RMT strips: %d
", LiteLED::getActiveInstanceCount());
Serial.printf("Active PARLIO strips: %d
", LiteLEDpio::getActiveInstanceCount());Applies to: LiteLEDpioGroup
LiteLEDpioLane &operator[](uint8_t lane);Returns the LiteLEDpioLane reference for the given bit-lane index. Returns a silent null lane (with an error log) if the index is out of range or was never registered via addStrip(). Use isValid() on the returned reference to verify it.
Parameters:
lane— bit-lane index (0-based)
Example:
LiteLEDpioGroup panels(LED_STRIP_WS2812, 64, false);
panels.addStrip<0>(21);
panels.addStrip<1>(19);
panels.begin();
// Access via operator[]
panels[0].fill(0xFF0000); // lane 0 red
panels[1].fill(0x0000FF); // lane 1 blue
panels.show();
// Null-lane guard
LiteLEDpioLane &lane = panels[5]; // lane 5 was never registered
if (!lane.isValid()) {
// handle gracefully
}LiteLED supports multiple simultaneous LED displays through two distinct mechanisms — the approach depends on which driver you are using.
The LiteLED (RMT) driver provides advanced support for managing multiple independent LED displays simultaneously, with automatic interrupt priority management and conflict resolution. Each display gets its own RMT TX channel and its own independent pixel buffer, so strips can have different LED counts, types, and update rates.
A maximum of eight displays are supported — that is the maximum number of available RMT TX channels of any ESP32 SoC at the time of writing.
The recommended approach for most applications:
LiteLED display1(LED_STRIP_WS2812, false);
LiteLED display2(LED_STRIP_WS2812, false);
LiteLED display3(LED_STRIP_WS2812, false);
void setup() {
// Simple initialization - automatic priority management
display1.begin(14, 100);
display2.begin(27, 100);
display3.begin(26, 100);
Serial.printf("Active displays: %d\n", LiteLED::getActiveInstanceCount());
}For applications with specific requirements:
void setup() {
// High priority display for time-critical applications
display1.begin(14, 100, DMA_OFF, PRIORITY_HIGH, PSRAM_DISABLE);
// Automatic priority selection (recommended)
display2.begin(27, 500, DMA_OFF, PRIORITY_DEFAULT, PSRAM_AUTO);
// Simple initialization (best for most cases)
display3.begin(26, 200);
}LiteLED v3.0.0+ includes intelligent priority conflict resolution:
- Pre-flight checks: Verifies priority availability before allocation
- Smart fallback: Automatically selects alternative priorities when conflicts occur
- Resource tracking: Monitors and manages interrupt priority usage
- Graceful degradation: Continues operation even when requested priorities are unavailable
Note:
- Interrupt priority support requires arduino-esp32 core version 3.2.0 or greater.
| Priority | Enumeration | Description |
|---|---|---|
| 0 | PRIORITY_DEFAULT |
Default priority (recommended) |
| 1 | PRIORITY_HIGH |
High priority for time-critical displays |
| 2 | PRIORITY_MED |
Medium priority |
| 3 | PRIORITY_LOW |
Low priority for background displays |
Integration with ESP32 Peripheral Manager ensures safe GPIO usage:
// Check GPIO availability before use
if (LiteLED::isGpioAvailable(gpio_pin)) {
display.begin(gpio_pin, num_leds); // Safe to use
} else {
Serial.printf("GPIO %d is already in use\n", gpio_pin);
}
// Get count of active instances
uint8_t active = LiteLED::getActiveInstanceCount();
Serial.printf("Currently managing %d displays\n", active);-
Use simple initialization when possible:
display.begin(gpio, leds); // Recommended -
Check initialization results:
esp_err_t result = display.begin(gpio, leds); if (result != ESP_OK) { Serial.printf("Failed: %s\n", esp_err_to_name(result)); }
-
Monitor resources:
Serial.printf("Active: %d\n", LiteLED::getActiveInstanceCount());
The PARLIO driver offers two multi-display strategies with different trade-offs.
Independent strips — mix LiteLEDpio with LiteLED
On SoCs with one PARLIO TX unit (ESP32-C6, ESP32-H2), one LiteLEDpio instance can run alongside up to the SoC's RMT channel limit of LiteLED instances. Each strip is fully independent — different LED types, lengths, and update timing are all fine.
LiteLEDpio parlioStrip(LED_STRIP_WS2812, false); // PARLIO TX unit
LiteLED rmtStrip1(LED_STRIP_WS2812, false); // RMT channel 0
LiteLED rmtStrip2(LED_STRIP_SK6812, true); // RMT channel 1
void setup() {
parlioStrip.begin(21, 64);
rmtStrip1.begin(14, 30);
rmtStrip2.begin(27, 30);
}Synchronised strips — LiteLEDpioGroup
When strips share the same LED type and length and must update in perfect lock-step, LiteLEDpioGroup drives up to 8 of them from a single PARLIO TX unit using one shared DMA buffer. This is more memory-efficient than running multiple separate instances and guarantees frame-perfect synchronisation at the cost of a single shared show(). See the LiteLEDpioGroup / LiteLEDpioLane — PARLIO Multi-Strip Driver section in Driver Architecture for full details.
LiteLEDpioGroup panels(LED_STRIP_WS2812, 64, false);
LiteLEDpioLane &panelA = panels.addStrip(21);
LiteLEDpioLane &panelB = panels.addStrip(19);
void setup() {
panels.begin();
panels.brightness(20);
}
void loop() {
panelA.fill(0xFF0000);
panelB.fill(0x0000FF);
panels.show(); // both panels update simultaneously
delay(1000);
}Note: the design of the PARLIO peripheral requires that the driver be instantiated with the largest number of LEDs in a strip. However, strips smaller than that can be attached to the other lanes of the driver as the pixel buffers are sent "last LED first". Meaning as long as the buffer "blacks out" the unused LEDs at the end of the buffer, it will show as expected.
PSRAM is useful for LED arrays with hundreds of LEDs:
// Auto-detect and use PSRAM if available
LiteLED bigStrip(LED_STRIP_WS2812, false);
bigStrip.begin(14, 2000, PSRAM_AUTO);
// Force PSRAM usage
bigStrip.begin(14, 2000, PSRAM_ENABLE);
// Force internal RAM (fastest)
bigStrip.begin(14, 100, PSRAM_DISABLE);PSRAM Considerations:
- Slightly slower than internal RAM (~5-10% performance impact)
- Essential for large arrays (>500 LEDs) on ESP32 with limited RAM
- Automatically detected with
PSRAM_AUTO
In dynamic applications where GPIO pins might be reassigned:
void loop() {
if (!strip.isValid()) {
Serial.println("Warning: Strip instance invalidated!");
// Attempt to reinitialize
if (strip.begin(14, 60) == ESP_OK) {
Serial.println("Strip reinitialized");
}
}
// Normal operation
strip.fillRandom(true);
delay(1000);
}The LiteLED_Utils namespace provides compile-time utility functions for querying hardware capabilities. These functions allow developers to check platform support for various features before configuring their LED strips.
Description:
Checks at compile time whether the current ESP32 chip supports RMT DMA (Direct Memory Access) for LED strip operations.
Syntax:
constexpr bool LiteLED_Utils::isDmaSupported()Parameters:
None
Returns:
true- RMT DMA is supported on this ESP32 modelfalse- RMT DMA is not supported on this ESP32 model
Notes:
- This is a
constexprfunction, so the result is determined at compile time - On chips without RMT DMA support (ESP32, ESP32-C2, ESP32-C6), this returns
false - On chips with RMT DMA support (ESP32-S2, ESP32-S3, ESP32-C3, ESP32-H2), this returns
true - If you attempt to use
DMA_ONon unsupported hardware, the library will automatically fall back toDMA_OFFwith a warning
Example Usage:
#include <LiteLED.h>
void setup() {
Serial.begin(115200);
// Query DMA support at compile time
if (LiteLED_Utils::isDmaSupported()) {
Serial.println("This chip supports RMT DMA");
}
else {
Serial.println("This chip does not support RMT DMA");
}
// Use in conditional initialization
LiteLED strip(LED_STRIP_WS2812, false);
if (LiteLED_Utils::isDmaSupported()) {
// Enable DMA for better performance on supported chips
strip.begin(14, 100, DMA_ON, PRIORITY_DEFAULT, PSRAM_AUTO);
}
else {
// Use non-DMA mode on chips without support
strip.begin(14, 100, DMA_OFF, PRIORITY_DEFAULT, PSRAM_AUTO);
}
}Description: Checks at compile time whether the current ESP-IDF/arduino-esp32 core version supports setting RMT interrupt priority levels.
Syntax:
constexpr bool LiteLED_Utils::isPrioritySupported()Parameters:
None
Returns:
true- Interrupt priority setting is supported (ESP-IDF 5.1.2 or later)false- Interrupt priority setting is not supported (older ESP-IDF versions)
Notes:
- This is a
constexprfunction, so the result is determined at compile time - Requires ESP-IDF version 5.1.2 or higher for support
- If priority setting is not supported, the library will use the default priority (0) and log a note
- Attempting to set priority on unsupported versions is safe - the library gracefully falls back to defaults
Example Usage:
#include <LiteLED.h>
void setup() {
Serial.begin(115200);
// Query interrupt priority support at compile time
if (LiteLED_Utils::isPrioritySupported()) {
Serial.println("This core version supports interrupt priority setting");
} else {
Serial.println("This core version uses default interrupt priority");
}
// Use in conditional configuration
LiteLED strip(LED_STRIP_WS2812, false);
if (LiteLED_Utils::isPrioritySupported()) {
// Set custom priority on supported cores
strip.begin(14, 100, DMA_OFF, PRIORITY_HIGH, PSRAM_AUTO);
} else {
// Priority parameter is ignored on older cores (uses default)
strip.begin(14, 100, DMA_OFF, PRIORITY_DEFAULT, PSRAM_AUTO);
}
}Here's a comprehensive example showing how to use both utility functions together:
#include <LiteLED.h>
#define LED_GPIO 14
#define LED_COUNT 100
LiteLED myStrip(LED_STRIP_WS2812, false);
void setup() {
Serial.begin(115200);
delay(2000);
// Display hardware capabilities
Serial.println("=== LiteLED Hardware Capabilities ===");
Serial.printf("RMT DMA Support: %s\n",
LiteLED_Utils::isDmaSupported() ? "YES" : "NO");
Serial.printf("Interrupt Priority Support: %s\n",
LiteLED_Utils::isPrioritySupported() ? "YES" : "NO");
Serial.printf("DMA_DEFAULT value: %s\n",
(DMA_DEFAULT == DMA_ON) ? "DMA_ON" : "DMA_OFF");
Serial.println("=====================================\n");
// Smart initialization based on capabilities
ll_dma_t dma_setting = DMA_DEFAULT; // Let the library choose the best default
ll_priority_t priority = PRIORITY_DEFAULT;
// Optionally enable DMA on supported hardware if you want the performance boost
if (LiteLED_Utils::isDmaSupported() && LED_COUNT > 300) {
dma_setting = DMA_ON; // DMA is beneficial for large strips
Serial.println("Enabling DMA for large LED strip");
}
// Optionally set higher priority on supported cores if timing is critical
if (LiteLED_Utils::isPrioritySupported()) {
priority = PRIORITY_HIGH;
Serial.println("Setting high interrupt priority");
}
// Initialize with optimal settings
esp_err_t result = myStrip.begin(LED_GPIO, LED_COUNT, dma_setting, priority, PSRAM_AUTO);
if (result == ESP_OK) {
Serial.println("LED strip initialized successfully!");
myStrip.brightness(50);
myStrip.fill(0x00FF00, true); // Green
}
else {
Serial.printf("LED strip initialization failed: %s\n", esp_err_to_name(result));
}
}
void loop() {
// Your LED animation code here
}These utility functions are particularly useful when:
- Writing portable code that runs on multiple ESP32 variants
- Creating libraries that build on top of LiteLED
- Performance optimization where you want to enable DMA only on supported chips
- Debugging to understand platform limitations
- Compile-time configuration using
if constexprin C++17 or later
The DMA_DEFAULT enum value is always set to DMA_OFF to preserve DMA channels for user applications. This is a conservative default that works on all ESP32 chips. Users who want DMA performance benefits should explicitly use DMA_ON after checking isDmaSupported().
A number of functions are included to assist in manipulation and conversion of colours.
Convert a 32-bit colour code to rgb_t structure.
static inline rgb_t rgb_from_code(crgb_t color_code);Parameters:
color_code: 32-bit colour in format0x00RRGGBB
Returns: rgb_t structure with separated R, G, B values
Example:
rgb_t red = rgb_from_code(0xFF0000);
// red.r = 255, red.g = 0, red.b = 0Create an rgb_t colour from individual channel values.
static inline rgb_t rgb_from_values(uint8_t r, uint8_t g, uint8_t b);Parameters:
r: Red value (0-255)g: Green value (0-255)b: Blue value (0-255)
Returns: rgb_t structure
Example:
rgb_t purple = rgb_from_values(128, 0, 128);Convert an rgb_t structure to a 32-bit colour code.
static inline crgb_t rgb_to_code(rgb_t color);Parameters:
color:rgb_tstructure
Returns: 32-bit colour code in format 0x00RRGGBB
Example:
rgb_t color = {255, 128, 0};
crgb_t code = rgb_to_code(color); // Returns 0xFF8000Check if an RGB colour is black (all channels zero).
static inline bool rgb_is_zero(rgb_t a);Parameters:
a:rgb_tcolour to test
Returns: true if all colour channels are 0, false otherwise
Example:
rgb_t black = {0, 0, 0};
if (rgb_is_zero(black)) {
// Colour is black
}Calculate the perceived brightness (luma) of an RGB colour.
static inline uint8_t rgb_luma(rgb_t a);Parameters:
a:rgb_tcolor
Returns: Luma value (0-255)
Description: Calculates perceptual brightness using the formula:
Luma = (R * 54 + G * 183 + B * 18) / 256
This approximates human eye sensitivity where green contributes most to perceived brightness.
Example:
rgb_t color = {100, 200, 50};
uint8_t brightness = rgb_luma(color); // Returns ~158Scale an 8-bit value by a fractional amount.
static inline uint8_t scale8(uint8_t i, fract8 scale);
static inline uint8_t scale8_video(uint8_t i, fract8 scale);Parameters:
i: Value to scale (0-255)scale: Scale factor (0-255, represents 0.0-1.0)
Returns:
Scaled value (0-255)
Difference:
scale8(): Fast scaling, may produce zero for non-zero inputsscale8_video(): Guarantees non-zero output if both inputs are non-zero (better for LED dimming)
Example:
uint8_t half_bright = scale8_video(255, 128); // Returns 128
uint8_t dim = scale8_video(255, 10); // Returns ~10LED strip update time depends on the number of LEDs:
| LED Count | Transmission Time (approx.) |
|---|---|
| 30 LEDs | ~1 ms |
| 60 LEDs | ~2 ms |
| 150 LEDs | ~5 ms |
| 300 LEDs | ~10 ms |
| 600 LEDs | ~20 ms |
| 1000 LEDs | ~33 ms |
Formula: Time ≈ (LED_COUNT × 30µs)
Each driver allocates two distinct buffers: a pixel colour buffer that holds the R/G/B(/W) values you write via setPixel(), fill(), etc., and a DMA bitstream buffer that holds the pre-encoded waveform sent to the hardware. The two buffers have different size characteristics and different allocation rules.
The RMT driver encodes pixels on-the-fly inside an encoder callback, so there is no pre-encoded DMA buffer. Only the pixel colour buffer is heap-allocated.
| Strip type | Bytes per LED | 30 LEDs | 60 LEDs | 144 LEDs | 300 LEDs | 1000 LEDs |
|---|---|---|---|---|---|---|
| RGB | 3 | 90 B | 180 B | 432 B | 900 B | 2.9 KB |
| RGBW | 4 | 120 B | 240 B | 576 B | 1.2 KB | 3.9 KB |
The pixel colour buffer can optionally be placed in PSRAM using the psram_flag parameter in begin(). When DMA is enabled on the RMT channel (via DMA_ON in the full begin() overload), the IDF RMT driver allocates additional internal DMA memory; this is managed internally and not reflected in the table above.
The PARLIO driver pre-encodes the entire waveform before transmission. Each input byte expands to 24 DMA bytes (8 bits × 3 samples/bit), and a 1000-byte reset tail is appended and zero-filled. This DMA bitstream buffer is always allocated from internal DMA-capable RAM — GDMA cannot access PSRAM on C6/H2.
The pixel colour buffer follows the same rule as the RMT driver and can optionally be placed in PSRAM.
RGB strips (3 bytes/LED colour buffer · 72 DMA bytes/LED encoded)
| LEDs | Colour buffer | DMA buffer | Total heap | DMA buf (internal only) |
|---|---|---|---|---|
| 8 | 24 B | 1,576 B | 1,600 B | 1,576 B |
| 16 | 48 B | 2,152 B | 2,200 B | 2,152 B |
| 30 | 90 B | 3,160 B | 3,250 B | 3,160 B |
| 60 | 180 B | 5,320 B | 5,500 B | 5,320 B |
| 64 | 192 B | 5,608 B | 5,800 B | 5,608 B |
| 144 | 432 B | 11,368 B | 11.5 KB | 11,368 B |
| 256 | 768 B | 19,432 B | 20.0 KB | 19,432 B |
| 300 | 900 B | 22,600 B | 23.4 KB | 22,600 B |
RGBW strips (4 bytes/LED colour buffer · 96 DMA bytes/LED encoded)
| LEDs | Colour buffer | DMA buffer | Total heap | DMA buf (internal only) |
|---|---|---|---|---|
| 30 | 120 B | 3,880 B | 4,000 B | 3,880 B |
| 60 | 240 B | 6,760 B | 7,000 B | 6,760 B |
| 64 | 256 B | 7,144 B | 7,400 B | 7,144 B |
| 144 | 576 B | 14,824 B | 15.2 KB | 14,824 B |
| 256 | 1,024 B | 25,576 B | 26.4 KB | 25,576 B |
DMA buffer formula (total bytes, 4-byte aligned):
RGB: floor(N × 72 + 1000 + 3) & ~3
RGBW: floor(N × 96 + 1000 + 3) & ~3
LiteLEDpioGroup allocates one pixel colour buffer per lane plus a single shared DMA bitstream buffer for all lanes. The DMA buffer size depends only on the strip length and LED type — it does not grow with the number of lanes, because the per-bit-lane encoding packs each lane into individual bits of each DMA byte without increasing the byte count.
This makes LiteLEDpioGroup significantly more memory-efficient than running N separate LiteLEDpio instances: a group pays one DMA buffer cost regardless of how many lanes are active.
RGB strips, 64 LEDs per lane:
| Configuration | Pixel buffers | DMA buffer | Total heap | Internal RAM |
|---|---|---|---|---|
| 1-lane group | 192 B | 5,608 B | 5,800 B | 5,800 B |
| 2-lane group | 384 B | 5,608 B | 5,992 B | 5,608 B minimum* |
| 4-lane group | 768 B | 5,608 B | 6,376 B | 5,608 B minimum* |
| 8-lane group | 1,536 B | 5,608 B | 7,144 B | 5,608 B minimum* |
2 × separate LiteLEDpio |
2 × 192 = 384 B | 2 × 5,608 = 11,216 B | 11,600 B | 11,216 B |
* Pixel colour buffers may reside in PSRAM via begin(PSRAM_ENABLE), leaving only the shared DMA buffer in internal RAM.
DMA buffer size formula (same as single-strip; independent of lane count):
RGB: floor(N × 72 + 1000 + 3) & ~3
RGBW: floor(N × 96 + 1000 + 3) & ~3
The critical constraint is internal DMA-capable RAM. On the ESP32-C6 this is shared system SRAM (~400 KB usable after IDF overhead). For LiteLEDpio the DMA buffer is the dominant cost:
| Driver | Pixel buffer location | DMA buffer location | Internal RAM used (RGB, 64 LEDs) |
|---|---|---|---|
LiteLED (no DMA) |
configurable | none | 192 B |
LiteLED (DMA on) |
configurable | internal (IDF-managed) | ~192 B + IDF overhead |
LiteLEDpio |
configurable (PSRAM ok) | always internal | 5,608 B (+ 192 B if colour buf also internal) |
LiteLEDpioGroup 2 lanes |
configurable (PSRAM ok) | always internal, shared | 5,608 B DMA + 384 B pixel (if internal) |
LiteLEDpioGroup 8 lanes |
configurable (PSRAM ok) | always internal, shared | 5,608 B DMA + 1,536 B pixel (if internal) |
For very large arrays with LiteLEDpio, use PSRAM_ENABLE for the colour buffer to keep it out of internal SRAM, and ensure the DMA buffer fits within available internal heap before calling begin().
- Use DMA for large arrays (300+ LEDs on supported chips)
- Use PSRAM for very large arrays (1000+ LEDs)
- Batch updates: Set multiple pixels before calling
show() - Avoid frequent
show()calls: Maximum practical update rate is ~60 Hz - Use
showparameter: Methods likesetPixel()have optionalshowparameter for immediate update
Good Practice:
// Efficient: Batch updates
for (size_t i = 0; i < LED_COUNT; i++) {
strip.setPixel(i, color);
}
strip.show(); // Single update
// Inefficient: Update after each pixel
for (size_t i = 0; i < LED_COUNT; i++) {
strip.setPixel(i, color, true); // Don't do this!
}- Check GPIO connection: Verify DIN pin connection
- Check power supply: LEDs need adequate power (5V, sufficient current)
- Check LED type: Ensure
LED_STRIP_*****matches your LED type - Verify initialization: Check
begin()return value - Call
show(): LED buffer must be transmitted withshow()
- Check colour order: Try different
setOrder()values - Check LED type: Some "WS2812" clones use RGB instead of GRB
- Power supply issues: Insufficient power can cause colour errors
- Add 0.1µF capacitor between ESP32 GND and LED strip GND
- Use shorter wires for DIN connection (< 15cm recommended)
- Add 330Ω resistor in series with DIN line
- Use a level shifter between the GPIO pin and the DIN line if the strip power supply is greater than 3.3V.
- Check power supply quality: Use stable supply
- Check for duplicate instances using same GPIO
- Free previous instance before reusing GPIO
- Use
isGpioAvailable()to verify if GPIO is free
- Reduce LED count if using internal RAM
- Enable PSRAM: Use
PSRAM_AUTOorPSRAM_ENABLE - Check available heap: Use
ESP.getFreeHeap()
The library performs the following compile-time checks and will halt compilation if any of the following conditions are found.:
- ESP32 platform: target is not an ESP32 microcontroller.
- RMT peripheral: target ESP32 does not have an RMT peripheral.
- arduino-esp32 core version: the version of the arduino-esp32 core is incompatible with this version of the library.
When compiling for chips or core versions with limited capabilities, you'll see compile-time warnings:
"LiteLED: Selected ESP32 model does not support RMT DMA access. Use of RMT DMA will be disabled.""LiteLED: This version of the core does not support setting of RMT interrupt priority. Default will be used."
These warnings inform you at build time of platform limitations, while the utility functions allow you to query these capabilities programmatically.
The LiteLED methods that write to the LED string buffer or set/reset the colour order return a status code of esp_err_t type on completion. Checking this code and taking action is optional and is an exercise left to the developer.
If things go as normal, the return code is ESP_OK which is of type int with a value of 0. So a quick check would be, if the return code is anything other than 0, something went amok.
Full description of these codes can be found on the Espressif ESP-IDF site here.
If you're really interested in diving deeper, head over to the Espressif ESP-IDF Error Handling docs.
| Error Code | Value | Description |
|---|---|---|
ESP_OK |
0 |
Success |
ESP_ERR_INVALID_ARG |
0x102 |
Invalid argument (e.g., invalid GPIO, out-of-bounds LED index) |
ESP_ERR_INVALID_STATE |
0x103 |
Invalid state (e.g., GPIO already in use) |
ESP_ERR_NO_MEM |
0x101 |
Memory allocation failed |
ESP_FAIL |
-1 |
Generic failure |
esp_err_t err = strip.begin(14, 60);
if (err != ESP_OK) {
Serial.printf("Failed to initialize LED strip: %s\n", esp_err_to_name(err));
// Handle error
}// First strip uses GPIO 14
LiteLED strip1(LED_STRIP_WS2812, false);
strip1.begin(14, 30); // OK
// Attempting to use same GPIO fails
LiteLED strip2(LED_STRIP_WS2812, false);
esp_err_t err = strip2.begin(14, 60); // Returns ESP_ERR_INVALID_STATESolution:
Use different GPIO pins for each strip, or free the first strip before reusing the GPIO.
LiteLED strip(LED_STRIP_WS2812, false);
strip.begin(14, 30); // 30 LEDs (indices 0-29)
esp_err_t err = strip.setPixel(30, 0xFF0000); // Returns ESP_ERR_INVALID_ARGSolution:
Ensure LED indices are within valid range (0 to length-1).
// Very large LED array might fail
LiteLED hugeStrip(LED_STRIP_WS2812, false);
esp_err_t err = hugeStrip.begin(14, 10000, PSRAM_DISABLE); // May return ESP_ERR_NO_MEMSolution:
Use PSRAM for large arrays, reduce LED count, or free unused memory.
To assist in debugging, a number of status and error messages can be sent to the serial port via the esp32 log_'x' facility.
All log messages from the library are sent at the log_d level except a very nerdy and long LiteLED Debug Report which is sent at the log_v level.
To see these messages, in the Arduino IDE, set the Core Debug Level from the Tools menu to either Debug or Verbose
If using PlatformIO or pioarduino, add -DCORE_DEBUG_LEVEL=<level> to the build_flags section the platformio.ini file setting where <level> is either 4 (debug) or 5 (verbose).
If something is not behaving as you think, or you're just curious, set the log level to Debug or Verbose, recompile and upload the sketch and review the messages.
#include <LiteLED.h>
#define LED_GPIO 14
#define LED_COUNT 30
LiteLED strip(LED_STRIP_WS2812, false);
void setup() {
Serial.begin(115200);
if (strip.begin(LED_GPIO, LED_COUNT) == ESP_OK) {
Serial.println("LED strip initialized");
// Set all LEDs to blue at 50% brightness
strip.brightness(128);
strip.fill(0x0000FF, true);
} else {
Serial.println("Failed to initialize LED strip");
}
}
void loop() {
// Nothing to do
}Smooth brightness transitions:
void rampBrightness(LiteLED &strip, uint8_t target, uint8_t step = 1) {
uint8_t current = strip.getBrightness();
if (current < target) {
for (uint8_t b = current; b <= target; b += step) {
strip.brightness(b, true);
delay(10);
}
} else {
for (uint8_t b = current; b >= target; b -= step) {
strip.brightness(b, true);
delay(10);
}
}
}
// Usage
rampBrightness(strip, 255, 5); // Fade up to full brightness
delay(2000);
rampBrightness(strip, 0, 5); // Fade down to off#include <LiteLED.h>
#define LED_GPIO 14
#define LED_COUNT 60
LiteLED strip(LED_STRIP_WS2812, false);
void setup() {
strip.begin(LED_GPIO, LED_COUNT);
strip.brightness(50);
}
void loop() {
static uint8_t hue = 0;
for (size_t i = 0; i < LED_COUNT; i++) {
// Create rainbow effect
uint8_t pixelHue = hue + (i * 255 / LED_COUNT);
strip.setPixel(i, HSVtoRGB(pixelHue, 255, 255));
}
strip.show();
hue += 2;
delay(20);
}
// Helper function to convert HSV to RGB
crgb_t HSVtoRGB(uint8_t h, uint8_t s, uint8_t v) {
uint8_t region, remainder, p, q, t;
uint8_t r, g, b;
if (s == 0) {
return ((uint32_t)v << 16) | ((uint32_t)v << 8) | v;
}
region = h / 43;
remainder = (h - (region * 43)) * 6;
p = (v * (255 - s)) >> 8;
q = (v * (255 - ((s * remainder) >> 8))) >> 8;
t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;
switch (region) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
default: r = v; g = p; b = q; break;
}
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}#include <LiteLED.h>
#define STRIP1_GPIO 14
#define STRIP2_GPIO 27
#define LED_COUNT 30
LiteLED strip1(LED_STRIP_WS2812, false);
LiteLED strip2(LED_STRIP_WS2812, false);
void setup() {
Serial.begin(115200);
// Initialize first strip
if (strip1.begin(STRIP1_GPIO, LED_COUNT) == ESP_OK) {
Serial.println("Strip 1 initialized");
strip1.fill(0xFF0000, true); // Red
}
// Initialize second strip
if (strip2.begin(STRIP2_GPIO, LED_COUNT) == ESP_OK) {
Serial.println("Strip 2 initialized");
strip2.fill(0x0000FF, true); // Blue
}
Serial.printf("Active instances: %d\n", LiteLED::getActiveInstanceCount());
}
void loop() {
// Alternate colours
delay(1000);
strip1.fill(0x00FF00, true); // Green
strip2.fill(0xFF00FF, true); // Magenta
delay(1000);
strip1.fill(0xFF0000, true); // Red
strip2.fill(0x0000FF, true); // Blue
}#include <LiteLED.h>
#define LED_GPIO 14
#define LED_COUNT 30
LiteLED rgbwStrip(LED_STRIP_SK6812, true); // RGBW mode
void setup() {
// Enable automatic white channel calculation
rgbwStrip.begin(LED_GPIO, LED_COUNT, true);
rgbwStrip.brightness(100);
}
void loop() {
// Set pure white - auto_w will calculate W channel
rgbwStrip.fill(rgb_from_values(255, 255, 255), true);
delay(1000);
// Set colour (no white channel)
rgbwStrip.fill(rgb_from_values(255, 0, 0), true);
delay(1000);
}#include <LiteLED.h>
#define LED_GPIO 14
#define LED_COUNT 1000 // Large array
LiteLED bigStrip(LED_STRIP_WS2812, false);
void setup() {
Serial.begin(115200);
// Use PSRAM for large buffer
esp_err_t err = bigStrip.begin(LED_GPIO, LED_COUNT, PSRAM_AUTO);
if (err == ESP_OK) {
Serial.println("Large strip initialized successfully");
Serial.printf("Using %d LEDs\n", LED_COUNT);
// Create gradient effect
for (size_t i = 0; i < LED_COUNT; i++) {
uint8_t brightness = (i * 255) / LED_COUNT;
bigStrip.setPixel(i, rgb_from_values(brightness, 0, 255 - brightness));
}
bigStrip.show();
} else {
Serial.printf("Failed to initialize: %s\n", esp_err_to_name(err));
}
}
void loop() {
// Nothing to do
}#include <LiteLED.h>
#define LED_GPIO 14
#define LED_COUNT 300
LiteLED perfStrip(LED_STRIP_WS2812, false);
void setup() {
Serial.begin(115200);
// Use DMA and high priority for best performance
esp_err_t err = perfStrip.begin(LED_GPIO, LED_COUNT, DMA_ON,
PRIORITY_HIGH, PSRAM_AUTO);
if (err == ESP_OK) {
Serial.println("High-performance strip initialized");
} else {
Serial.printf("Init failed: %s\n", esp_err_to_name(err));
}
}
void loop() {
// Fast animation updates
static uint8_t offset = 0;
for (size_t i = 0; i < LED_COUNT; i++) {
uint8_t pos = (i + offset) & 0xFF;
perfStrip.setPixel(i, rgb_from_values(pos, 0, 255 - pos));
}
perfStrip.show();
offset += 2;
delay(10);
}v3.1.0
- Added support for using the ESP32 PARLIO peripheral as the driver.
- Added
LiteLEDpioclass:- PARLIO-backed single-strip LED driver for SoCs with a PARLIO peripheral
- Added
LiteLEDpioGroupandLiteLEDpioLaneclasses:- drive up to 8 independent strips simultaneously from one PARLIO TX unit;
- single
show()transmits all lanes in lock-step via one GDMA transfer
- API-identical to
LiteLED;- for single-strip LEDs, switch between RMT and PARLIO driver with one type declaration
- Added
multi_strip_parlioexample sketch
v3.0.1
- Bug fixes.
v3.0.0
- Initial release for LiteLED library version 3.0.0.