Skip to content

I2S ESP32Dev driver: I2S ESP32 IDF 5 issues #2136

@zackees

Description

@zackees

ESP32 I2S Investigation Report

disclaimer: AI generated, contains misinformation

Executive Summary

FastLED's ESP32 I2S parallel driver is incompatible with Arduino ESP32 Core 3.0+ (ESP-IDF 5.1+) due to a complete architectural redesign of the I2S driver that occurred in ESP-IDF 5.0. The fundamental issue is that FastLED uses direct low-level register access (soc/i2s_struct.h, soc/i2s_reg.h) and configures I2S in LCD parallel mode - an approach that worked reliably with ESP-IDF 4.4 but conflicts with the new channel-based API architecture introduced in ESP-IDF 5.0.

Key Finding: The I2S LCD mode that FastLED depends on is no longer implemented by the I2S driver in ESP-IDF 5.0+. The official documentation explicitly states: "LCD/Camera mode is only supported on I2S0 over a parallel bus. These two modes are not implemented by the I2S driver" and redirects users to dedicated LCD interface drivers instead.

Critical Discovery: While soc/i2s_struct.h register headers still exist in ESP-IDF 5.x (confirmed to be present in master branch as of 2025), the ESP-IDF hardware abstraction layer (HAL/LL) is marked as experimental and "does not adhere to API name changing restrictions," meaning register layouts and access patterns can change between non-major releases.

Recommended Path Forward: The most viable solution is to continue using low-level register access via HAL/LL layers with conditional compilation for ESP-IDF 5.x, but this requires significant testing and carries ongoing maintenance burden as the HAL/LL API is experimental. Alternative approaches (rewriting for high-level API or switching peripherals) are less viable due to the removal of I2S LCD mode support.


Research Findings

ESP-IDF 5.0/5.1 I2S Driver Redesign

The I2S driver underwent a complete ground-up rewrite starting in ESP-IDF 5.0, which carries forward to 5.1 used by Arduino Core 3.0. This was not a simple API update but a fundamental architectural change.

What Changed in ESP-IDF 5.0

1. Driver Architecture: Controller-Based → Channel-Based

  • Old (IDF 4.4): Control entire I2S controller (I2S0/I2S1) as single unit
  • New (IDF 5.0+): Independent TX/RX channel control with i2s_chan_handle_t handles
  • Impact: Minimum control unit changed from controller-level to channel-level

2. Header File Reorganization

// ESP-IDF 4.4
#include "driver/i2s.h"  // Single monolithic driver

// ESP-IDF 5.0+
#include "driver/i2s_std.h"  // Standard mode (Philips, MSB, PCM)
#include "driver/i2s_pdm.h"  // PDM mode
#include "driver/i2s_tdm.h"  // TDM mode
// Legacy APIs moved to:
#include "driver/deprecated/driver/i2s.h"  // Deprecated with warnings

3. Initialization Pattern Completely Changed

ESP-IDF 4.4 Pattern:

i2s_config_t config = { /* ... */ };
i2s_driver_install(I2S_NUM_0, &config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);
i2s_write(I2S_NUM_0, data, size, &bytes_written, timeout);

ESP-IDF 5.0+ Pattern:

// 1. Allocate channel
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
i2s_new_channel(&chan_cfg, &tx_handle, NULL);

// 2. Initialize mode-specific configuration
i2s_std_config_t std_cfg = {
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000),
    .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
    .gpio_cfg = { /* GPIO pins */ }
};
i2s_channel_init_std_mode(tx_handle, &std_cfg);

// 3. Explicitly enable channel
i2s_channel_enable(tx_handle);

// 4. Write data
i2s_channel_write(tx_handle, data, size, &bytes_written, timeout);

4. LCD Mode Removed from I2S Driver

This is the most critical issue for FastLED:

Official ESP-IDF 5.x Documentation: "LCD/Camera mode is only supported on I2S0 over a parallel bus. These two modes are not implemented by the I2S driver. Please refer to I80 Interfaced LCD for more information."

  • ESP-IDF 4.4: I2S driver directly supported LCD parallel mode via i2s_config_t with mode I2S_MODE_MASTER | I2S_MODE_TX
  • ESP-IDF 5.0+: LCD mode removed from I2S driver entirely - redirected to dedicated LCD interface
  • Impact on FastLED: The core functionality FastLED depends on (parallel 24-bit output via I2S LCD mode) is no longer officially supported by the I2S driver

5. ADC/DAC Mode Also Removed

ESP-IDF 5.0+ removed ADC/DAC modes from I2S driver, requiring separate peripheral drivers for audio routing.

6. State Machine Enforcement

The new driver implements strict state transitions:

  • REGISTERED (allocated) → READY (initialized) → RUNNING (enabled)
  • APIs will fail if called in incorrect state
  • Must disable before reconfiguring

7. DMA Buffer Management

While both versions use DMA, the management approach differs:

  • IDF 4.4: Manual linked-list descriptor management via lldesc_t
  • IDF 5.0+: Automatic buffer management with size calculation: dma_buffer_size = dma_frame_num * slot_num * slot_bit_width / 8

What Did NOT Change (Important for Solutions)

Low-Level Register Headers Still Exist:

  • soc/i2s_struct.h - Confirmed present in ESP-IDF master branch (2025)
  • soc/i2s_reg.h - Register definitions
  • soc/gpio_sig_map.h - Signal routing
  • rom/lldesc.h - DMA descriptor structures

However, the ESP-IDF Hardware Abstraction Layer documentation warns:

"Hardware abstraction API (excluding the driver and xxx_types.h) should be considered an experimental feature... does not adhere to the API name changing restrictions."

This means:

  • ✅ Register headers are still available
  • ⚠️ Register layouts may change between non-major releases
  • ⚠️ No stability guarantees for low-level access
  • ⚠️ HAL/LL API is experimental

Arduino ESP32 Core 3.0 Implementation

Arduino ESP32 Core 3.0 is based on ESP-IDF 5.1.4 and inherits all the I2S driver changes from ESP-IDF 5.0+.

Migration Guide Findings

The official Arduino ESP32 migration guide provides minimal detail on I2S:

"The I2S driver has been completely redesigned and refactored to use the new ESP-IDF driver. For more information about the new API, check I2S documentation."

Unlike other peripherals (Timer, LEDC, RMT) where specific removed/new APIs are enumerated, the I2S section lacks detail, suggesting substantial architectural modifications requiring complete API specification review rather than incremental updates.

Version Compatibility Matrix

Arduino Core PlatformIO ESP-IDF I2S LCD Mode Status
2.0.17 espressif32@6.4.0 4.4.7 ✅ Available ✅ FastLED Works
2.0.18 espressif32@6.5.0 4.4.8 ✅ Available ✅ FastLED Works
3.0.0 espressif32@6.6.0 5.1.4 ❌ Removed ⚠️ FastLED Issues
3.0.1 espressif32@6.7.0 5.1.4 ❌ Removed ⚠️ FastLED Issues
3.0.2+ espressif32@6.8.0+ 5.1.4+ ❌ Removed ⚠️ FastLED Issues
3.2.0 espressif32@? 5.x ❌ Removed ⚠️ Needs Testing

Note: Arduino Core 3.2.0 was mentioned in FastLED release notes as "now known to work" with auto-error on known bad versions, but I2S driver compatibility with Core 3.x is still problematic.


Community Experience

GitHub Issues Found

1. ESP-IDF Repository

  • Issue Control over 3000 WS2812B LEDs #1056: "[TW#15701] I2S parallel mode support" - Long-standing discussion about I2S parallel mode
  • Issue #5238: "[ESP32-S2] How do I use the I2S to control a 8-bit parallel LCD?" - User directed away from I2S to dedicated LCD driver
  • Multiple users: Reported conflicts when mixing legacy and new I2S drivers

2. Arduino-ESP32 Repository

  • Issue #10786 (Dec 2024): Legacy driver conflicts - "Starting with ESP-IDF v5, new drivers have appeared... Mixing new and legacy drivers can cause malfunctions"
  • Issue #11004 (Feb 2025): "I2S failed to set up tx callback" with errors like "gdma_register_tx_event_callbacks(464): user context not in internal RAM"
  • Issue #11058: "I2S issues in v3.1.3" affecting ESP32-S3
  • Issue #8796: "3.0.0 version Migration related issues"

3. FastLED Repository

  • No specific GitHub issues found linking "ESP32 I2S" with "Arduino Core 3.0" in search results
  • FastLED wiki and release notes mention ESP32-Arduino Core version compatibility
  • Community relies heavily on staying on Arduino Core 2.0.17 for I2S support

ESP32 Forum Discussions

Forum Thread: "I2S v5.1 upgrade: conflicts" (esp32.com/viewtopic.php?t=35239)

  • User upgraded to ESP-IDF v5.1 by replacing driver/i2s.h with driver/i2s_std.h
  • Received error: "CONFLICT! The new i2s driver can't work along with the legacy i2s driver"
  • Issue: Cannot mix old and new driver code in same project
  • Resolution: Complete rewrite required; no incremental migration path

Forum Thread: "API 5.x I2S legacy issue" (esp32.com/viewtopic.php?t=35422)

  • Similar conflicts reported
  • Legacy API warnings can be suppressed with CONFIG_I2S_SUPPRESS_DEPRECATE_WARN
  • But mixing APIs causes runtime conflicts

Forum Discussions on I2S Parallel/LCD Mode:

  • Multiple threads (dating back to IDF v4.x) discuss using I2S parallel mode for LED displays
  • Community projects like esp_i2s_parallel (TobleMiner) widely referenced
  • No clear migration path to ESP-IDF 5.x for these parallel output use cases

Third-Party Library Experience

esp_i2s_parallel (TobleMiner/esp_i2s_parallel)

  • Popular component for parallel I2S output using LCD mode and DMA
  • Targets ESP-IDF 4.x (version not explicitly stated in README)
  • No ESP-IDF 5.x compatibility information
  • Transmit-only due to LCD mode limitations
  • Key differences between I2S0/I2S1:
    • I2S0: 8-bit samples must be extended to 16-bit, limited to peripheral_clk / 4
    • I2S1: True 8-bit mode supported, achieves peripheral_clk / 2 in 8-bit mode

Other LED Matrix Projects:

  • Multiple projects (esp-idf-parallel-tft, esp32-leddisplay) use I2S parallel for displays
  • Most target ESP-IDF v3.x to v4.x
  • One project (esp-idf-parallel-tft) requires "ESP-IDF V5.0 or later" but uses dedicated LCD driver, not I2S

Technical Analysis

Current FastLED Implementation

FastLED's I2S driver (src/platforms/esp/32/drivers/i2s/i2s_esp32dev.{h,cpp}) uses a sophisticated low-level approach:

1. Direct Register Access

FastLED includes low-level headers and directly manipulates I2S peripheral registers:

// i2s_esp32dev.h (lines 11-14)
#include "soc/gpio_sig_map.h"
#include "soc/i2s_reg.h"
#include "soc/i2s_struct.h"
#include "soc/io_mux_reg.h"

// i2s_esp32dev.cpp (lines 65-66)
static i2s_dev_t *i2s;  // Pointer to I2S0 or I2S1 memory-mapped registers

2. LCD Parallel Mode Configuration

FastLED configures I2S in LCD mode for 24-bit parallel output:

// i2s_esp32dev.cpp (lines 366-378)
void i2s_init(int i2s_device) {
    // Main configuration
    i2s->conf.tx_msb_right = 1;
    i2s->conf.tx_mono = 0;
    i2s->conf.tx_short_sync = 0;
    i2s->conf.tx_msb_shift = 0;
    i2s->conf.tx_right_first = 1;
    i2s->conf.tx_slave_mod = 0;

    // Set parallel mode (LCD mode)
    i2s->conf2.val = 0;
    i2s->conf2.lcd_en = 1;              // Enable LCD mode
    i2s->conf2.lcd_tx_wrx2_en = 0;      // 32-bit parallel output
    i2s->conf2.lcd_tx_sdx2_en = 0;
}

3. Clock Configuration via Register Fields

Precise clock timing calculated for LED chipset requirements:

// i2s_esp32dev.cpp (lines 380-392)
i2s->sample_rate_conf.val = 0;
i2s->sample_rate_conf.tx_bits_mod = 32;  // 32 parallel bits/pins
i2s->sample_rate_conf.tx_bck_div_num = 1;
i2s->clkm_conf.val = 0;
i2s->clkm_conf.clka_en = 0;
// Clock = BASE_CLK / (div_num + (div_b / div_a))
// Base is 80MHz, so 80/(10 + 0/1) = 8MHz (125ns per cycle)
i2s->clkm_conf.clkm_div_a = CLOCK_DIVIDER_A;
i2s->clkm_conf.clkm_div_b = CLOCK_DIVIDER_B;
i2s->clkm_conf.clkm_div_num = CLOCK_DIVIDER_N;

4. Manual DMA Descriptor Management

FastLED manually creates and links DMA descriptors:

// i2s_esp32dev.h (lines 62-65)
struct I2SDMABuffer {
    lldesc_t descriptor;  // Low-level DMA descriptor from rom/lldesc.h
    uint8_t *buffer;
};

// i2s_esp32dev.cpp (lines 92-109)
static I2SDMABuffer *allocateDMABuffer(int bytes) {
    I2SDMABuffer *b = (I2SDMABuffer *)heap_caps_malloc(sizeof(I2SDMABuffer), MALLOC_CAP_DMA);
    b->buffer = (uint8_t *)heap_caps_malloc(bytes, MALLOC_CAP_DMA);

    b->descriptor.length = bytes;
    b->descriptor.size = bytes;
    b->descriptor.owner = 1;
    b->descriptor.sosf = 1;
    b->descriptor.buf = b->buffer;
    b->descriptor.offset = 0;
    b->descriptor.empty = 0;
    b->descriptor.eof = 1;
    b->descriptor.qe.stqe_next = 0;

    return b;
}

// Circular linked list of DMA buffers (lines 416-419)
for (int i = 0; i < NUM_DMA_BUFFERS; i++) {
    dmaBuffers[i]->descriptor.qe.stqe_next =
        &(dmaBuffers[(i + 1) % NUM_DMA_BUFFERS]->descriptor);
}

5. Custom Interrupt Handler

Direct ISR registration for DMA completion events:

// i2s_esp32dev.cpp (lines 124-146)
static IRAM_ATTR void interruptHandler(void *arg) {
    if (i2s->int_st.out_eof) {  // End-of-frame interrupt
        i2s->int_clr.val = i2s->int_raw.val;
        // Handle buffer completion, trigger callbacks
        // ...
    }
}

// Register interrupt (lines 421-426)
SET_PERI_REG_BITS(I2S_INT_ENA_REG(i2s_device), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S);
esp_intr_alloc(interruptSource, 0, &interruptHandler, 0, &gI2S_intr_handle);

6. GPIO Matrix Routing

Manual signal routing to arbitrary GPIO pins:

// i2s_esp32dev.cpp (lines 518-524)
void i2s_setup_pin(int _pin, int offset) {
    gpio_num_t pin = (gpio_num_t)_pin;
    PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO);
    gpio_set_direction(pin, GPIO_MODE_DEF_OUTPUT);
    gpio_matrix_out(pin, i2s_base_pin_index + offset, false, false);
}

For I2S0: i2s_base_pin_index = I2S0O_DATA_OUT0_IDX (routes to pins DATA0-DATA23)

7. Bit Encoding for LED Timing

Sophisticated pulse-width encoding to match LED chipset timing (WS2812, APA102, etc.):

// Each LED data bit encoded as multiple I2S pulses
// Example: WS2812 at 8MHz I2S clock (125ns per pulse)
//   "1" bit: 875ns HIGH → encoded as 7 pulses HIGH (7 * 125ns = 875ns)
//   "0" bit: 375ns HIGH → encoded as 3 pulses HIGH (3 * 125ns = 375ns)

// i2s_esp32dev.cpp (lines 190-340)
void i2s_define_bit_patterns(const ChipsetTiming& TIMING) {
    // Calculate optimal clock divider for chipset timing
    // Generate pulse patterns for "1" and "0" bits
    // Transpose 8x8 matrices for parallel output
}

8. ESP-IDF 5.0 Partial Compatibility Patch

FastLED includes a minimal patch for ESP-IDF 5.0:

// i2s_esp32dev.cpp (lines 23-28)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// Patches the i2s driver for compatibility with ESP-IDF v5.0.
// This has only been compile tested. If there are issues then please file a bug.
#include "soc/gpio_periph.h"
#define gpio_matrix_out esp_rom_gpio_connect_out_signal
#endif

Critical Note: Comment states "This has only been compile tested" - runtime testing unconfirmed.


Compatibility Issues Identified

Issue 1: I2S LCD Mode Removed from Driver API

Problem: FastLED relies on i2s->conf2.lcd_en = 1 to enable LCD parallel mode, but this mode is no longer implemented by the I2S driver in ESP-IDF 5.0+.

Evidence:

  • ESP-IDF 5.x documentation: "LCD/Camera mode... not implemented by the I2S driver"
  • Redirects users to I80 Interfaced LCD driver instead
  • New I2S driver focuses on audio modes (Standard, PDM, TDM) only

Impact: Even if low-level registers still exist, the hardware initialization and DMA routing for LCD mode may not work as expected with the new driver architecture.

Issue 2: Potential Register Layout Changes

Problem: While soc/i2s_struct.h still exists, the HAL/LL API is marked experimental with no stability guarantees.

Evidence:

  • ESP-IDF docs: "Hardware abstraction API... does not adhere to API name changing restrictions"
  • Register layouts may change between non-major releases
  • i2s_dev_t structure fields could be reorganized

Risk Assessment:

  • Low risk for ESP-IDF 5.1.4 (Arduino Core 3.0) - register layouts likely stable within 5.x series
  • Medium risk for future ESP-IDF 6.x - potential breaking changes to register structures
  • Ongoing maintenance burden - must track ESP-IDF register changes

Issue 3: GPIO Matrix Function Renamed

Problem: gpio_matrix_out() function renamed in ESP-IDF 5.0+.

Current Workaround:

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define gpio_matrix_out esp_rom_gpio_connect_out_signal
#endif

Status: ✅ Already patched, but indicates broader API instability

Issue 4: Peripheral Control Header Moved

Problem: periph_ctrl.h moved to private API in ESP-IDF 5.0+.

Current Workaround:

#if defined(ESP_IDF_VERSION_MAJOR) && ESP_IDF_VERSION_MAJOR >= 5
#include "esp_private/periph_ctrl.h"
#else
#include "driver/periph_ctrl.h"
#endif

Status: ✅ Already handled

Issue 5: Register Access Macro Changes

Problem: ESP-IDF 5.0+ changed register access macros to statement-only (not expressions).

Evidence (ESP-IDF migration guide):

"Register access macros which write or read-modify-write the register can no longer be used as expressions, and can only be used as statements."

Affected Macros: REG_WRITE, REG_SET_BIT, REG_CLR_BIT, SET_PERI_REG_BITS, etc.

Impact on FastLED: Code like this may break:

// i2s_esp32dev.cpp line 422
SET_PERI_REG_BITS(I2S_INT_ENA_REG(i2s_device), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S);

Risk: ⚠️ Needs verification - this could cause compilation errors or unexpected behavior

Issue 6: Conflict with New I2S Driver

Problem: Community reports that new I2S driver conflicts with direct register manipulation.

Evidence:

  • ESP32 forum user error: "CONFLICT! The new i2s driver can't work along with the legacy i2s driver"
  • Issue occurs when both high-level API and low-level register access are used in same project
  • Likely due to shared peripheral state management

Impact: If Arduino Core 3.0 internally uses the new I2S driver (even for unrelated audio tasks), it might conflict with FastLED's register access.

Risk: ⚠️ HIGH - This could be the primary cause of runtime issues

Issue 7: DMA GDMA Migration

Problem: ESP32-S3 and newer chips use GDMA (General DMA) instead of legacy DMA engine.

Evidence from Arduino-ESP32 Issue #11004:

"gdma_register_tx_event_callbacks(464): user context not in internal RAM"

Impact: DMA descriptor format and management may differ between ESP32 (legacy DMA) and ESP32-S3 (GDMA).

FastLED Status: Has separate ESP32-S3 implementation (clockless_i2s_esp32s3.{h,cpp}) but may need ESP-IDF 5.x updates.


Register/API Differences: IDF 4.4 vs 5.1

Header Files Comparison

Component ESP-IDF 4.4 ESP-IDF 5.0/5.1 Status
I2S Driver driver/i2s.h driver/i2s_std.h / i2s_pdm.h / i2s_tdm.h Changed
Legacy Driver N/A driver/deprecated/driver/i2s.h Deprecated
Register Structures soc/i2s_struct.h soc/i2s_struct.h ✅ Available
Register Definitions soc/i2s_reg.h soc/i2s_reg.h ✅ Available
DMA Descriptors rom/lldesc.h rom/lldesc.h ✅ Available
GPIO Signal Map soc/gpio_sig_map.h soc/gpio_sig_map.h ✅ Available
Peripheral Control driver/periph_ctrl.h esp_private/periph_ctrl.h Moved
GPIO Matrix gpio_matrix_out() esp_rom_gpio_connect_out_signal() Renamed

I2S Configuration Register Access

ESP-IDF 4.4 - Direct Register Access (FastLED's Approach):

i2s_dev_t *i2s = &I2S0;  // Memory-mapped register structure

// Configuration via register fields
i2s->conf.tx_start = 1;
i2s->conf.tx_reset = 1;
i2s->conf2.lcd_en = 1;
i2s->sample_rate_conf.tx_bits_mod = 32;
i2s->clkm_conf.clkm_div_num = 10;

// Start DMA
i2s->out_link.addr = (uint32_t)&dma_descriptor;
i2s->out_link.start = 1;

ESP-IDF 5.0+ - High-Level Channel API (Official Approach):

// Allocate channel
i2s_chan_handle_t tx_handle;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_new_channel(&chan_cfg, &tx_handle, NULL);

// Configure standard mode
i2s_std_config_t std_cfg = {
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000),
    .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
    .gpio_cfg = { .mclk = -1, .bclk = 4, .ws = 5, .dout = 18, .din = -1 }
};
i2s_channel_init_std_mode(tx_handle, &std_cfg);

// Enable and write
i2s_channel_enable(tx_handle);
i2s_channel_write(tx_handle, data, size, &bytes_written, portMAX_DELAY);

ESP-IDF 5.0+ - HAL/LL Low-Level Access (Potential FastLED Path):

#include "hal/i2s_hal.h"
#include "hal/i2s_ll.h"

// Access HAL context (abstraction over registers)
i2s_hal_context_t hal_ctx;
i2s_hal_init(&hal_ctx, 0);  // I2S0

// Use LL functions for register access
i2s_ll_tx_enable(&hal_ctx);
i2s_ll_tx_start(&hal_ctx);
// ... (exact functions require ESP-IDF source code analysis)

Key API Differences

Operation ESP-IDF 4.4 ESP-IDF 5.0+
Install driver i2s_driver_install() i2s_new_channel()
Configure mode i2s_config_t (unified) i2s_channel_init_std_mode() etc.
Set GPIO pins i2s_set_pin() GPIO config in std_cfg.gpio_cfg
Write data i2s_write() i2s_channel_write()
Start transmission i2s_start() (if using direct regs) i2s_channel_enable()
LCD mode enable i2s_config_t.mode flags ❌ Not available (removed)
Direct register access i2s_dev_t *i2s = &I2S0; ⚠️ Experimental HAL/LL only

Proposed Solutions

Solution 1: Use HAL/LL Layer with Conditional Compilation

Feasibility: Medium-High
Complexity: 7/10
Maintenance Burden: High

Approach

Continue using low-level register access but migrate from direct i2s_dev_t structures to ESP-IDF HAL/LL functions. Add conditional compilation for ESP-IDF 5.x.

Pros

  • ✅ Maintains FastLED's precision timing control
  • ✅ Supports up to 24 parallel LED strips (vs 8 for RMT)
  • ✅ Register headers (soc/i2s_struct.h) confirmed to still exist in ESP-IDF 5.x
  • ✅ No fundamental architectural changes required
  • ✅ Can keep existing bit encoding and DMA management logic
  • ✅ Backward compatible with ESP-IDF 4.4 via #if ESP_IDF_VERSION >= ...

Cons

  • ❌ HAL/LL API is experimental - no stability guarantees
  • ❌ Register layouts may change in future ESP-IDF releases
  • ❌ Requires deep ESP-IDF source code analysis to find correct LL functions
  • ❌ Ongoing maintenance burden for each ESP-IDF major version
  • ❌ I2S LCD mode is "not implemented by I2S driver" - uncertain if registers still work
  • ❌ Potential conflicts with new I2S driver if both are used
  • ❌ Limited documentation - HAL/LL APIs not in public docs

Implementation Steps

  1. Research ESP-IDF 5.x HAL/LL I2S APIs

    • Examine components/hal/esp32/include/hal/i2s_ll.h in ESP-IDF source
    • Identify LL functions for:
      • Peripheral enable/reset (periph_module_enable)
      • Clock configuration (clkm_conf, sample_rate_conf)
      • LCD mode enable (conf2.lcd_en)
      • DMA setup (out_link.addr, out_link.start)
      • Interrupt configuration
  2. Verify Register Availability

    • Confirm i2s_dev_t structure in ESP-IDF 5.1.4 has:
      • conf.tx_start, conf.tx_reset, etc.
      • conf2.lcd_en, conf2.lcd_tx_wrx2_en
      • clkm_conf, sample_rate_conf
    • Check for renamed or removed fields
  3. Test LCD Mode Hardware

    • Create minimal test program using direct registers on ESP-IDF 5.1
    • Verify LCD mode still functions at hardware level
    • Test on ESP32 (original), ESP32-S2, ESP32-S3
  4. Implement Conditional Compilation

    #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
    // Use HAL/LL functions or adapted register access
    #include "hal/i2s_ll.h"
    #include "hal/i2s_hal.h"
    
    void i2s_init_idf5(int i2s_device) {
        // ESP-IDF 5.x initialization using HAL/LL or direct registers
        // ...
    }
    #else
    // Use existing ESP-IDF 4.4 code
    void i2s_init_idf4(int i2s_device) {
        // Current FastLED implementation
        // ...
    }
    #endif
    
    void i2s_init(int i2s_device) {
    #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
        i2s_init_idf5(i2s_device);
    #else
        i2s_init_idf4(i2s_device);
    #endif
    }
  5. Handle Arduino Core Detection

    // Detect Arduino Core version
    #if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR >= 3
    #define FASTLED_ARDUINO_CORE_3_OR_HIGHER 1
    #endif
  6. Comprehensive Testing

    • Test on Arduino Core 3.0, 3.1, 3.2
    • Test with WiFi/Bluetooth active (high interrupt load)
    • Test multiple DMA buffer sizes
    • Verify no conflicts with Arduino libraries using I2S
    • Test on multiple ESP32 variants

Code Example: Adapted Register Access

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)

void i2s_init_idf5(int i2s_device) {
    i2s_dev_t *i2s = (i2s_device == 0) ? &I2S0 : &I2S1;

    // Enable peripheral (function moved in IDF 5.x)
    if (i2s_device == 0) {
        periph_module_enable(PERIPH_I2S0_MODULE);
    } else {
        periph_module_enable(PERIPH_I2S1_MODULE);
    }

    // Reset - verify register fields still exist
    i2s_reset();
    i2s_reset_dma();
    i2s_reset_fifo();

    // Check if LCD mode registers still accessible
    i2s->conf2.lcd_en = 1;
    // If this causes crash or doesn't work, LCD mode is truly unusable

    // Continue with existing FastLED logic
    // ...
}

#endif

Risk Mitigation

  • Runtime detection: Check if LCD mode actually works by verifying register writes
  • Fallback mechanism: If LCD mode fails, disable I2S driver with clear error message
  • Version pinning: Document tested ESP-IDF/Arduino Core versions
  • Community testing: Beta release for Arduino Core 3.x users to report issues

Estimated Effort

  • Research/prototyping: 16-24 hours
  • Implementation: 24-40 hours
  • Testing across platforms: 16-24 hours
  • Documentation: 8 hours
  • Total: 64-96 hours (8-12 developer days)

Solution 2: Migrate to High-Level I2S API (Standard Mode)

Feasibility: Low
Complexity: 9/10
Maintenance Burden: Medium

Approach

Rewrite I2S driver to use new ESP-IDF 5.x channel-based API with Standard mode, abandoning LCD parallel mode.

Pros

  • ✅ Uses officially supported API with stability guarantees
  • ✅ Future-proof against ESP-IDF changes
  • ✅ No conflicts with other I2S users
  • ✅ Automatic DMA management by driver
  • ✅ Better error handling and state management

Cons

  • LCD mode is removed - Standard/PDM/TDM modes don't provide parallel output
  • ❌ Standard I2S is serial (2 channels: BCLK, DOUT) not parallel (24 channels)
  • ❌ Cannot achieve same parallel strip count as LCD mode
  • ❌ Timing precision may be different
  • ❌ Complete rewrite required
  • Fundamentally incompatible with parallel LED driving requirements

Verdict

Not Viable: Standard I2S mode is for audio (serial stereo data), not parallel multi-pin output. This would require abandoning parallel LED support entirely or finding alternative peripheral.


Solution 3: Use Alternative Peripheral (Dedicated LCD Driver)

Feasibility: Low-Medium
Complexity: 8/10
Maintenance Burden: Medium

Approach

Since ESP-IDF 5.x directs LCD mode users to "I80 Interfaced LCD" driver, investigate using this for parallel LED output.

Pros

  • ✅ Officially supported in ESP-IDF 5.x
  • ✅ Designed for parallel data transmission
  • ✅ DMA-based transfers
  • ✅ Stable API going forward

Cons

  • ❌ I80 LCD driver is for display panels with specific protocol (Command/Data signals)
  • ❌ Timing requirements different from WS2812/APA102 LED protocols
  • ❌ May not support continuous streaming like I2S
  • ❌ Likely limited to 16-bit parallel (vs I2S 24-bit)
  • ❌ Significant architectural changes required
  • ❌ Uncertain if clock precision matches LED requirements (nanosecond-level timing)

Verdict

Low Viability: I80 LCD driver is purpose-built for LCD panels, not general parallel GPIO output with arbitrary timing. Would require extensive testing to determine if it can meet LED timing requirements.


Solution 4: Hybrid Approach - RMT + GPIO Expander

Feasibility: Medium
Complexity: 7/10
Maintenance Burden: Low

Approach

Use FastLED's RMT driver (already works with Arduino Core 3.0) combined with external GPIO expander chip for more strips.

Pros

  • ✅ RMT driver fully compatible with Arduino Core 3.0
  • ✅ No ESP-IDF version issues
  • ✅ Clean, maintainable solution
  • ✅ Official peripheral support
  • ✅ Can mix different LED chipsets per strip

Cons

  • ❌ ESP32 RMT has only 8 channels (vs 24 for I2S parallel)
  • ❌ Requires external hardware (GPIO expander IC)
  • ❌ Increased BOM cost
  • ❌ Additional wiring complexity
  • ❌ May not achieve same refresh rates as I2S

Verdict

Viable Alternative: For users who can accept 8 strips or add external hardware. Not a drop-in replacement for I2S parallel mode.


Solution 5: Stay on Arduino Core 2.0.17 (Recommended Short-Term)

Feasibility: High
Complexity: 0/10 (no code changes)
Maintenance Burden: None (until Core 2.x EOL)

Approach

Document Arduino Core 2.0.17 as maximum supported version for I2S users. Update ESP32_I2S_ISSUES.md with clear compatibility matrix.

Pros

  • ✅ Requires no code changes
  • ✅ Works reliably with I2S parallel mode
  • ✅ Well-tested configuration
  • ✅ No new bugs introduced
  • ✅ Clear user guidance

Cons

  • ❌ Misses ESP-IDF 5.x features and improvements
  • ❌ Eventually Core 2.x will be unmaintained
  • ❌ Doesn't solve long-term compatibility
  • ❌ Users on Core 3.x cannot use I2S

Verdict

Best Short-Term Solution: Document this clearly while working on Solution 1 for long-term fix.


Recommended Path Forward

Phase 1: Immediate Actions (Short-Term)

  1. Update Documentation

    • Enhance ESP32_I2S_ISSUES.md with findings from this report
    • Add explicit compatibility matrix for Arduino Core versions
    • Document I2S LCD mode removal in ESP-IDF 5.0+
    • Recommend Arduino Core 2.0.17 for I2S users
  2. Add Arduino Core 3.x Detection Warning

    #if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR >= 3
    #ifdef FASTLED_ESP32_I2S
    #warning "I2S parallel mode has compatibility issues with Arduino Core 3.0+. \
             See ESP32_I2S_ISSUES.md for details. Recommend using RMT driver or \
             downgrading to Arduino Core 2.0.17."
    #endif
    #endif
  3. Create Known Issues List

    • GitHub issue documenting ESP-IDF 5.x I2S incompatibility
    • Tag with "esp32", "i2s", "arduino-core-3", "help-wanted"
    • Link to this investigation report

Phase 2: Research & Prototyping (Medium-Term)

Goal: Determine if Solution 1 (HAL/LL layer) is viable

  1. ESP-IDF Source Code Analysis

    • Clone ESP-IDF 5.1.4 source
    • Examine components/hal/esp32/include/hal/i2s_ll.h
    • Document available LL functions for I2S configuration
    • Check if LCD mode registers still exist and are accessible
  2. Hardware Testing

    • Create minimal test program on ESP-IDF 5.1.4 (no Arduino)
    • Use direct register access to enable LCD mode
    • Configure I2S for parallel output
    • Drive single LED strip to verify hardware functionality
    • Critical Test: Does i2s->conf2.lcd_en = 1 still work?
  3. Conflict Testing

    • Test if direct register manipulation conflicts with new I2S driver
    • Create program that uses both high-level I2S API (for audio) and low-level registers (for LEDs)
    • Document any conflicts or shared state issues
  4. Prototype Implementation

    • Create experimental branch: feature/esp-idf-5-i2s
    • Implement ESP-IDF 5.x code path using HAL/LL or direct registers
    • Test compilation on Arduino Core 3.0, 3.1, 3.2
    • Request community beta testing

Decision Point: If hardware tests show LCD mode registers don't work or conflict with new driver, abandon Solution 1 and recommend permanent migration to RMT.

Phase 3: Implementation (Long-Term)

If Solution 1 is viable:

  1. Full Implementation

    • Complete ESP-IDF 5.x I2S driver code
    • Add comprehensive conditional compilation
    • Maintain backward compatibility with ESP-IDF 4.4
    • Update ESP32-S3 variant for GDMA compatibility
  2. Testing Matrix

    • Test on Arduino Core 2.0.17, 3.0.0, 3.1.x, 3.2.x
    • Test on ESP32, ESP32-S2, ESP32-S3
    • Test with/without WiFi/Bluetooth
    • Test with multiple DMA buffer configurations
    • Verify no conflicts with Arduino I2S examples
  3. Documentation

    • Update platform README
    • Add Arduino Core 3.x setup guide
    • Document limitations and known issues
    • Create migration guide for users on Core 2.x
  4. Release

    • Merge to main branch
    • Release notes highlighting ESP-IDF 5.x support
    • Announce to community for broader testing

If Solution 1 is NOT viable:

  1. Deprecate I2S Driver

    • Add deprecation warnings for FASTLED_ESP32_I2S
    • Update documentation to recommend RMT
    • Keep I2S code for ESP-IDF 4.4 users only
  2. Enhance RMT Driver

    • Optimize RMT performance
    • Add documentation for GPIO expander use
    • Provide migration guide from I2S to RMT

Testing Checklist

If implementing Solution 1, the following tests are required:

Compilation Tests

  • Compiles on Arduino Core 2.0.17 (ESP-IDF 4.4)
  • Compiles on Arduino Core 3.0.0 (ESP-IDF 5.1.4)
  • Compiles on Arduino Core 3.1.x
  • Compiles on Arduino Core 3.2.x
  • Compiles on ESP32 (original)
  • Compiles on ESP32-S2
  • Compiles on ESP32-S3
  • No deprecation warnings (or warnings suppressible)

Functional Tests

  • Single LED strip (WS2812B) works
  • 8 parallel LED strips work
  • 16 parallel LED strips work
  • 24 parallel LED strips work
  • Different LED chipsets (WS2812, APA102, SK6812)
  • Long LED strips (300+ LEDs per strip)
  • High refresh rates (>60 FPS)

Stability Tests

  • Continuous operation for 1 hour
  • Continuous operation for 24 hours
  • With WiFi enabled and active
  • With Bluetooth enabled and active
  • With both WiFi and Bluetooth active
  • Under high interrupt load

DMA Buffer Tests

  • NUM_DMA_BUFFERS = 2 (default)
  • NUM_DMA_BUFFERS = 4
  • NUM_DMA_BUFFERS = 8
  • NUM_DMA_BUFFERS = 16

Conflict Tests

  • No conflicts with Arduino I2S audio examples
  • No conflicts with Arduino I2S microphone examples
  • No conflicts with other DMA users (SPI, UART)

Platform-Specific Tests

  • ESP32 (original) - I2S0 and I2S1
  • ESP32-S2 - I2S0 only
  • ESP32-S3 - I2S0 and I2S1 with GDMA

References

Official Documentation

ESP-IDF Documentation:

Arduino ESP32 Documentation:

GitHub Issues/PRs

ESP-IDF Repository:

Arduino-ESP32 Repository:

FastLED Repository:

  • Current I2S implementation: src/platforms/esp/32/drivers/i2s/
  • ESP32 Platform README: src/platforms/esp/32/README.md

Forum Discussions

ESP32 Forums:

  • "I2S v5.1 upgrade: conflicts" - esp32.com/viewtopic.php?t=35239
    • User reports conflict error when mixing old/new I2S drivers
  • "API 5.x I2S legacy issue" - esp32.com/viewtopic.php?t=35422
    • Legacy API compatibility issues
  • "RMT migration to IDF 5.0" - esp32.com/viewtopic.php?t=37020
    • Community RMT migration experiences
  • "I2S-parallel example: Drive a 64x32 display" - esp32.com/viewtopic.php?t=3188
    • Historical I2S parallel mode usage

Random Nerd Tutorials:

Code Examples

Third-Party Libraries:

ESP-IDF Examples:

Technical References

Register Header Files (ESP-IDF Source):


Appendix

ESP-IDF Version Differences Summary Table

Feature ESP-IDF 4.4 ESP-IDF 5.0+ Impact on FastLED
I2S Driver API Unified driver/i2s.h Mode-specific headers Must adapt includes
LCD Parallel Mode ✅ Supported ❌ Removed from I2S Critical Issue
Register Headers Available ✅ Still available ✅ Can continue using
HAL/LL API Stability N/A ⚠️ Experimental Maintenance burden
gpio_matrix_out() Function Available Renamed ✅ Already patched
periph_ctrl.h Location Public driver/ Private esp_private/ ✅ Already patched
DMA Descriptor (lldesc_t) Available ✅ Still available ✅ Can continue using
Initialization Pattern Single function Multi-step (alloc/init/enable) Must adapt for 5.x
Channel Control Controller-level Independent TX/RX N/A (FastLED TX only)
Register Access Macros Expression-capable Statement-only ⚠️ Needs verification

Testing Platforms Reference

Platform I2S Units DMA Type Arduino Core 2.0.17 Arduino Core 3.0+
ESP32 I2S0, I2S1 Legacy ✅ Tested ⚠️ Needs testing
ESP32-S2 I2S0 only Legacy ✅ Tested ⚠️ Needs testing
ESP32-S3 I2S0, I2S1 GDMA ✅ Tested ⚠️ Needs testing
ESP32-C3 I2S0 only GDMA Limited support ⚠️ Needs testing

Key Quotes from Research

ESP-IDF Documentation on LCD Mode Removal:

"LCD/Camera mode is only supported on I2S0 over a parallel bus. These two modes are not implemented by the I2S driver. Please refer to I80 Interfaced LCD for details."
— ESP-IDF 5.x I2S Documentation

ESP-IDF Documentation on HAL/LL Stability:

"Hardware abstraction API (excluding the driver and xxx_types.h) should be considered an experimental feature... does not adhere to the API name changing restrictions."
— ESP-IDF Hardware Abstraction Guide

FastLED Code Comment on ESP-IDF 5.0 Patch:

"Patches the i2s driver for compatibility with ESP-IDF v5.0. This has only been compile tested. If there are issues then please file a bug."
src/platforms/esp/32/drivers/i2s/i2s_esp32dev.cpp lines 23-25

User Experience on ESP32 Forum:

"CONFLICT! The new i2s driver can't work along with the legacy i2s driver"
— ESP32.com forum thread on I2S v5.1 upgrade

Arduino ESP32 Migration Guide on I2S:

"The I2S driver has been completely redesigned and refactored to use the new ESP-IDF driver."
— Arduino ESP32 Core 3.0 Migration Guide


Document Version: 1.0
Research Date: 2025-01-11
Author: FastLED Investigation Agent (Claude)
Status: Initial Research Complete


Next Steps for Maintainers

  1. Review this report and decide on implementation priority for Solution 1
  2. Assign developer to Phase 2 (Research & Prototyping)
  3. Create GitHub issue linking to this report for community visibility
  4. Set up testing environment with Arduino Core 3.x and ESP-IDF 5.1.4
  5. Establish success criteria for hardware tests before committing to full implementation

If you have questions or need clarification on any findings, please reference specific section numbers in this report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions