Describe the bug
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: pixels 0–7 produce one ghost each; pixels 8–15 produce two; pixels 16–23 produce three; pixels 24+ produce nothing. Dense patterns mask the problem; all-off works correctly.
GetPixelColor immediately before Show() confirms the software buffer is always correct. The defect is introduced during RMT transmission.
The root cause is the hardcoded mem_block_symbols = 192 in NeoEsp32RmtXMethod.h. With 96 symbols per ping-pong half, WS2811/WS2812x transmits one half in approximately 120 µs. NimBLE and WiFi event handlers run at higher interrupt priority than the RMT refill ISR and routinely preempt for longer than 120 µs, leaving the data line idle past the LED latch threshold (~300 µs). The LEDs latch the partial frame, producing a false frame boundary at every 8-pixel interval.
Increasing mem_block_symbols to 512 raises the half-buffer transmission time to ~320 µs - exceeding the latch threshold - which eliminates the false latches.
To Reproduce
Connect a WS2811 or WS2812x strip to an ESP32.
Initialize a NimBLE (or Arduino BLE) server and establish a BLE connection from a client device.
In the main loop, clear the strip to black and set a single pixel to red, then call Show(). Repeat with a visible delay.
Observe that the addressed pixel lights correctly but an additional pixel 8 positions downstream also lights red. Pixels addressed at index 8+ produce proportionally more ghost pixels downstream.
Expected behavior
Only the explicitly addressed pixel should light. All other pixels should remain off.
Development environment
OS: Windows 11
Build Environment: PlatformIO with pioarduino platform (ESP-IDF v5.x / Arduino framework)
Board target: ESP32-WROOM-32 (esp32dev)
Library version: NeoPixelBus CORE3 branch (GitHub clone, Makuna/NeoPixelBus#CORE3)
Minimal Sketch that reproduced the problem:
#include <NeoPixelBus.h>
#include <NimBLEDevice.h>
// Adjust pin and count for your hardware
const uint8_t PIN_DATA = 33;
const uint16_t NUM_LEDS = 40;
NeoPixelBus<NeoGrbFeature, NeoEsp32RmtXWs2811Method> strip(NUM_LEDS, PIN_DATA);
void setup() {
Serial.begin(115200);
// Start BLE — connection is required to trigger the bug
NimBLEDevice::init("test");
NimBLEServer *server = NimBLEDevice::createServer();
NimBLEAdvertising *adv = NimBLEDevice::getAdvertising();
adv->start();
Serial.println("Advertising — connect a BLE client, then observe LEDs");
strip.Begin();
strip.ClearTo(RgbColor(0, 0, 0));
strip.Show();
}
void loop() {
// Light only pixel 0 red; all others black
strip.ClearTo(RgbColor(0, 0, 0));
strip.SetPixelColor(0, RgbColor(255, 0, 0));
strip.Show();
delay(500);
}
Without a BLE client connected: only pixel 0 lights.
With a BLE client connected: pixel 0 and pixel 8 both light red.
Additional context
Two related issues in NeoEsp32RmtXMethod.h:
NEOPIXELBUS_RMT_INT_FLAGS is defined but never used. Line 60 defines #define NEOPIXELBUS_RMT_INT_FLAGS (ESP_INTR_FLAG_LOWMED) but this macro is not referenced anywhere in Initialize() or elsewhere in the CORE3 driver. If this flag could be applied to elevate the RMT ISR above BLE/WiFi priority, the buffer-size workaround would be unnecessary.
Stale comment. config.mem_block_symbols = 192; // memory block size, 64 * 4 = 256 Bytes - the comment describes a single 64-symbol block (256 bytes). 192 symbols × 4 bytes = 768 bytes; the comment was not updated when the value changed from 64.
Suggested fix: make mem_block_symbols user-overridable so BLE/WiFi projects can increase the ISR deadline without patching the library:
#ifndef NEOPIXELBUS_RMT_MEM_BLOCK_SYMBOLS
#define NEOPIXELBUS_RMT_MEM_BLOCK_SYMBOLS 192
#endif
config.mem_block_symbols = NEOPIXELBUS_RMT_MEM_BLOCK_SYMBOLS;
This preserves backward compatibility. Projects with BLE or WiFi active can add -D NEOPIXELBUS_RMT_MEM_BLOCK_SYMBOLS=512 to their build flags. 512 is the maximum on ESP32 original (8 channels × 64 symbols); 256 or 384 are reasonable defaults that fit all ESP32 variants and provide meaningful improvement over 192.
Describe the bug
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: pixels 0–7 produce one ghost each; pixels 8–15 produce two; pixels 16–23 produce three; pixels 24+ produce nothing. Dense patterns mask the problem; all-off works correctly.
GetPixelColorimmediately beforeShow()confirms the software buffer is always correct. The defect is introduced during RMT transmission.The root cause is the hardcoded
mem_block_symbols = 192inNeoEsp32RmtXMethod.h. With 96 symbols per ping-pong half, WS2811/WS2812x transmits one half in approximately 120 µs. NimBLE and WiFi event handlers run at higher interrupt priority than the RMT refill ISR and routinely preempt for longer than 120 µs, leaving the data line idle past the LED latch threshold (~300 µs). The LEDs latch the partial frame, producing a false frame boundary at every 8-pixel interval.Increasing
mem_block_symbolsto 512 raises the half-buffer transmission time to ~320 µs - exceeding the latch threshold - which eliminates the false latches.To Reproduce
Connect a WS2811 or WS2812x strip to an ESP32.
Initialize a NimBLE (or Arduino BLE) server and establish a BLE connection from a client device.
In the main loop, clear the strip to black and set a single pixel to red, then call Show(). Repeat with a visible delay.
Observe that the addressed pixel lights correctly but an additional pixel 8 positions downstream also lights red. Pixels addressed at index 8+ produce proportionally more ghost pixels downstream.
Expected behavior
Only the explicitly addressed pixel should light. All other pixels should remain off.
Development environment
OS: Windows 11
Build Environment: PlatformIO with pioarduino platform (ESP-IDF v5.x / Arduino framework)
Board target: ESP32-WROOM-32 (esp32dev)
Library version: NeoPixelBus CORE3 branch (GitHub clone, Makuna/NeoPixelBus#CORE3)
Minimal Sketch that reproduced the problem:
Without a BLE client connected: only pixel 0 lights.
With a BLE client connected: pixel 0 and pixel 8 both light red.
Additional context
Two related issues in
NeoEsp32RmtXMethod.h:NEOPIXELBUS_RMT_INT_FLAGSis defined but never used. Line 60 defines#define NEOPIXELBUS_RMT_INT_FLAGS (ESP_INTR_FLAG_LOWMED)but this macro is not referenced anywhere inInitialize()or elsewhere in the CORE3 driver. If this flag could be applied to elevate the RMT ISR above BLE/WiFi priority, the buffer-size workaround would be unnecessary.Stale comment.
config.mem_block_symbols = 192; // memory block size, 64 * 4 = 256 Bytes- the comment describes a single 64-symbol block (256 bytes). 192 symbols × 4 bytes = 768 bytes; the comment was not updated when the value changed from 64.Suggested fix: make
mem_block_symbolsuser-overridable so BLE/WiFi projects can increase the ISR deadline without patching the library:This preserves backward compatibility. Projects with BLE or WiFi active can add
-D NEOPIXELBUS_RMT_MEM_BLOCK_SYMBOLS=512to their build flags. 512 is the maximum on ESP32 original (8 channels × 64 symbols); 256 or 384 are reasonable defaults that fit all ESP32 variants and provide meaningful improvement over 192.