esp_hal_smartled/
lib.rs

1//! Allows for the use of an RMT output channel on the ESP32 family to easily drive smart RGB LEDs. This is a driver for the [smart-leds](https://crates.io/crates/smart-leds) framework and allows using the utility functions from this crate as well as higher-level libraries based on smart-leds.
2//!
3//! Different from [ws2812-esp32-rmt-driver](https://crates.io/crates/ws2812-esp32-rmt-driver), which is based on the unofficial `esp-idf` SDK, this crate is based on the official no-std [esp-hal](https://github.com/esp-rs/esp-hal).
4//!
5//! This driver uses the blocking RMT API, which is not suitable for use in async code. The [`SmartLedsWrite`] trait is implemented for [`SmartLedsAdapter`] only if a [`Blocking`] RMT channel is passed.
6//!
7//! ## Example
8//!
9//! ```rust,ignore
10//! let rmt = Rmt::new(peripherals.RMT, Rate::from_mhz(80)).unwrap();
11//!
12//! let mut led = SmartLedsAdapter::<{ buffer_size(1) }, _, color_order::Rgb, Ws2812Timing>::new(
13//!     rmt.channel0, peripherals.GPIO2
14//! );
15//!
16//! led.write(brightness([RED], 10)).unwrap();
17//! ```
18//!
19//! ## Usage overview
20//!
21//! The [`SmartLedsAdapter`] struct implements [`SmartLedsWrite`]
22//! and can be used to send color data to connected LEDs.
23//! To initialize a [`SmartLedsAdapter`], use [`SmartLedsAdapter::new`],
24//! which takes an RMT channel and a [`PeripheralOutput`].
25//! If you want to reuse the channel afterwards, you can use [`esp_hal::rmt::ChannelCreator::reborrow`] to create a shorter-lived derived channel.
26//! [`SmartLedsAdapter`] is configured at compile-time to support a variety of LED configurations. See the documentation for [`SmartLedsAdapter`] for more info.
27//!
28//! ## Features
29//!
30//! None of the features provided by this crate are for external use, they are only used for testing and examples.
31#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
32#![deny(missing_docs)]
33#![no_std]
34
35use core::{fmt::Debug, marker::PhantomData, slice::IterMut};
36
37pub use color_order::ColorOrder;
38use esp_hal::{
39    Async, Blocking, DriverMode,
40    clock::Clocks,
41    gpio::{Level, interconnect::PeripheralOutput},
42    rmt::{Channel, Error as RmtError, PulseCode, Tx, TxChannelConfig, TxChannelCreator},
43};
44use smart_leds_trait::{RGB8, SmartLedsWrite, SmartLedsWriteAsync};
45
46/// Common trait for all different smart LED dependent timings.
47///
48/// All common smart LEDs are controlled by sending PWM-like pulses, in two different configurations for high and low.
49/// The required timings (and tolerances) can be found in the relevant datasheets.
50///
51/// Provided timings: [`Sk68xxTiming`], [`Ws2812bTiming`], [`Ws2811Timing`], [`Ws2812Timing`]
52// Implementations of this should be vacant enums so they can’t be constructed.
53pub trait Timing {
54    /// Low time for zero pulse, in nanoseconds.
55    const TIME_0_LOW: u16;
56    /// High time for zero pulse, in nanoseconds.
57    const TIME_0_HIGH: u16;
58    /// Low time for one pulse, in nanoseconds.
59    const TIME_1_LOW: u16;
60    /// High time for one pulse, in nanoseconds.
61    const TIME_1_HIGH: u16;
62}
63
64const SK68XX_CODE_PERIOD: u16 = 1200;
65/// Timing for the SK68 collection of LEDs.
66/// Note: it is not verified that this is correct, the datasheet for SK6812 says otherwise.
67/// These values have been carried over from an earlier version.
68pub enum Sk68xxTiming {}
69impl Timing for Sk68xxTiming {
70    const TIME_0_HIGH: u16 = 320;
71    const TIME_0_LOW: u16 = SK68XX_CODE_PERIOD - Self::TIME_0_HIGH;
72    const TIME_1_HIGH: u16 = 640;
73    const TIME_1_LOW: u16 = SK68XX_CODE_PERIOD - Self::TIME_1_HIGH;
74}
75
76/// Timing for the WS2812B LEDs.
77pub enum Ws2812bTiming {}
78impl Timing for Ws2812bTiming {
79    const TIME_0_HIGH: u16 = 400;
80    const TIME_0_LOW: u16 = 800;
81    const TIME_1_HIGH: u16 = 850;
82    const TIME_1_LOW: u16 = 450;
83}
84
85/// Timing for the WS2812 LEDs.
86pub enum Ws2812Timing {}
87impl Timing for Ws2812Timing {
88    const TIME_0_HIGH: u16 = 350;
89    const TIME_0_LOW: u16 = 700;
90    const TIME_1_HIGH: u16 = 800;
91    const TIME_1_LOW: u16 = 600;
92}
93
94/// Timing for the WS2811 driver ICs, low-speed mode.
95pub enum Ws2811LowSpeedTiming {}
96impl Timing for Ws2811LowSpeedTiming {
97    const TIME_0_HIGH: u16 = 500;
98    const TIME_0_LOW: u16 = 2000;
99    const TIME_1_HIGH: u16 = 1200;
100    const TIME_1_LOW: u16 = 1300;
101}
102
103/// Timing for the WS2811 driver ICs, high-speed mode.
104pub enum Ws2811Timing {}
105impl Timing for Ws2811Timing {
106    const TIME_0_HIGH: u16 = Ws2811LowSpeedTiming::TIME_0_HIGH / 2;
107    const TIME_0_LOW: u16 = Ws2811LowSpeedTiming::TIME_0_LOW / 2;
108    const TIME_1_HIGH: u16 = Ws2811LowSpeedTiming::TIME_1_HIGH / 2;
109    const TIME_1_LOW: u16 = Ws2811LowSpeedTiming::TIME_1_LOW / 2;
110}
111
112/// All types of errors that can happen during the conversion and transmission
113/// of LED commands.
114#[derive(Debug, Clone, Copy)]
115#[cfg_attr(feature = "defmt", derive(defmt::Format))]
116#[non_exhaustive]
117pub enum AdapterError {
118    /// Raised in the event that the RMT buffer is not large enough.
119    ///
120    /// This almost always points to an issue with the `BUFFER_SIZE` parameter of [`SmartLedsAdapter`]. You should create this parameter using [`buffer_size`], passing in the desired number of LEDs that will be controlled.
121    BufferSizeExceeded,
122    /// Raised if something goes wrong in the transmission. This contains the inner HAL error ([`RmtError`]).
123    TransmissionError(RmtError),
124}
125
126impl From<RmtError> for AdapterError {
127    fn from(value: RmtError) -> Self {
128        Self::TransmissionError(value)
129    }
130}
131
132/// Calculate the required buffer size for a certain number of LEDs. This should be used to create the `BUFFER_SIZE` parameter of [`SmartLedsAdapter`].
133///
134/// Attempting to use more LEDs that the buffer is configured for will result in
135/// an [`AdapterError::BufferSizeExceeded`] error.
136pub const fn buffer_size(led_count: usize) -> usize {
137    // The size we're assigning here is calculated as following
138    //  (
139    //   Nr. of LEDs
140    //   * channels (r,g,b -> 3)
141    //   * pulses per channel 8)
142    //  ) + 1 additional pulse for the end delimiter
143    led_count * 24 + 1
144}
145
146/// Common [`ColorOrder`] implementations.
147pub mod color_order {
148    use smart_leds_trait::RGB8;
149
150    /// Specific channel to request from [`ColorOrder`].
151    #[derive(Copy, Clone, Debug)]
152    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
153    #[repr(u8)]
154    pub enum Channel {
155        /// First channel.
156        First = 0,
157        /// Second channel.
158        Second = 1,
159        /// Third channel.
160        Third = 2,
161    }
162
163    /// Order of colors in the physical LEDs.
164    /// Some common color orders are:
165    /// - [`Rgb`] for WS2811
166    /// - [`Grb`] for SK86XX and WS2812(B)
167    // Implementations of this should be vacant enums so they can’t be constructed.
168    pub trait ColorOrder {
169        /// Retrieve the output value for the provided channel.
170        /// For instance, if color order is RGB, then the red value will be returned for channel 0,
171        /// the green value for channel 1 and the blue value for channel 2.
172        fn get_channel_data(color: RGB8, channel: Channel) -> u8;
173    }
174
175    /// [`ColorOrder`] RGB.
176    pub enum Rgb {}
177    impl ColorOrder for Rgb {
178        fn get_channel_data(color: RGB8, channel: Channel) -> u8 {
179            match channel {
180                Channel::First => color.r,
181                Channel::Second => color.g,
182                Channel::Third => color.b,
183            }
184        }
185    }
186
187    /// [`ColorOrder`] GRB.
188    pub enum Grb {}
189    impl ColorOrder for Grb {
190        fn get_channel_data(color: RGB8, channel: Channel) -> u8 {
191            match channel {
192                Channel::First => color.g,
193                Channel::Second => color.r,
194                Channel::Third => color.b,
195            }
196        }
197    }
198}
199
200/// [`SmartLedsWrite`] driver implementation using the ESP32’s “remote control” (RMT) peripheral for hardware-offloaded, fast control of smart LEDs.
201///
202/// For usage examples and a general overview see [the crate documentation](`crate`).
203///
204/// This type supports many configurations of color order, LED timings, and LED count. For this reason, there are three main type parameters you have to choose:
205/// - The buffer size. This determines how many RMT pulses can be sent by this driver, and allows it to function entirely without heap allocation. It is strongly recommended to use the [`buffer_size`] function with the desired number of LEDs to choose a correct buffer size, otherwise [`SmartLedsWrite::write`] will return [`AdapterError::BufferSizeExceeded`].
206/// - The [`ColorOrder`]. This determines what order the LED expects the color values in. Almost all LEDs use [`color_order::Rgb`] or [`color_order::Grb`].
207/// - The [`Timing`]. This determines the smart LED type in use; what kind of signal it expects. Several implementations for common LED types like WS2812 are provided. Note that many WS2812-like LEDs are at least almost compatible in their timing, even though the datasheets specify different amounts, the other LEDs’ values are within the tolerance range, and even exceeding these, many LEDs continue to work beyond their specified timing range. It is however recommended to use the corresponding LED type, or implement your own when needed.
208///
209/// When the driver move is [`Blocking`], this type implements the blocking [`SmartLedsWrite`] interface. An async interface for [`esp_hal::Async`] may be added in the future. (You usually don’t need to choose this manually, Rust can deduce it from the passed-in RMT channel.)
210pub struct SmartLedsAdapter<'d, const BUFFER_SIZE: usize, Mode, Order, Timing>
211where
212    Mode: DriverMode,
213    Order: ColorOrder,
214    Timing: crate::Timing,
215{
216    channel: Option<Channel<'d, Mode, Tx>>,
217    rmt_buffer: [PulseCode; BUFFER_SIZE],
218    pulses: (PulseCode, PulseCode),
219    _order: PhantomData<Order>,
220    _timing: PhantomData<Timing>,
221}
222
223impl<'d, const BUFFER_SIZE: usize, Mode, Order, Timing>
224    SmartLedsAdapter<'d, BUFFER_SIZE, Mode, Order, Timing>
225where
226    Mode: DriverMode,
227    Order: ColorOrder,
228    Timing: crate::Timing,
229{
230    /// Creates a new [`SmartLedsAdapter`] that drives the provided output using the given RMT channel.
231    ///
232    /// Note that calling this function usually requires you to specify the desired buffer size, [`ColorOrder`] and [`Timing`]. See the struct documentation for details.
233    ///
234    /// If you want to reuse the channel afterwards, you can use [`esp_hal::rmt::ChannelCreator::reborrow`] to create a shorter-lived derived channel.
235    ///
236    /// # Errors
237    ///
238    /// If any configuration issue with the RMT [`Channel`] occurs, the error will be returned.
239    pub fn new<C, P>(channel: C, pin: P) -> Result<Self, RmtError>
240    where
241        C: TxChannelCreator<'d, Mode>,
242        P: PeripheralOutput<'d>,
243    {
244        Self::new_with_memsize(channel, pin, 1)
245    }
246    /// Creates a new [`SmartLedsAdapter`] that drives the provided output using the given RMT channel.
247    ///
248    /// Note that calling this function usually requires you to specify the desired buffer size, [`ColorOrder`] and [`Timing`]. See the struct documentation for details.
249    ///
250    /// If you want to reuse the channel afterwards, you can use [`esp_hal::rmt::ChannelCreator::reborrow`] to create a shorter-lived derived channel.
251    ///
252    /// The `memsize` parameter determines how many RMT blocks this adapter will use.
253    /// If you use any value other than 1, other RMT channels will not be available, as their memory blocks will be used up by this driver.
254    /// However, this can allow you to control many more LEDs without issues.
255    ///
256    /// # Errors
257    ///
258    /// If any configuration issue with the RMT [`Channel`] occurs, the error will be returned.
259    pub fn new_with_memsize<C, P>(channel: C, pin: P, memsize: u8) -> Result<Self, RmtError>
260    where
261        C: TxChannelCreator<'d, Mode>,
262        P: PeripheralOutput<'d>,
263    {
264        let config = TxChannelConfig::default()
265            .with_clk_divider(1)
266            .with_idle_output_level(Level::Low)
267            .with_memsize(memsize)
268            .with_carrier_modulation(false)
269            .with_idle_output(true);
270
271        let channel = channel.configure_tx(pin, config)?;
272
273        // Assume the RMT peripheral is set up to use the APB clock
274        let clocks = Clocks::get();
275        // convert to the MHz value to simplify nanosecond calculations
276        let src_clock = clocks.apb_clock.as_hz() / 1_000_000;
277
278        Ok(Self {
279            channel: Some(channel),
280            rmt_buffer: [PulseCode::end_marker(); _],
281            pulses: (
282                PulseCode::new(
283                    Level::High,
284                    ((Timing::TIME_0_HIGH as u32 * src_clock) / 1000) as u16,
285                    Level::Low,
286                    ((Timing::TIME_0_LOW as u32 * src_clock) / 1000) as u16,
287                ),
288                PulseCode::new(
289                    Level::High,
290                    ((Timing::TIME_1_HIGH as u32 * src_clock) / 1000) as u16,
291                    Level::Low,
292                    ((Timing::TIME_1_LOW as u32 * src_clock) / 1000) as u16,
293                ),
294            ),
295            _order: PhantomData,
296            _timing: PhantomData,
297        })
298    }
299
300    fn convert_rgb_to_pulse(
301        value: RGB8,
302        mut_iter: &mut IterMut<PulseCode>,
303        pulses: (PulseCode, PulseCode),
304    ) -> Result<(), AdapterError> {
305        use crate::color_order::Channel;
306
307        Self::convert_rgb_channel_to_pulses(
308            Order::get_channel_data(value, Channel::First),
309            mut_iter,
310            pulses,
311        )?;
312        Self::convert_rgb_channel_to_pulses(
313            Order::get_channel_data(value, Channel::Second),
314            mut_iter,
315            pulses,
316        )?;
317        Self::convert_rgb_channel_to_pulses(
318            Order::get_channel_data(value, Channel::Third),
319            mut_iter,
320            pulses,
321        )?;
322
323        Ok(())
324    }
325
326    fn convert_rgb_channel_to_pulses(
327        channel_value: u8,
328        mut_iter: &mut IterMut<PulseCode>,
329        pulses: (PulseCode, PulseCode),
330    ) -> Result<(), AdapterError> {
331        for position in [128, 64, 32, 16, 8, 4, 2, 1] {
332            *mut_iter.next().ok_or(AdapterError::BufferSizeExceeded)? =
333                match channel_value & position {
334                    0 => pulses.0,
335                    _ => pulses.1,
336                }
337        }
338
339        Ok(())
340    }
341
342    /// Create and store RMT data from the color information provided.
343    fn create_rmt_data(
344        &mut self,
345        iterator: impl IntoIterator<Item = impl Into<RGB8>>,
346    ) -> Result<(), AdapterError> {
347        // We always start from the beginning of the buffer
348        let mut seq_iter = self.rmt_buffer.iter_mut();
349
350        // Add all converted iterator items to the buffer.
351        // This will result in an `BufferSizeExceeded` error in case
352        // the iterator provides more elements than the buffer can take.
353        for item in iterator {
354            Self::convert_rgb_to_pulse(item.into(), &mut seq_iter, self.pulses)?;
355        }
356
357        // Finally, add an end element.
358        *seq_iter.next().ok_or(AdapterError::BufferSizeExceeded)? = PulseCode::end_marker();
359
360        Ok(())
361    }
362}
363
364impl<'d, const BUFFER_SIZE: usize, Order, Timing> SmartLedsWrite
365    for SmartLedsAdapter<'d, BUFFER_SIZE, Blocking, Order, Timing>
366where
367    Order: ColorOrder,
368    Timing: crate::Timing,
369{
370    type Error = AdapterError;
371    type Color = RGB8;
372
373    /// Convert all RGB8 items of the iterator to the RMT format and
374    /// add them to internal buffer, then start a singular RMT operation
375    /// based on that buffer.
376    fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
377    where
378        T: IntoIterator<Item = I>,
379        I: Into<Self::Color>,
380    {
381        self.create_rmt_data(iterator)?;
382
383        // Perform the actual RMT operation. We use the u32 values here right away.
384        let channel = self.channel.take().unwrap();
385        // TODO: If the transmit fails, we’re in an unsafe state and future calls to write() will panic.
386        // This is currently unavoidable since transmit consumes the channel on error.
387        // This is a known design flaw in the current RMT API and will be fixed soon.
388        // We should adjust our usage accordingly as soon as possible.
389        match channel.transmit(&self.rmt_buffer)?.wait() {
390            Ok(chan) => {
391                self.channel = Some(chan);
392                Ok(())
393            }
394            Err((e, chan)) => {
395                self.channel = Some(chan);
396                Err(AdapterError::TransmissionError(e))
397            }
398        }
399    }
400}
401
402impl<'d, const BUFFER_SIZE: usize, Order, Timing> SmartLedsWriteAsync
403    for SmartLedsAdapter<'d, BUFFER_SIZE, Async, Order, Timing>
404where
405    Order: ColorOrder,
406    Timing: crate::Timing,
407{
408    type Error = AdapterError;
409    type Color = RGB8;
410
411    /// Convert all RGB8 items of the iterator to the RMT format and
412    /// add them to internal buffer, then start a singular RMT operation
413    /// based on that buffer.
414    async fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
415    where
416        T: IntoIterator<Item = I>,
417        I: Into<Self::Color>,
418    {
419        self.create_rmt_data(iterator)?;
420
421        // Perform the actual RMT operation. We use the u32 values here right away.
422        self.channel
423            .as_mut()
424            .unwrap()
425            .transmit(&self.rmt_buffer)
426            .await?;
427        Ok(())
428    }
429}