From 37f484e667c6ab7242b8d7ca601e46f2c4743f44 Mon Sep 17 00:00:00 2001 From: darkgrue Date: Fri, 19 Jun 2026 12:16:33 -0500 Subject: [PATCH] fix(ESP32 RMT): size RMT buffer per chip and use DMA where available 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 #921 Co-Authored-By: Claude Sonnet 4.6 --- .../methods/ESP/ESP32/NeoEsp32RmtXMethod.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/internal/methods/ESP/ESP32/NeoEsp32RmtXMethod.h b/src/internal/methods/ESP/ESP32/NeoEsp32RmtXMethod.h index 326c5dfa..f8fee806 100644 --- a/src/internal/methods/ESP/ESP32/NeoEsp32RmtXMethod.h +++ b/src/internal/methods/ESP/ESP32/NeoEsp32RmtXMethod.h @@ -57,9 +57,6 @@ struct rmt_led_strip_encoder_t rmt_symbol_word_t reset_code; }; -#define NEOPIXELBUS_RMT_INT_FLAGS (ESP_INTR_FLAG_LOWMED) - - template class NeoEsp32RmtMethodBase { public: @@ -101,11 +98,19 @@ template class N rmt_tx_channel_config_t config = {}; config.clk_src = RMT_CLK_SRC_DEFAULT; config.gpio_num = static_cast(_pin); - config.mem_block_symbols = 192; // memory block size, 64 * 4 = 256 Bytes +#if SOC_RMT_SUPPORT_DMA + config.mem_block_symbols = 512; // DMA decouples the buffer from RMT memory, so this is just a sane DMA buffer size +#else + config.mem_block_symbols = SOC_RMT_TX_CANDIDATES_PER_GROUP * SOC_RMT_MEM_WORDS_PER_CHANNEL; // max symbols a single TX channel can borrow from all TX-capable channels in its group: 512 on original ESP32 (8x64), 96 on ESP32-C3 (2x48). Requesting more than this fails rmt_new_tx_channel with "no free tx channels" since there is no contiguous free memory to satisfy it. A bigger half-buffer gives the RMT ISR more slack against BLE-induced preemption before the WS2811/WS2812x 300 µs reset threshold is hit, so we ask for the most each chip can give. +#endif config.resolution_hz = T_SPEED::RmtTicksPerSecond; // 1 MHz tick resolution, i.e., 1 tick = 1 µs config.trans_queue_depth = 4; // set the number of transactions that can pend in the background config.flags.invert_out = T_INVERTED::Inverted; // do not invert output signal - config.flags.with_dma = false; // do not need DMA backend +#if SOC_RMT_SUPPORT_DMA + config.flags.with_dma = true; // chips with small per-channel RMT memory (e.g. ESP32-S3: 48 words/channel) can't fit a large static block; DMA decouples the buffer from RMT memory and from ISR refill timing entirely +#else + config.flags.with_dma = false; // chips without DMA support (original ESP32, ESP32-C3) use the static block sized above +#endif ret += rmt_new_tx_channel(&config, &_channel); led_strip_encoder_config_t encoder_config = {};