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
+24 -18

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 i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18);
let display = Some(DisplayParts { i2c }); let display = Some(DisplayParts { i2c });
// WS2812B RGB LED on GPIO38 via RMT channel 0. // WS2812B RGB LED on GPIO38 via RMT channel 0 (blocking, ~31µs per update)
// Wrap in ManuallyDrop so the Rmt peripheral isn't disabled when let rmt = Rmt::new(p.RMT, Rate::from_mhz(80)).expect("RMT init");
// the struct is dropped after we take channel0. let led_channel = rmt
let rmt = core::mem::ManuallyDrop::new( .channel0
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
.configure_tx( .configure_tx(
p.GPIO38, p.GPIO38,
TxChannelConfig::default() TxChannelConfig::default()
+20 -6
View File
@@ -2,6 +2,7 @@
//! //!
//! Encodes RGB values as GRB PulseCodes and transmits via RMT. //! Encodes RGB values as GRB PulseCodes and transmits via RMT.
//! At 80 MHz with divider=1, each tick is 12.5 ns. //! 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::gpio::Level;
use esp_hal::rmt::{Channel, PulseCode, Tx}; 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). /// WS2812 1-bit: High 800ns (64 ticks), Low 450ns (36 ticks).
const BIT_1: PulseCode = PulseCode::new(Level::High, 64, Level::Low, 36); 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 { pub struct Ws2812 {
channel: Channel<'static, esp_hal::Async, Tx>, channel: Option<Channel<'static, esp_hal::Blocking, Tx>>,
} }
impl Ws2812 { impl Ws2812 {
pub fn new(channel: Channel<'static, esp_hal::Async, Tx>) -> Self { pub fn new(channel: Channel<'static, esp_hal::Blocking, Tx>) -> Self {
Self { channel } Self {
channel: Some(channel),
}
} }
/// Encode 3 bytes (GRB order) + end marker into PulseCodes. /// Encode 3 bytes (GRB order) + end marker into PulseCodes.
@@ -35,7 +38,6 @@ impl Ws2812 {
idx += 1; idx += 1;
} }
} }
// data[24] is already end_marker
data data
} }
} }
@@ -43,8 +45,20 @@ impl Ws2812 {
impl RgbLed for Ws2812 { impl RgbLed for Ws2812 {
async fn set_rgb(&mut self, r: u8, g: u8, b: u8) { async fn set_rgb(&mut self, r: u8, g: u8, b: u8) {
let data = Self::encode(r, g, b); let data = Self::encode(r, g, b);
if let Err(e) = self.channel.transmit(&data).await { 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); defmt::warn!("WS2812 transmit error: {}", e);
// Channel consumed on transmit error — LED is lost
}
}
} }
} }
} }