Bare Metal: WS2812
- 3 minutes read - 534 wordsThis one works!
Virtual WS2812s
I’d gone cough many years and never heard of 1-Wire and, suddenly, it’s everywhere.
Addressable LEDs are hugely popular in tinkerer circles. Addressable LEDs come in myriad forms (wheels, matrices) but commonly they’re sold as long strips. The part number is WS2812 and they use 1-Wire too. Each, often multi-color (RGB) LED (often known as a pixel), is combined with an IC that enables the “addressable” behavior.
In essence, a message stream is sent alone the strip’s 1-Wire network each “pixel”, in turn, takes a message (encoded RGB color) from the stream, colors its LEDs to match and passes the rest (!) of the stream on to the next pixel in the strip.
The result is that the strip of pixels can be programmed easily to display complex patterns. The “addressability” is accurate since the stream originator deterministically controls which pixel gets which RGB encoding but it results entirely from each pixel’s position on the strip.
Each pixel is colored by a 24-bit RGB: GGGGGGGG
RRRRRRRR
BBBBBBBB
In Rust, it’s convenient to represent these using RGB8==RGB<u8>
and to pack these:
let color: u32 = ((rgb.g as u32) << 16) | ((rgb.r as u32) << 8) | rgb.b as u32;
ESP32’s include an RMT peripheral which permits fine control over signals and, somewhat similar to the (greater) ability of PIO on the RP2XX0, it can be used to implement protocols such as 1-Wire.
The esp-hal
crate (for the ESP32-C3) supports rmt
and includes rmt::PulseCode
which allows the creation of the correct signals required by 1-Wire where a 1
has a longer high signal than low signal and a 0
has a shorter high signal than low signal.
So, in rust, we can iterate over 24-bits of color
’s 32 (u32
) in reverse (most significant bit first!) to generate the appropriate pules:
for (i, pulse) in pulses.iter_mut().enumerate().take(24) {
let bit = (color >> (23 - i)) & 1;
if bit == 1 {
*pulse = PulseCode::new(Level::High, T1H, Level::Low, T1L);
} else {
*pulse = PulseCode::new(Level::High, T0H, Level::Low, T0L);
}
}
And then simply aggregate 24 pulses (1 pixel) for the number of pixels in our chain. The first 24 pulses will be consumed by the first pixel in the chain, the next 24 pulses will be consumed by the 2nd etc.
Onboard WS2812
The ESP32-C3-DevKit-RUST
includes an on-board Addressable LED (WS2812) bound to GPIO2 (see board diagram).
This provides a way in which I can use a physical WS2812 to test code.
This has a trivial diagram.json
:
{
"version": 1,
"author": "Daz",
"editor": "wokwi",
"parts": [
{
"type": "board-esp32-c3-rust-1",
"id": "esp",
"top": -211.5,
"left": -3.78,
"rotate": 0.0,
"attrs": { "builder": "rust-nostd-esp" }
}
],
"connections": [],
"serialMonitor": {
"display": "terminal",
"convertEol": true
}
}
Rust compilations on Wokwi (currently) fail. A neat solution to this problem is to build locally (targeting the appropriate device) and then upload the binary to a vanilla Rust project.
It’s difficult to capture the screenshot but even with an empty main.rs
file on Wokwi, from anywhere on that editor page, F1
will bring up a submenu from which you can select “Upload Firmware and Start Simulation`, select this option and select the binary and, the simulation should begin.