Skip to main content

oxstack_cardputer/
led.rs

1//! The WS2812-class RGB LED on GPIO21, driven by an RMT channel. Get it
2//! from [`crate::rmt::RmtResources::init`].
3//!
4//! **Powered through the LCD backlight pin:** it only works while the
5//! backlight is fully on. See [`crate::backlight`].
6
7use esp_hal::Async;
8use esp_hal::gpio::Level;
9use esp_hal::peripherals::GPIO21;
10use esp_hal::rmt::{Channel, ConfigError, Error, PulseCode, Tx, TxChannelConfig, TxChannelCreator};
11
12/// RMT clock this driver's timings assume (`Rmt::new(rmt, 80 MHz)`), in MHz.
13pub const RMT_MHZ: u32 = 80;
14
15// WS2812 bit timings in 12.5 ns ticks (datasheet: T0H 0.4 µs, T0L 0.85 µs,
16// T1H 0.8 µs, T1L 0.45 µs; ±150 ns tolerance).
17const T0H: u16 = 32;
18const T0L: u16 = 68;
19const T1H: u16 = 64;
20const T1L: u16 = 36;
21
22/// The RGB LED.
23pub struct RgbLed<'d> {
24    ch: Channel<'d, Async, Tx>,
25}
26
27impl<'d> RgbLed<'d> {
28    /// Configures an RMT TX channel for the LED. The `Rmt` the channel
29    /// comes from must run at [`RMT_MHZ`].
30    pub fn new(
31        channel: impl TxChannelCreator<'d, Async>,
32        pin: GPIO21<'d>,
33    ) -> Result<Self, ConfigError> {
34        let config = TxChannelConfig::default()
35            .with_clk_divider(1)
36            .with_idle_output(true)
37            .with_idle_output_level(Level::Low);
38        Ok(Self {
39            ch: channel.configure_tx(&config)?.with_pin(pin),
40        })
41    }
42
43    /// Sets the colour (8-bit channels). The LED is very bright; values of
44    /// 16–64 are plenty indoors.
45    pub async fn set(&mut self, red: u8, green: u8, blue: u8) -> Result<(), Error> {
46        let mut codes = [PulseCode::end_marker(); 25];
47        // WS2812 expects GRB, most significant bit first.
48        let grb = u32::from_be_bytes([0, green, red, blue]);
49        for (i, code) in codes[..24].iter_mut().enumerate() {
50            *code = if grb >> (23 - i) & 1 == 1 {
51                PulseCode::new(Level::High, T1H, Level::Low, T1L)
52            } else {
53                PulseCode::new(Level::High, T0H, Level::Low, T0L)
54            };
55        }
56        // The idle-low line after the last bit (>50 µs before the next
57        // update) latches the colour.
58        self.ch.transmit(&codes).await
59    }
60
61    /// Turns the LED off.
62    pub async fn off(&mut self) -> Result<(), Error> {
63        self.set(0, 0, 0).await
64    }
65}