ESP32 family support with multiple clockless backends.
fastled_esp32.h: Chooses backend based on IDF version/features/macros: RMT (IDF4/IDF5), I2S‑parallel, or clockless SPI.led_sysdefs_esp32.h: ESP32 system defines (architecture flags, millis support, interrupt policy).esp_log_control.h: Lightweight logging control (avoid heavy vfprintf dependency).
- RMT (recommended):
clockless_rmt_esp32.h: Common entry; dispatches to IDF4/IDF5 layers.rmt_4/: IDF4 RMT implementation.rmt_5/: IDF5 RMT implementation andstrip_rmt.*glue.
- I2S parallel (many strips, same timing):
clockless_i2s_esp32.h: I2S driver; DMA double‑buffering, transposition/encoding.clockless_i2s_esp32s3.*: S3 variant hooks.i2s/i2s_esp32dev.*: I2S helpers.
- Clockless over SPI (WS2812 only):
clockless_spi_esp32.h: SPI staging for WS2812‑class LEDs usingspi_ws2812/.spi_ws2812/: SPI strip driver.
fastpin_esp32.h: Pin helpers for direct GPIO.fastspi_esp32.h: SPI backend helpers.clock_cycles.h: Timing helpers.
- Define one of:
FASTLED_ESP32_HAS_RMT,FASTLED_ESP32_HAS_CLOCKLESS_SPI, orFASTLED_ESP32_I2S. ESP_IDF_VERSION(fromesp_version.h) determines RMT4 vs RMT5.- I2S requires identical timing across all strips; RMT handles per‑channel timing.
Notes:
- Prefer RMT on modern IDF; I2S for high strip counts with uniform timings; SPI path only for WS2812.
FastLED supports the parallel I2S clockless driver on the following ESP32 targets in this tree:
-
ESP32 (classic, e.g., "ESP32Dev")
- Enable via build flags (PlatformIO
platformio.ini):[env:esp32dev] platform = espressif32 board = esp32dev framework = arduino build_flags = -D FASTLED_ESP32_I2S=1 ; Optional tuning: ; -D FASTLED_ESP32_I2S_NUM_DMA_BUFFERS=4 ; 2–16; 4 often reduces Wi‑Fi flicker ; -D I2S_DEVICE=0 ; default 0
- Driver files:
clockless_i2s_esp32.h,i2s/i2s_esp32dev.* - Constraints: all lanes use identical timing (same LED chipset); up to 24 lanes.
- Enable via build flags (PlatformIO
-
ESP32-S3
- Enable via build flags (PlatformIO
platformio.ini):[env:esp32s3] platform = espressif32 board = esp32-s3-devkitc-1 framework = arduino build_flags = -D FASTLED_ESP32_I2S=1 ; Optional: ; -D FASTLED_ESP32_I2S_NUM_DMA_BUFFERS=4
- Driver files:
clockless_i2s_esp32s3.*(wraps a dedicated S3 I2S clockless implementation) - Notes: requires a compatible Arduino-ESP32 core/IDF; see version checks in
clockless_i2s_esp32s3.h.
- Enable via build flags (PlatformIO
Additional I2S defines and guidance:
FASTLED_I2S_MAX_CONTROLLERS(default 24): maximum parallel lanesFASTLED_ESP32_I2S_NUM_DMA_BUFFERS(default 2): set to 4 to reduce flicker when ISRs (e.g., Wi‑Fi) are active- All I2S lanes must share the same chipset/timings; choose RMT instead when per‑lane timing differs
-
FASTLED_USE_PROGMEM: Control PROGMEM usage on ESP32. Default0inled_sysdefs_esp32.h. -
FASTLED_ALLOW_INTERRUPTS: Allow interrupts during show. Default1inled_sysdefs_esp32.h. -
FASTLED_ESP32_RAW_PIN_ORDER: Pin-order override hook defined inled_sysdefs_esp32.h(left undefined by default so the platform headers can choose). Set if you need raw GPIO numbering. -
Backend selection
FASTLED_ESP32_I2S: Enable I2S-parallel backend. Good for many strips with identical timing. Seeclockless_i2s_esp32.h.FASTLED_ESP32_USE_CLOCKLESS_SPI: Force the WS2812-over-SPI path instead of RMT when available. Only supports WS2811/WS2812 timings. Seefastled_esp32.handclockless_spi_esp32.h.- The normal default is RMT when available (
FASTLED_ESP32_HAS_RMTcomes fromplatforms/esp/32/feature_flags/enabled.h).
-
SPI tuning (clocked LEDs and WS2812-over-SPI path)
- Hardware SPI is now enabled by default on ESP32 via GPIO matrix routing. Any GPIO can be used for
DATA_PINandCLOCK_PIN. FASTLED_ALL_PINS_HARDWARE_SPI: DEPRECATED. Hardware SPI is now the unconditional default. This define is accepted for backwards compatibility but no longer required.FASTLED_FORCE_SOFTWARE_SPI: Force software bit-banging SPI instead of hardware SPI. The SPI bus manager will still handle device registration and conflict detection, but all data transmission uses software bit-banging. Use only if hardware SPI causes issues.FASTLED_ESP32_SPI_BUS: Select SPI bus:VSPI,HSPI, orFSPI. Defaults per target: S2/S3 useFSPI, others default toVSPI(seefastspi_esp32.h).FASTLED_ESP32_SPI_BULK_TRANSFER: When1, batches pixels into blocks to reduce transfer overhead and improve throughput at the cost of RAM. Default0.FASTLED_ESP32_SPI_BULK_TRANSFER_SIZE: Bulk block size (CRGBs) when bulk mode is enabled. Default64.
- Hardware SPI is now enabled by default on ESP32 via GPIO matrix routing. Any GPIO can be used for
-
RMT (IDF4 path in
rmt_4/)FASTLED_RMT_SERIAL_DEBUG: When1, print RMT errors to Serial for debugging. Default0.FASTLED_RMT_MEM_WORDS_PER_CHANNEL: Override RMT words per channel. Defaults toSOC_RMT_MEM_WORDS_PER_CHANNELon newer IDF or64on older.FASTLED_RMT_MEM_BLOCKS: Number of RMT memory blocks per channel to use. Default2. Higher values reduce refill interrupts but consume more shared memory and reduce usable channels per group.FASTLED_RMT_MAX_CHANNELS: Max TX channels to use. Defaults to SoC capability (e.g.,SOC_RMT_TX_CANDIDATES_PER_GROUP) or chip-specific constants. Reduce to reserve channels for other RMT users.FASTLED_RMT4_TRANSMISSION_TIMEOUT_MS: Maximum time in milliseconds to wait for RMT transmission to complete before considering it stuck. Default2000(2 seconds). Set to0to disable timeout detection.FASTLED_ESP32_FLASH_LOCK: When set to1, blocks flash operations during show to avoid timing disruptions. Default0. Note: Currently only supported on IDF 3.x; IDF 4.x+ support is pending.- (Not currently used)
FASTLED_RMT_SHOW_TIMER: Timer debug toggle (legacy flag, no longer used). FASTLED_INTERRUPT_RETRY_COUNT: Global retry count when timing is disrupted (also used by the blockless path). Default2infastled_config.h.FASTLED_DEBUG_COUNT_FRAME_RETRIES: When defined, enable counters/logging of frame retries due to timing issues (used in blockless/clockless drivers).
-
RMT (IDF5 path in
rmt_5/)- Uses the ESP-IDF v5 LED Strip driver through
IRmtStrip. DMA is used automatically (FASTLED_RMT_USE_DMAmarker macro). No user macro to select DMA mode (the driver picks it at runtime withDMA_AUTO).
- Uses the ESP-IDF v5 LED Strip driver through
-
I2S-parallel
I2S_DEVICE: I2S device index. Default0.FASTLED_I2S_MAX_CONTROLLERS: Max number of parallel lanes (controllers). Default24.FASTLED_ESP32_I2S_NUM_DMA_BUFFERS: Number of I2S DMA buffers (2–16). Default2. Increasing to4often mitigates flicker during heavy interrupt activity. Seeclockless_i2s_esp32.handi2s/i2s_esp32dev.h.
-
Logging / binary size
FASTLED_ESP32_ENABLE_LOGGING: Controls inclusion/level ofesp_logmacros viaesp_log_control.h. Default: enabled only whenSKETCH_HAS_LOTS_OF_MEMORYis true; otherwise disabled to avoid pulling in heavy printf/vfprintf.FASTLED_ESP32_MINIMAL_ERROR_HANDLING: When defined and logging is disabled, overridesESP_ERROR_CHECKto abort without logging to keep binary size low.
-
Clock source override
F_CPU_RMT_CLOCK_MANUALLY_DEFINED: If defined, setsF_CPU_RMTexplicitly for SoCs where APB clock detection is problematic (e.g., some C6/H2 variants). OtherwiseF_CPU_RMTderives fromAPB_CLK_FREQ.
Unless otherwise noted, all defines should be placed before including FastLED.h in your sketch.
The ESP32 platform uses a unified hardware manager pattern for initializing SPI controllers. This provides a clean, maintainable architecture with feature flags and priority-based registration.
src/platforms/esp/32/drivers/spi_hw_manager_esp32.cpp.hpp
This file contains the initSpiHardware() function that initializes all available SPI hardware on ESP32 platforms.
The manager follows a helper function pattern with feature flags:
namespace fl {
namespace detail {
constexpr int PRIORITY_HW_16 = 9; // Highest (16-lane I2S)
constexpr int PRIORITY_HW_8 = 8; // 8-lane (ESP32-P4)
constexpr int PRIORITY_HW_4 = 7; // 4-lane (Quad SPI)
constexpr int PRIORITY_HW_2 = 6; // 2-lane (Dual SPI)
constexpr int PRIORITY_HW_1 = 5; // Lowest (Single SPI)
static void addSpiHw16IfPossible() {
#if FASTLED_ESP32_HAS_I2S
// Include and register I2S SPI implementation
#include "platforms/esp/32/drivers/i2s/spi_hw_i2s_esp32.cpp.hpp"
static auto i2s0 = fl::make_shared<SpiHwI2SESP32>(0);
SpiHw16::registerInstance(i2s0, PRIORITY_HW_16);
FL_DBG("ESP32: Added I2S SpiHw16 controller");
#endif
}
} // namespace detail
namespace platform {
void initSpiHardware() {
FL_DBG("ESP32: Initializing SPI hardware");
// Register in priority order (highest to lowest)
detail::addSpiHw16IfPossible(); // Priority 9
detail::addSpiHw8IfPossible(); // Priority 8
detail::addSpiHw4IfPossible(); // Priority 7
detail::addSpiHw2IfPossible(); // Priority 6
detail::addSpiHw1IfPossible(); // Priority 5
FL_DBG("ESP32: SPI hardware initialized");
}
} // namespace platform
} // namespace flThe manager uses these feature flags to conditionally compile hardware support:
FASTLED_ESP32_HAS_I2S- Enables 16-lane I2S-based SPI (ESP32 classic, S2, S3)FASTLED_ESP32_HAS_OCTAL_SPI- Enables 8-lane Octal SPI (ESP32-P4 only, IDF 5.0+)- SPI peripheral (1/2/4-lane) is available on all ESP32 variants
Hardware is only initialized on first access to SpiHwN::getAll(), following the Meyer's Singleton pattern:
- No static constructors run at startup
- Avoids initialization order issues
- Zero overhead if SPI hardware is not used
- Thread-safe via static local variables
| ESP32 Variant | SpiHw1 | SpiHw2 | SpiHw4 | SpiHw8 | SpiHw16 |
|---|---|---|---|---|---|
| ESP32 classic | ✅ | ✅ | ✅ | ❌ | ✅ (I2S) |
| ESP32-S2 | ✅ | ✅ | ✅ | ❌ | ✅ (I2S) |
| ESP32-S3 | ✅ | ✅ | ✅ | ❌ | ✅ (I2S) |
| ESP32-C3 | ✅ | ✅ | ✅ | ❌ | ❌ |
| ESP32-C6 | ✅ | ✅ | ✅ | ❌ | ❌ |
| ESP32-P4 | ✅ | ✅ | ✅ | ✅ (IDF 5.0+) | ❌ |
FastLED provides hardware-accelerated multi-lane SPI for parallel LED strip control via DMA.
1-Lane, 2-Lane, 4-Lane (All ESP32 variants):
- Interfaces:
SpiHw1,SpiHw2,SpiHw4 - Files:
spi_hw_1_esp32.cpp,spi_hw_2_esp32.cpp,spi_hw_4_esp32.cpp - Platforms: ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-P4
- ESP-IDF: 3.3+ (any version)
8-Lane (Octal-SPI - ESP32-P4 only):
- Interface:
SpiHw8 - File:
spi_hw_8_esp32.cpp - Platform: ESP32-P4 only
- ESP-IDF: 5.0.0 or higher required
- Pins: Up to 8 data pins (D0-D7) + 1 clock pin
- Automatic Mode Selection: Hardware driver auto-detects lane count (1/2/4/8) based on pin configuration
- Version Safety: 8-lane code guarded with ESP-IDF 5.x version checks
- Graceful Fallback: Older platforms/IDF versions automatically fall back to software implementation
- Zero CPU Usage: DMA handles transmission (~40× faster than software)
- Separate Interfaces: Clean separation between 4-lane (
SpiHw4) and 8-lane (SpiHw8)
- Speed: ~40× faster than software octo-SPI
- Transmission: 8×100 LED strips in <500µs
- CPU Usage: 0% during transmission (DMA handles everything)
Hardware SPI implementations are organized by lane count:
spi_hw_1_esp32.cpp- 1-lane (Single) SPIspi_hw_2_esp32.cpp- 2-lane (Dual) SPIspi_hw_4_esp32.cpp- 4-lane (Quad) SPIspi_hw_8_esp32.cpp- 8-lane (Octal) SPI (ESP32-P4 + IDF 5.0+)
Shared infrastructure:
src/platforms/shared/spi_hw_*.h- Platform-agnostic interfacessrc/platforms/shared/spi_transposer.*- Unified bit-interleaving for all widthssrc/platforms/shared/spi_bus_manager.h- Automatic multi-lane detection
Comprehensive unit tests validate all lane configurations:
- 1-lane, 2-lane tests:
uv run test.py test_single_spi test_dual_spi - 4-lane, 8-lane tests:
uv run test.py test_quad_spi
- All lane configurations (1/2/4) work on all ESP32 variants
- 8-lane requires ESP32-P4 with ESP-IDF 5.0+
- Implementation is backward-compatible with existing platforms
- On unsupported platforms, software bitbang fallback is used automatically
FastLED provides automatic multi-lane SPI support for driving up to 16 parallel SPI LED strips simultaneously using the same fl::Spi API. On ESP32 platforms, this is implemented via the I2S peripheral in parallel mode.
| Feature | Hardware SPI (1-8 lane) | I2S-based SPI (9-16 lane) |
|---|---|---|
| Technology | SPI peripheral with DMA | I2S peripheral in parallel mode |
| Max Lanes | 8 (ESP32-P4 only) | 16 (ESP32/S2/S3) |
| Platforms | All ESP32 variants | ESP32, ESP32-S2, ESP32-S3 |
| Pin Assignment | Flexible (any GPIO) | Fixed range (GPIO 8-23) |
| Memory | Internal RAM | PSRAM+DMA recommended |
| API | fl::Spi class |
fl::Spi class (same API!) |
| Best For | 1-8 strips, flexible pins | 9-16 strips, maximum throughput |
Note: The system automatically promotes to the appropriate hardware backend based on lane count. Users always use the same fl::Spi API.
- ESP32 (classic): 16 lanes via I2S0 parallel mode (GPIO 8-23)
- ESP32-S2: 16 lanes via I2S0 (GPIO 8-23)
- ESP32-S3: 16 lanes via I2S0 (GPIO 8-23) - Recommended with PSRAM
- ESP32-C3/C6: Not supported (only 2 I2S lanes)
Multi-lane SPI works with clock-based SPI LED chipsets:
- ✅ APA102 (tested)
- ✅ SK9822 (APA102-compatible)
- ✅ HD107S (newer, faster variant)
- ❌ WS2812/WS2811 (use the Channel API for clockless LEDs)
#include <FastLED.h>
const int CLOCK_PIN = 18;
const int DATA_PINS[] = {23, 22, 21, 19}; // 4 strips (can be up to 16!)
const int NUM_LEDS = 300;
CRGB leds[4][NUM_LEDS];
// Standard fl::Spi API - automatically uses I2S backend on ESP32
fl::Spi spi(CLOCK_PIN, DATA_PINS, fl::SPI_HW);
void setup() {
if (!spi.ok()) {
Serial.println("SPI init failed!");
while(1);
}
}
void loop() {
// Write all 4 strips in parallel using standard API
spi.write(
fl::span<const uint8_t>((uint8_t*)leds[0], NUM_LEDS * 3),
fl::span<const uint8_t>((uint8_t*)leds[1], NUM_LEDS * 3),
fl::span<const uint8_t>((uint8_t*)leds[2], NUM_LEDS * 3),
fl::span<const uint8_t>((uint8_t*)leds[3], NUM_LEDS * 3)
);
spi.wait(); // Optional: block until transmission complete
}Throughput: ~16× improvement over sequential SPI
- 16 strips × 300 LEDs transmitted in ~10ms (vs ~160ms sequential)
- Zero CPU usage during transmission (DMA handles everything)
- Supports up to 8K+ LEDs per strip with PSRAM
Multi-lane SPI requires DMA-capable memory for internal buffers:
- Small installations (<500 LEDs/strip): Internal RAM sufficient
- Large installations (>500 LEDs/strip): PSRAM strongly recommended
- ESP32-S3 EDMA enables PSRAM to be DMA-capable (automatic)
Enable PSRAM in PlatformIO:
[env:esp32s3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.memory_type = qio_qspi
build_flags =
-D BOARD_HAS_PSRAMData Pins: Must use GPIO 8-23 (I2S parallel mode range)
- Use any subset (e.g., GPIO 23-20 for 4 strips)
- Avoid GPIO 6-11 if used for SPI flash (platform-dependent)
Clock Pin: Any free GPIO (recommend GPIO 18 or higher)
- Shared across all strips
- Keep wiring short for signal integrity
- Public API:
src/fl/spi.h(unified 1-16 lane SPI API) - 16-lane interface:
src/platforms/shared/spi_hw_16.h - ESP32 I2S backend:
src/platforms/esp/32/drivers/i2s/spi_hw_i2s_esp32.{h,cpp} - Bus manager:
src/platforms/shared/spi_bus_manager.h(automatic promotion to 16-lane mode)
FastLED uses a unified SPI API (fl::Spi) that automatically handles 1-16 lanes:
- 1-2 lanes: Uses
SpiHw2(dual SPI) - 3-4 lanes: Uses
SpiHw4(quad SPI) - 5-8 lanes: Uses
SpiHw8(octal SPI) - 9-16 lanes: Uses
SpiHw16(hexadeca SPI via ESP32 I2S)
The SPIBusManager automatically detects lane count and promotes to the appropriate hardware implementation.
For comprehensive documentation, see LOOP_SPI_I2S.md which includes:
- Hardware requirements and wiring diagrams
- Complete API reference
- Integration with fl::Spi system
- Memory optimization tips
- Troubleshooting guide
- Technical implementation details
The unified fl::Spi API works on all platforms and automatically uses hardware acceleration when available:
#include <FastLED.h>
// Define data pins (4 strips example)
int data_pins[] = {8, 9, 10, 11};
// Create SPI device with hardware mode
fl::Spi spi(18, data_pins, fl::SPI_HW); // Clock on GPIO 18
if (!spi.ok()) {
Serial.println("SPI init failed");
return;
}
// Write data to all strips
spi.write(strip0, strip1, strip2, strip3);
spi.wait(); // Block until transmission completeOn ESP32, when using 9-16 lanes, the SPIBusManager automatically:
- Detects the lane count via
SpiHw16::getAll() - Finds the I2S hardware implementation (
SpiHwI2SESP32) - Promotes to hexadeca-SPI mode
- Uses hardware-accelerated parallel transmission
Two RMT implementations exist and are selected by IDF version unless you override it:
- RMT4 (ESP‑IDF v4): legacy FastLED RMT driver in
rmt_4/ - RMT5 (ESP‑IDF v5): uses the ESP‑IDF v5 LED Strip driver via
IRmtStripinrmt_5/
Selection rules:
- Default:
ESP_IDF_VERSIONdecides. On IDF < 5, RMT5 is disabled; on IDF ≥ 5, RMT5 is enabled. - To force RMT4 on IDF5, set this build define (PlatformIO
platformio.ini):This ensures only one RMT implementation is compiled. Do not link/use the IDF v5build_flags = -D FASTLED_RMT5=0
led_stripdriver in the same binary when forcing RMT4.
Important compatibility note:
- The RMT4 and RMT5 drivers cannot co‑exist in the same sketch/binary. Mixing both (e.g., using IDF v5
led_stripalongside FastLED’s RMT4) will trigger an Espressif runtime abort at startup.
Behavioral differences and practical guidance:
-
RMT4 (IDF4)
- Uses ISR-driven double-buffering for LED transmission with WiFi interference detection.
- Supports time-multiplexing for >8 LED strips via channel sharing.
- Enable
FASTLED_ESP32_FLASH_LOCKto reduce flicker during WiFi activity (IDF 3.x only).
-
RMT5 (IDF5)
- Asynchronous, DMA‑backed LED driver. Your sketch can continue running while a frame is transmitted.
- Recommended on modern IDF when you want concurrent work during frame output.
-
I2S (parallel)
- Also DMA‑driven; while frame data is being transferred to the peripheral, your loop can continue doing work. Use more DMA buffers (
FASTLED_ESP32_I2S_NUM_DMA_BUFFERS 4) to improve resilience under interrupt load.
- Also DMA‑driven; while frame data is being transferred to the peripheral, your loop can continue doing work. Use more DMA buffers (
Quick PlatformIO examples
-
Force RMT4 on IDF5 (legacy path):
[env:esp32dev_rmt4] platform = espressif32 board = esp32dev framework = arduino build_flags = -D FASTLED_RMT5=0
-
Enable I2S on ESP32Dev or ESP32‑S3 with extra DMA buffers:
[env:esp32dev_i2s] platform = espressif32 board = esp32dev framework = arduino build_flags = -D FASTLED_ESP32_I2S=1 -D FASTLED_ESP32_I2S_NUM_DMA_BUFFERS=4