Addressable LEDs with RMT
A WS2812 strip is one data wire and a protocol measured in hundreds of nanoseconds - far too fast to bit-bang reliably while Wi-Fi is running. The RMT peripheral does the timing in hardware, which is why it exists.
Two ceilings, both invisible
The wiring that stops the flickering
Four things, and all four are cheap:
- A separate 5 V supply for the strip, with its ground joined to the board's ground. Not optional past about 20 pixels.
- A 300–500 Ω resistor in series with the data line, at the board.
- A 1000 µF capacitor across the strip's 5 V and ground, at the strip.
- A level shifter, or a first pixel you never use.
What RMT actually is
A peripheral for sending and receiving precisely timed pulse trains. Addressable LEDs are its most common use, but the same hardware drives infrared remotes, one-wire sensors and anything else where the timing is tighter than software can guarantee. It runs in the background with DMA, so a 300-pixel frame does not block your loop for 9 ms.
The code
FastLED and NeoPixelBus both use RMT on the ESP32, so you get hardware timing without asking. The two lines that matter are the brightness cap and the power arithmetic behind it.
#include <FastLED.h>
#define PIN 18
#define NUM 60
CRGB leds[NUM];
void setup() {
FastLED.addLeds<WS2812B, PIN, GRB>(leds, NUM);
FastLED.setBrightness(40); // out of 255
FastLED.setMaxPowerInVoltsAndMilliamps(5, 500); // honour USB
}
void loop() {
static uint8_t hue = 0;
fill_rainbow(leds, NUM, hue++, 4);
FastLED.show(); // RMT sends it in the background
delay(20);
}setBrightness is not cosmetic on a strip this size - it is the difference between drawing 1.8 A and drawing 700 mA. Set it before you wonder why the board resets.
MicroPython has NeoPixel built in and it uses the same hardware underneath. write() blocks for the length of the frame, so keep the strip short or move it to its own task.
from machine import Pin
import neopixel, time
NUM = 60
np = neopixel.NeoPixel(Pin(18), NUM)
while True:
for i in range(NUM):
np[i] = (8, 0, 12) # keep values low - this is current
np.write()
time.sleep_ms(20)The ESP32 NeoPixel driver defaults to 800 kHz, which is right for WS2812B. The older WS2811 wants 400 kHz and gives you noise at the wrong rate.
When it does not work
The strip is pulling more than the supply can give. Sixty pixels at full white is nearly 3 A. Power the strip separately, join the grounds, and cap the brightness.
A 3.3 V data pin driving a 5 V strip. It usually works, and the first pixel is where usually runs out. Use a level shifter, or sacrifice pixel zero by never displaying anything on it.
Two causes, both wiring. Put a 300 to 500 Ω resistor in series with the data line at the board end, and a 1000 µF capacitor across the strip's supply at the strip end.
WS2812B is GRB, not RGB, and clones vary. Change the colour order in the constructor rather than swapping the values in your code.
Time to leave the chip. The next chapter is about how two devices agree what a wire means, on any board at all.
Digital in and digital out →Edit this page — content/esp32/addressable-leds-with-rmt.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.