Signal timing¶
How the driver turns pixels into pulses: ticks, the time a frame takes, the memory it needs and the reset gap.
Ticks¶
WS2812Strip runs its RMT channel at 10 MHz, so every duration it sends is a whole number of 100 ns ticks. The
four timing arguments (t0h, t1h, t0l, t1l) are those tick counts, used as they are: the driver does not
convert from nanoseconds.
| Pulse | Driver value | WS2812B window |
|---|---|---|
0 bit high, t0h |
4: 400 ns | 250-550 ns |
0 bit low, t0l |
9: 900 ns | 700-1000 ns |
1 bit high, t1h |
8: 800 ns | 650-950 ns |
1 bit low, t1l |
5: 500 ns | 300-600 ns |
Any value inside the window works; the LED samples the line roughly in the middle of the bit, so the difference between a short and a long high pulse is what matters. The Kconfig defaults are the values in the table (4, 8, 9, 5), so a strip created without timing arguments is right for a WS2812B.
ws2812::RmtTx::TransmitWs2812(), the lower-level byte path, has its own fixed timing for 10 MHz: 400 / 900 ns
for a 0 and 800 / 500 ns for a 1, the same as the table.
Frame time¶
Each LED takes 24 bits (32 for RGBW). With the timing above every bit lasts 1.3 µs:
| LEDs | RGB frame | Highest refresh rate |
|---|---|---|
| 8 | 0.25 ms | about 1800 Hz |
| 60 | 1.9 ms | about 460 Hz |
| 144 | 4.5 ms | about 210 Hz |
| 300 | 9.4 ms | about 104 Hz |
An RGBW frame is a third longer (32 bits instead of 24).
The refresh rate allows for the frame and the 300 µs reset Show() sends after it. These numbers are derived from the
bit time, not measured; Show() also spends CPU time building the symbols before the transfer starts.
Show() blocks the calling task until the last symbol is out (rmt_tx_wait_all_done() with no timeout). Call it
from a task that can afford the wait, never from an interrupt.
The ws2812::RmtTx transmit calls take the same wait as rmt_tx_wait_all_done(): timeout_ms in milliseconds as
an int32_t, with RmtTx::kWaitForever (-1, the default) to wait until the frame is out. The value does not depend
on the FreeRTOS tick rate.
Memory¶
| Buffer | Bytes per LED |
|---|---|
Pixels (uint32_t) |
4 |
| RMT symbols, RGB (24 × 4 bytes) | 96 |
| RMT symbols, RGBW (32 × 4 bytes) | 128 |
A 60-LED RGB strip needs 6004 bytes: the symbol buffer has one more symbol for the reset.
Both are std::vectors allocated when the strip is created or resized. The RMT channel itself has one memory
block of hardware memory (64 symbols on the ESP32 and ESP32-S2, 48 on the S3, C3, C6 and H2) and no DMA; ESP-IDF
refills it from an interrupt while a frame is sent. A refill delayed by a
long critical section stretches a low period, which the LEDs read as a reset or a wrong bit. If you see glitches
under heavy interrupt load, keep strips short or send frames from a high-priority task.
The reset gap¶
A WS2812B shows the new colours once its data line has stayed low for the reset time: more than 50 µs in the
WS2812B datasheet, at least 280 µs for the WS2812B-V5 (80 µs for the SK6812). WS2812Strip::Show() ends every
frame with a low symbol of WS2812Strip::kResetTimeUs (300 µs), which covers all of them, and returns only after
it has been sent. Show() calls back to back are therefore safe.
The C interface's encoder (led_strip_encoder.c) appends the same 300 µs reset. ws2812::RmtTx::TransmitWs2812()
sends only the bytes you give it; leave 300 µs before the next call.
Colour order¶
Pixels are packed 0xRRGGBB: SetPixel(i, 0xFF0000) is red. A WS2812B reads green, red, blue
(datasheet "Composition of 24bit data": G7 ... G0, R7 ... R0, B7 ... B0), so Show() sends the green byte, then
red, then blue, each most significant bit first. ColorWheel() and the effects use the same packing.
For an RGBW strip pack 0xWWRRGGBB; Show() sends green, red, blue, then white, the order of the SK6812RGBW.