Skip to content

fix(ESP32 RMT): size RMT memory block per chip, use DMA where available (fixes BLE/WiFi ghost pixels)#923

Open
darkgrue wants to merge 1 commit into
Makuna:CORE3from
darkgrue:issue-921-rmt-x-dma-mem-block
Open

fix(ESP32 RMT): size RMT memory block per chip, use DMA where available (fixes BLE/WiFi ghost pixels)#923
darkgrue wants to merge 1 commit into
Makuna:CORE3from
darkgrue:issue-921-rmt-x-dma-mem-block

Conversation

@darkgrue

Copy link
Copy Markdown

Problem

When a BLE or WiFi connection is active on ESP32, sparse LED patterns produce ghost pixels at index N+8 for every addressed pixel N. The defect scales with strip position and is introduced during RMT transmission, not in the software color buffer.

Root cause: config.mem_block_symbols = 192 in NeoEsp32RmtXMethod.h gives the RMT ping-pong half-buffer only ~96 symbols, ~120 µs at WS2812x 800 Kbps. NimBLE and WiFi event handlers run at higher interrupt priority than the RMT refill ISR and routinely preempt for longer, leaving the data line idle past the ~300 µs LED latch threshold. The LEDs latch the partial frame, producing a false frame boundary at every 8-pixel interval.

Fixes #921.

Note on PR #922

PR #922 ("fixes #921") modified src/internal/methods/NeoEsp32RmtMethod.h on master. That file is the legacy ESP-IDF v3/v4 RMT driver and does not contain the code described in #921 at all — the reporter built against the CORE3 branch's NeoEsp32RmtXMethod.h (the ESP-IDF v5 rmt_tx_channel_config_t driver), which only exists on this branch. #922 doesn't address this issue; this PR does.

Solution

Rather than requiring a user-set build flag, size the RMT buffer automatically per chip at compile time:

  • Chips with DMA-capable RMT (ESP32-S3, ESP32-P4, gated on SOC_RMT_SUPPORT_DMA): set flags.with_dma = true and request a generous 512-symbol DMA buffer. DMA decouples the buffer from RMT channel memory and from ISR refill timing entirely, so preemption no longer matters.
  • Chips without DMA support (ESP32, S2, C3, C6, H2, C5): request the maximum mem_block_symbols a single TX channel can borrow from all TX-capable channels in its group (SOC_RMT_TX_CANDIDATES_PER_GROUP * SOC_RMT_MEM_WORDS_PER_CHANNEL) — 512 on original ESP32 (8×64), 256 on S2 (4×64), 96 on C3/C6/H2/C5 (2×48). This gives the RMT ISR the most slack each chip's hardware can provide.

Also removes NEOPIXELBUS_RMT_INT_FLAGS, a leftover macro from the legacy IDF v3/v4 driver (still used in Core_2_x/NeoEsp32RmtMethod.h) that was copied into this file but is unused by the IDF v5 channel API — called out as dead/confusing code in the issue.

Testing

Verified by real pio run builds (not just syntax checks) against the actual ESP-IDF/Arduino headers for every distinct code path this change introduces:

Target Path exercised Result
esp32dev non-DMA, 512 symbols (8×64) SUCCESS
esp32-s2-saola-1 non-DMA, 256 symbols (4×64) SUCCESS
esp32-s3-devkitc-1 DMA, 512 symbols SUCCESS
esp32-c3-devkitm-1 non-DMA, 96 symbols (2×48), RISC-V SUCCESS
esp32-c6-devkitc-1 non-DMA, 96 symbols (2×48), RISC-V SUCCESS

Hardware verification of the ghost-pixel fix itself (BLE connected, single pixel set) still requires physical hardware, per the original issue repro.

config.mem_block_symbols = 192 in NeoEsp32RmtXMethod.h gives the RMT
ping-pong half-buffer only ~96 symbols (~120us at WS2812x 800Kbps).
NimBLE and WiFi event handlers run at higher interrupt priority than
the RMT refill ISR and routinely preempt for longer, leaving the data
line idle past the ~300us LED latch threshold and producing ghost
pixels at 8-pixel intervals.

On chips with DMA-capable RMT (ESP32-S3, ESP32-P4), enable
flags.with_dma and request a generous DMA buffer; DMA decouples the
buffer from RMT channel memory and from ISR refill timing entirely.

On chips without DMA support (ESP32, S2, C3, C6, H2, C5), request the
maximum mem_block_symbols a single TX channel can borrow from its
group (SOC_RMT_TX_CANDIDATES_PER_GROUP * SOC_RMT_MEM_WORDS_PER_CHANNEL),
giving the ISR the most slack each chip can provide.

Also removes NEOPIXELBUS_RMT_INT_FLAGS, a leftover macro from the
legacy IDF v3/v4 driver that is unused by this IDF v5 channel API.

Fixes Makuna#921

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant