Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
ws2812-rmt
A minimal, no_std WS2812/NeoPixel driver over esp-hal's RMT peripheral —
no third-party smart-leds/RMT abstraction in between.
Why this exists
Extracted from real-hardware debugging on an ESP32-C6 after
esp-hal-smartled2 proved
unable to reliably update a WS2812 pixel to a changing value on repeated
writes — a single steady color always rendered correctly, but blink/breathe
patterns (i.e. anything other than "set it once and leave it") reliably
corrupted: either sticking on a stale frame, or bleeding a previously-sent
channel's value into later frames (a once-used blue value would visibly
tint every later color — red became pink, green became cyan).
Root cause: that crate — like most WS2812/RMT drivers, including an earlier
version of this one — relies on the RMT channel's idle-low state between
separate transmissions to serve as the WS2812's required ≥50µs reset/latch
pulse, rather than encoding that reset explicitly in the transmitted buffer
the way Espressif's own reference encoder
(esp-idf/examples/peripherals/rmt/led_strip_simple_encoder)
does. This crate always appends an explicit reset pulse, and transmits in
blocking mode (Channel::transmit(..).wait(), which busy-polls hardware
status registers — no interrupt/async-waker path at all) so a bug in an
async completion path can't be a variable either.
RMT clock tick period is chip- and configuration-specific — verify it
Every Timing field is a count of RMT clock ticks, not nanoseconds, and
Ws2812::new takes an explicit clk_divider alongside it. The resulting
tick period depends on your chip's RMT clock source, which varies by chip
family (this crate exists partly because that assumption was wrong once
already, on an ESP32-C6) and on Rmt::new's requested frequency combined
with that clk_divider.
Two presets are confirmed on real hardware so far:
| Constant | Chip | Rmt::new frequency |
clk_divider |
Datasheet variant |
|---|---|---|---|---|
Timing::WS2812_AT_12_5NS_TICK_ESP32C6 |
ESP32-C6 | 80MHz | 2 |
WS2812 (6-pin) |
Timing::WS2812B_AT_12_5NS_TICK_ESP32C3 |
ESP32-C3 | 80MHz | 1 |
WS2812B (4-pin) |
Both land on the same 12.5ns tick period, reached differently because the two chips default their RMT clock source differently (a fixed 80MHz PLL on C6, the APB clock — itself fixed at 80MHz on C3 — on C3). If you're on a different chip or clock configuration, verify on real hardware — a logic analyzer is the reliable way. Empirically, a wrong tick period here doesn't fail loudly; it renders as anything from "stuck on one color" to "completely dark".
Usage
use ;
let rmt = new?;
let mut led = buffer_len }>new?;
led.write?;
For more than one pixel, size the buffer for however many you have:
const PIXELS: usize = 25;
let mut leds = buffer_len }>new?;
leds.write?;
Chip support
Gated behind Cargo features matching esp-hal's own chip features — enable
the one matching your target:
esp32, esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2,
esp32s2, esp32s3.
CI only exercises esp32c3/esp32c6 (RISC-V targets installable via plain
rustup, no Xtensa toolchain needed) — the others should work by the same
esp-hal feature-forwarding mechanism, but haven't been build-tested here.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.