fix: switch WS2812 driver to blocking RMT transmit

Async RMT had peripheral lifetime issues causing silent failures.
Blocking transmit is ~31µs per update (negligible) and avoids the
async complexity. Also removes the ManuallyDrop/unsafe workaround.
This commit is contained in:
swaits committed 2026-04-07 11:51:18 -06:00
1 parent d67c0699c9
commit c78e2ceaa9
2 files changed
+25 -19

No files matched your search

+4 -12
View File
@@ -77,18 +77,10 @@ impl LoRaBoard for Board {
let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18);
let display = Some(DisplayParts { i2c });
// WS2812B RGB LED on GPIO38 via RMT channel 0.
// Wrap in ManuallyDrop so the Rmt peripheral isn't disabled when
// the struct is dropped after we take channel0.
let rmt = core::mem::ManuallyDrop::new(
Rmt::new(p.RMT, Rate::from_mhz(80))
.expect("RMT init")
.into_async(),
);
// SAFETY: we're reading channel0 out of the ManuallyDrop wrapper.
// The Rmt won't be dropped, so the peripheral stays enabled.
let channel0 = unsafe { core::ptr::read(&raw const rmt.channel0) };
let led_channel = channel0
// WS2812B RGB LED on GPIO38 via RMT channel 0 (blocking, ~31µs per update)
let rmt = Rmt::new(p.RMT, Rate::from_mhz(80)).expect("RMT init");
let led_channel = rmt
.channel0
.configure_tx(
p.GPIO38,
TxChannelConfig::default()
+21 -7
View File
@@ -2,6 +2,7 @@
//!
//! Encodes RGB values as GRB PulseCodes and transmits via RMT.
//! At 80 MHz with divider=1, each tick is 12.5 ns.
//! Uses blocking transmit (~31µs per LED update).
use esp_hal::gpio::Level;
use esp_hal::rmt::{Channel, PulseCode, Tx};
@@ -14,14 +15,16 @@ const BIT_0: PulseCode = PulseCode::new(Level::High, 32, Level::Low, 68);
/// WS2812 1-bit: High 800ns (64 ticks), Low 450ns (36 ticks).
const BIT_1: PulseCode = PulseCode::new(Level::High, 64, Level::Low, 36);
/// WS2812B single-LED driver over RMT.
/// WS2812B single-LED driver over RMT (blocking).
pub struct Ws2812 {
channel: Channel<'static, esp_hal::Async, Tx>,
channel: Option<Channel<'static, esp_hal::Blocking, Tx>>,
}
impl Ws2812 {
pub fn new(channel: Channel<'static, esp_hal::Async, Tx>) -> Self {
Self { channel }
pub fn new(channel: Channel<'static, esp_hal::Blocking, Tx>) -> Self {
Self {
channel: Some(channel),
}
}
/// Encode 3 bytes (GRB order) + end marker into PulseCodes.
@@ -35,7 +38,6 @@ impl Ws2812 {
idx += 1;
}
}
// data[24] is already end_marker
data
}
}
@@ -43,8 +45,20 @@ impl Ws2812 {
impl RgbLed for Ws2812 {
async fn set_rgb(&mut self, r: u8, g: u8, b: u8) {
let data = Self::encode(r, g, b);
if let Err(e) = self.channel.transmit(&data).await {
defmt::warn!("WS2812 transmit error: {}", e);
if let Some(ch) = self.channel.take() {
match ch.transmit(&data) {
Ok(tx) => match tx.wait() {
Ok(ch) => self.channel = Some(ch),
Err((e, ch)) => {
defmt::warn!("WS2812 wait error: {}", e);
self.channel = Some(ch);
}
},
Err(e) => {
defmt::warn!("WS2812 transmit error: {}", e);
// Channel consumed on transmit error — LED is lost
}
}
}
}
}