Skip to content

Latest commit

 

History

History
469 lines (364 loc) · 20 KB

File metadata and controls

469 lines (364 loc) · 20 KB

FastLED Platform: ESP32 (Xtensa/RISC‑V)

ESP32 family support with multiple clockless backends.

Aggregator & sysdefs

  • 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).

Backends

  • RMT (recommended):
    • 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.
  • 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 using spi_ws2812/.
    • spi_ws2812/: SPI strip driver.

Pins, SPI, timing

  • fastpin_esp32.h: Pin helpers for direct GPIO.
  • fastspi_esp32.h: SPI backend helpers.
  • clock_cycles.h: Timing helpers.

Behavior & selection

  • Define one of: FASTLED_ESP32_HAS_RMT, FASTLED_ESP32_HAS_CLOCKLESS_SPI, or FASTLED_ESP32_I2S.
  • ESP_IDF_VERSION (from esp_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.

I2S support by board (parallel output)

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.
  • 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.

Additional I2S defines and guidance:

  • FASTLED_I2S_MAX_CONTROLLERS (default 24): maximum parallel lanes
  • FASTLED_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

Optional feature defines

  • FASTLED_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.
    • The normal default is RMT when available (FASTLED_ESP32_HAS_RMT comes from platforms/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_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.
    • (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). 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/)

    • Uses the ESP-IDF v5 LED Strip driver through 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.

SPI Hardware Manager

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.

Manager File

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.

Architecture

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

Feature Flags

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+)
  • SPI peripheral (1/2/4-lane) is available on all ESP32 variants

Lazy Initialization

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

Platform Support

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+)

Hardware Multi-Lane SPI Support

FastLED provides hardware-accelerated multi-lane SPI for parallel LED strip control via DMA.

Supported Configurations

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

Key Features

  • 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)

Expected Performance (8-Lane)

  • Speed: ~40× faster than software octo-SPI
  • Transmission: 8×100 LED strips in <500µs
  • CPU Usage: 0% during transmission (DMA handles everything)

Implementation Files

Hardware SPI implementations are organized by lane count:

  • spi_hw_1_esp32.cpp - 1-lane (Single) SPI
  • spi_hw_2_esp32.cpp - 2-lane (Dual) SPI
  • spi_hw_4_esp32.cpp - 4-lane (Quad) SPI
  • spi_hw_8_esp32.cpp - 8-lane (Octal) SPI (ESP32-P4 + IDF 5.0+)

Shared infrastructure:

  • src/platforms/shared/spi_hw_*.h - Platform-agnostic interfaces
  • src/platforms/shared/spi_transposer.* - Unified bit-interleaving for all widths
  • src/platforms/shared/spi_bus_manager.h - Automatic multi-lane detection

Testing

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

Notes

  • 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

Multi-Lane SPI: I2S-Based 16-Lane Parallel SPI

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.

Implementation Details by Lane Count

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.

Supported Platforms

  • 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)

Supported LED Chipsets

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)

Quick Example

#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
}

Performance

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

Memory Requirements

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_PSRAM

Pin Configuration

Data 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

Implementation Files

  • 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)

Architecture

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.

Documentation

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

Using Multi-Lane SPI

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:

  1. Detects the lane count via SpiHw16::getAll()
  2. Finds the I2S hardware implementation (SpiHwI2SESP32)
  3. Promotes to hexadeca-SPI mode
  4. Uses hardware-accelerated parallel transmission

RMT backends (IDF4 vs IDF5) and how to select

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 IRmtStrip in rmt_5/

Selection rules:

  • Default: ESP_IDF_VERSION decides. On IDF < 5, RMT5 is disabled; on IDF ≥ 5, RMT5 is enabled.
  • To force RMT4 on IDF5, set this build define (PlatformIO platformio.ini):
    build_flags =
      -D FASTLED_RMT5=0
    This ensures only one RMT implementation is compiled. Do not link/use the IDF v5 led_strip driver 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_strip alongside 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_LOCK to 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.

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