src/platforms/esp/32/README.md
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).clockless_rmt_esp32.h: Common entry; dispatches to IDF4/IDF5 layers.rmt_4/: IDF4 RMT implementation.rmt_5/: IDF5 RMT implementation and strip_rmt.* glue.clockless_i2s_esp32.h: I2S driver; DMA double‑buffering, transposition/encoding.clockless_i2s_esp32s3.*: S3 variant hooks.i2s/i2s_esp32dev.*: I2S helpers.clockless_spi_esp32.h: SPI staging for WS2812‑class LEDs using spi_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.FASTLED_ESP32_HAS_RMT, FASTLED_ESP32_HAS_CLOCKLESS_SPI, or FASTLED_ESP32_I2S.ESP_IDF_VERSION (from esp_version.h) determines RMT4 vs RMT5.Notes:
FastLED supports the parallel I2S clockless driver on the following ESP32 targets in this tree:
ESP32 (classic, e.g., "ESP32Dev")
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
clockless_i2s_esp32.h, i2s/i2s_esp32dev.*ESP32-S3
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
clockless_i2s_esp32s3.* (wraps a dedicated S3 I2S clockless implementation)clockless_i2s_esp32s3.h.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 activeFASTLED_USE_PROGMEM: Control PROGMEM usage on ESP32. Default 0 in led_sysdefs_esp32.h.
FASTLED_ALLOW_INTERRUPTS: Allow interrupts during show. Default 1 in led_sysdefs_esp32.h.
FASTLED_ESP32_RAW_PIN_ORDER: Pin-order override hook defined in led_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. See clockless_i2s_esp32.h.FASTLED_ESP32_USE_CLOCKLESS_SPI: Force the WS2812-over-SPI path instead of RMT when available. Only supports WS2811/WS2812 timings. See fastled_esp32.h and clockless_spi_esp32.h.FASTLED_ESP32_HAS_RMT comes from platforms/esp/32/feature_flags/enabled.h).SPI tuning (clocked LEDs and WS2812-over-SPI path)
DATA_PIN and CLOCK_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, or FSPI. Defaults per target: S2/S3 use FSPI, others default to VSPI (see fastspi_esp32.h).FASTLED_ESP32_SPI_BULK_TRANSFER: When 1, batches pixels into blocks to reduce transfer overhead and improve throughput at the cost of RAM. Default 0.FASTLED_ESP32_SPI_BULK_TRANSFER_SIZE: Bulk block size (CRGBs) when bulk mode is enabled. Default 64.RMT (IDF4 path in rmt_4/)
FASTLED_RMT_SERIAL_DEBUG: When 1, print RMT errors to Serial for debugging. Default 0.FASTLED_RMT_MEM_WORDS_PER_CHANNEL: Override RMT words per channel. Defaults to SOC_RMT_MEM_WORDS_PER_CHANNEL on newer IDF or 64 on older.FASTLED_RMT_MEM_BLOCKS: Number of RMT memory blocks per channel to use. Default 2. 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. Default 2000 (2 seconds). Set to 0 to disable timeout detection.FASTLED_ESP32_FLASH_LOCK: When set to 1, blocks flash operations during show to avoid timing disruptions. Default 0. Note: Currently only supported on IDF 3.x; IDF 4.x+ support is pending.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). Default 2 in fastled_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/)
IRmtStrip. DMA is used automatically (FASTLED_RMT_USE_DMA marker macro). No user macro to select DMA mode (the driver picks it at runtime with DMA_AUTO).I2S-parallel
I2S_DEVICE: I2S device index. Default 0.FASTLED_I2S_MAX_CONTROLLERS: Max number of parallel lanes (controllers). Default 24.FASTLED_ESP32_I2S_NUM_DMA_BUFFERS: Number of I2S DMA buffers (2–16). Default 2. Increasing to 4 often mitigates flicker during heavy interrupt activity. See clockless_i2s_esp32.h and i2s/i2s_esp32dev.h.Logging / binary size
FASTLED_ESP32_ENABLE_LOGGING: Controls inclusion/level of esp_log macros via esp_log_control.h. Default: enabled only when SKETCH_HAS_LOTS_OF_MEMORY is true; otherwise disabled to avoid pulling in heavy printf/vfprintf.FASTLED_ESP32_MINIMAL_ERROR_HANDLING: When defined and logging is disabled, overrides ESP_ERROR_CHECK to abort without logging to keep binary size low.Clock source override
F_CPU_RMT_CLOCK_MANUALLY_DEFINED: If defined, sets F_CPU_RMT explicitly for SoCs where APB clock detection is problematic (e.g., some C6/H2 variants). Otherwise F_CPU_RMT derives from APB_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 fl
The 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+)Hardware is only initialized on first access to SpiHwN::getAll(), following the Meyer's Singleton pattern:
| 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):
SpiHw1, SpiHw2, SpiHw4spi_hw_1_esp32.cpp, spi_hw_2_esp32.cpp, spi_hw_4_esp32.cpp8-Lane (Octal-SPI - ESP32-P4 only):
SpiHw8spi_hw_8_esp32.cppSpiHw4) and 8-lane (SpiHw8)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_manager.h - Automatic multi-lane detectionComprehensive unit tests validate all lane configurations:
uv run test.py test_single_spi test_dual_spiuv run test.py test_quad_spiFastLED 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.
Multi-lane SPI works with clock-based SPI LED chipsets:
#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
Multi-lane SPI requires DMA-capable memory for internal buffers:
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_PSRAM
Data Pins: Must use GPIO 8-23 (I2S parallel mode range)
Clock Pin: Any free GPIO (recommend GPIO 18 or higher)
src/fl/spi.h (unified 1-16 lane SPI API)src/platforms/shared/spi_hw_16.hsrc/platforms/esp/32/drivers/i2s/spi_hw_i2s_esp32.{h,cpp}src/platforms/shared/spi_manager.h (automatic promotion to 16-lane mode)FastLED uses a unified SPI API (fl::Spi) that automatically handles 1-16 lanes:
SpiHw2 (dual SPI)SpiHw4 (quad SPI)SpiHw8 (octal SPI)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:
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 complete
On ESP32, when using 9-16 lanes, the SPIBusManager automatically:
SpiHw16::getAll()SpiHwI2SESP32)Two RMT implementations exist and are selected by IDF version unless you override it:
rmt_4/IRmtStrip in rmt_5/Selection rules:
ESP_IDF_VERSION decides. On IDF < 5, RMT5 is disabled; on IDF ≥ 5, RMT5 is enabled.platformio.ini):
build_flags =
-D FASTLED_RMT5=0
led_strip driver in the same binary when forcing RMT4.Important compatibility note:
led_strip alongside FastLED’s RMT4) will trigger an Espressif runtime abort at startup.Behavioral differences and practical guidance:
RMT4 (IDF4)
FASTLED_ESP32_FLASH_LOCK to reduce flicker during WiFi activity (IDF 3.x only).RMT5 (IDF5)
I2S (parallel)
FASTLED_ESP32_I2S_NUM_DMA_BUFFERS 4) to improve resilience under interrupt load.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