esp_hal_smartled/
lib.rs

1//! This adapter allows for the use of an RMT output channel to easily interact
2//! with RGB LEDs and use the convenience functions of the
3//! [`smart-leds`](https://crates.io/crates/smart-leds) crate.
4//!
5//! This is a simple implementation where every LED is adressed in an
6//! individual RMT operation. This is working perfectly fine in blocking mode,
7//! but in case this is used in combination with interrupts that might disturb
8//! the sequential sending, an alternative implementation (addressing the LEDs
9//! in a sequence in a single RMT send operation) might be required!
10//!
11//! ## Example
12//!
13//! ```rust
14//! #![no_std]
15//! #![no_main]
16//!
17//! use esp_backtrace as _;
18//! use esp_hal::{rmt::Rmt, time::Rate, Config};
19//! use esp_hal_smartled::{smartLedBuffer, SmartLedsAdapter};
20//! use smart_leds::{brightness, colors::RED, SmartLedsWrite as _};
21//!
22//! #[esp_hal::main]
23//! fn main() -> ! {
24//!     let p = esp_hal::init(Config::default());
25//!     let mut led = {
26//!         let frequency = Rate::from_mhz(80);
27//!         let rmt = Rmt::new(p.RMT, frequency).expect("Failed to initialize RMT0");
28//!         SmartLedsAdapter::new(rmt.channel0, p.GPIO2, smartLedBuffer!(1))
29//!     };
30//!     let level = 10;
31//!     led.write(brightness([RED].into_iter(), level)).unwrap();
32//!     loop {} // loop forever
33//! }
34//! ```
35//!
36//! ## Feature Flags
37#![doc = document_features::document_features!()]
38#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
39#![deny(missing_docs)]
40#![no_std]
41
42use core::{fmt::Debug, marker::PhantomData, slice::IterMut};
43
44use esp_hal::{
45    Async, Blocking,
46    clock::Clocks,
47    gpio::{Level, interconnect::PeripheralOutput},
48    rmt::{Channel, Error as RmtError, PulseCode, Tx, TxChannelConfig, TxChannelCreator},
49};
50use rgb::Grb;
51use smart_leds_trait::{SmartLedsWrite, SmartLedsWriteAsync};
52
53// Required RMT RAM to drive one LED.
54// number of channels (r,g,b -> 3) * pulses per channel 8)
55const RMT_RAM_ONE_LED: usize = 3 * 8;
56const RMT_RAM_ONE_RBGW_LED: usize = 4 * 8;
57
58const SK68XX_CODE_PERIOD: u32 = 1250; // 800kHz
59const SK68XX_T0H_NS: u32 = 400; // 300ns per SK6812 datasheet, 400 per WS2812. Some require >350ns for T0H. Others <500ns for T0H.
60const SK68XX_T0L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T0H_NS;
61const SK68XX_T1H_NS: u32 = 850; // 900ns per SK6812 datasheet, 850 per WS2812. > 550ns is sometimes enough. Some require T1H >= 2 * T0H. Some require > 300ns T1L.
62const SK68XX_T1L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T1H_NS;
63
64/// All types of errors that can happen during the conversion and transmission
65/// of LED commands
66#[derive(Debug)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68pub enum LedAdapterError {
69    /// Raised in the event that the provided data container is not large enough
70    BufferSizeExceeded,
71    /// Raised if something goes wrong in the transmission,
72    TransmissionError(RmtError),
73}
74
75impl From<RmtError> for LedAdapterError {
76    fn from(e: RmtError) -> Self {
77        LedAdapterError::TransmissionError(e)
78    }
79}
80
81fn led_pulses_for_clock(src_clock: u32) -> (PulseCode, PulseCode) {
82    (
83        PulseCode::new(
84            Level::High,
85            ((SK68XX_T0H_NS * src_clock) / 1000) as u16,
86            Level::Low,
87            ((SK68XX_T0L_NS * src_clock) / 1000) as u16,
88        ),
89        PulseCode::new(
90            Level::High,
91            ((SK68XX_T1H_NS * src_clock) / 1000) as u16,
92            Level::Low,
93            ((SK68XX_T1L_NS * src_clock) / 1000) as u16,
94        ),
95    )
96}
97
98fn led_config() -> TxChannelConfig {
99    TxChannelConfig::default()
100        .with_clk_divider(1)
101        .with_idle_output_level(Level::Low)
102        .with_carrier_modulation(false)
103        .with_idle_output(true)
104}
105
106fn convert_to_pulses(
107    value: &[u8],
108    mut_iter: &mut IterMut<PulseCode>,
109    pulses: (PulseCode, PulseCode),
110) -> Result<(), LedAdapterError> {
111    for v in value {
112        convert_rgb_channel_to_pulses(*v, mut_iter, pulses)?;
113    }
114    Ok(())
115}
116
117fn convert_rgb_channel_to_pulses(
118    channel_value: u8,
119    mut_iter: &mut IterMut<PulseCode>,
120    pulses: (PulseCode, PulseCode),
121) -> Result<(), LedAdapterError> {
122    for position in [128, 64, 32, 16, 8, 4, 2, 1] {
123        *mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? =
124            match channel_value & position {
125                0 => pulses.0,
126                _ => pulses.1,
127            }
128    }
129
130    Ok(())
131}
132
133/// Function to calculate the required RMT buffer size for a given number of RGB LEDs when using
134/// the blocking API.
135///
136/// For RGBW leds use [buffer_size_rgbw].
137///
138/// This buffer size is calculated for the synchronous API provided by the [SmartLedsAdapter].
139/// [buffer_size_async] should be used for the asynchronous API.
140pub const fn buffer_size(num_leds: usize) -> usize {
141    // 1 additional pulse for the end delimiter
142    num_leds * RMT_RAM_ONE_LED + 1
143}
144
145/// Function to calculate the required RMT buffer size for a given number of RGBW LEDs when using
146/// the blocking API.
147///
148/// For RGB leds use [buffer_size].
149///
150/// This buffer size is calculated for the synchronous API provided by the [SmartLedsAdapter].
151/// [buffer_size_async] should be used for the asynchronous API.
152pub const fn buffer_size_rgbw(num_leds: usize) -> usize {
153    // 1 additional pulse for the end delimiter
154    num_leds * RMT_RAM_ONE_RBGW_LED + 1
155}
156
157/// Macro to allocate a buffer sized for a specific number of LEDs to be
158/// addressed.
159///
160/// For RGBW leds, use `[smart_led_buffer!(NUM_LEDS, RGBW)]` where `NUM_LEDS` is your number of leds.
161///
162/// Attempting to use more LEDs that the buffer is configured for will result in
163/// an `LedAdapterError:BufferSizeExceeded` error.
164#[macro_export]
165macro_rules! smart_led_buffer {
166    ( $num_leds: expr ) => {
167        [::esp_hal::rmt::PulseCode::end_marker(); $crate::buffer_size($num_leds)]
168    };
169    ( $num_leds: expr; RGBW ) => {
170        [::esp_hal::rmt::PulseCode::end_marker(); $crate::buffer_size_rgbw($num_leds)]
171    };
172}
173
174/// Deprecated alias for [smart_led_buffer] macro.
175#[macro_export]
176#[deprecated]
177macro_rules! smartLedBuffer {
178    ( $num_leds: expr ) => {
179        smart_led_buffer!($num_leds);
180    };
181}
182
183/// Adapter taking an RMT channel and a specific pin and providing RGB LED
184/// interaction functionality using the `smart-leds` crate
185pub struct SmartLedsAdapter<'ch, const BUFFER_SIZE: usize, Color = Grb<u8>> {
186    channel: Option<Channel<'ch, Blocking, Tx>>,
187    rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE],
188    pulses: (PulseCode, PulseCode),
189    color: PhantomData<Color>,
190}
191
192impl<'ch, const BUFFER_SIZE: usize> SmartLedsAdapter<'ch, BUFFER_SIZE, Grb<u8>> {
193    /// Create a new adapter object that drives the pin using the RMT channel.
194    pub fn new<C, O>(channel: C, pin: O, rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE]) -> Self
195    where
196        O: PeripheralOutput<'ch>,
197        C: TxChannelCreator<'ch, Blocking>,
198    {
199        Self::new_with_color(channel, pin, rmt_buffer)
200    }
201}
202
203impl<'ch, const BUFFER_SIZE: usize, Color> SmartLedsAdapter<'ch, BUFFER_SIZE, Color>
204where
205    Color: rgb::ComponentSlice<u8>,
206{
207    /// Create a new adapter object that drives the pin using the RMT channel.
208    pub fn new_with_color<C, O>(
209        channel: C,
210        pin: O,
211        rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE],
212    ) -> SmartLedsAdapter<'ch, BUFFER_SIZE, Color>
213    where
214        O: PeripheralOutput<'ch>,
215        C: TxChannelCreator<'ch, Blocking>,
216    {
217        let channel = channel.configure_tx(pin, led_config()).unwrap();
218
219        // Assume the RMT peripheral is set up to use the APB clock
220        let src_clock = Clocks::get().apb_clock.as_mhz();
221
222        Self {
223            channel: Some(channel),
224            rmt_buffer,
225            pulses: led_pulses_for_clock(src_clock),
226            color: PhantomData,
227        }
228    }
229}
230
231impl<'ch, const BUFFER_SIZE: usize, Color> SmartLedsWrite
232    for SmartLedsAdapter<'ch, BUFFER_SIZE, Color>
233where
234    Color: rgb::ComponentSlice<u8>,
235{
236    type Error = LedAdapterError;
237    type Color = Color;
238
239    /// Convert all items of the iterator to the RMT format and
240    /// add them to internal buffer, then start a singular RMT operation
241    /// based on that buffer.
242    fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
243    where
244        T: IntoIterator<Item = I>,
245        I: Into<Self::Color>,
246    {
247        // We always start from the beginning of the buffer
248        let mut seq_iter = self.rmt_buffer.iter_mut();
249
250        // Add all converted iterator items to the buffer.
251        // This will result in an `BufferSizeExceeded` error in case
252        // the iterator provides more elements than the buffer can take.
253        for item in iterator {
254            convert_to_pulses(item.into().as_slice(), &mut seq_iter, self.pulses)?;
255        }
256
257        // Finally, add an end element.
258        *seq_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = PulseCode::end_marker();
259
260        // Perform the actual RMT operation.
261        let channel = self.channel.take().unwrap();
262        match channel.transmit(self.rmt_buffer)?.wait() {
263            Ok(chan) => {
264                self.channel = Some(chan);
265                Ok(())
266            }
267            Err((e, chan)) => {
268                self.channel = Some(chan);
269                Err(LedAdapterError::TransmissionError(e))
270            }
271        }
272    }
273}
274
275// Support for asynchronous and non-blocking use of the RMT peripheral to drive smart LEDs.
276
277/// Function to calculate the required RMT buffer size for a given number of RGB LEDs when using
278/// the asynchronous API.
279///
280/// Use [buffer_size_async_rgbw] for RGBW leds.
281///
282/// This buffer size is calculated for the asynchronous API provided by the
283/// [SmartLedsAdapterAsync]. [buffer_size] should be used for the synchronous API.
284pub const fn buffer_size_async(num_leds: usize) -> usize {
285    // 1 byte end delimiter for each transfer.
286    num_leds * (RMT_RAM_ONE_LED + 1)
287}
288
289/// Function to calculate the required RMT buffer size for a given number of RGBW LEDs when using
290/// the asynchronous API.
291///
292/// Use [buffer_size_async] for RGB leds.
293///
294/// This buffer size is calculated for the asynchronous API provided by the
295/// [SmartLedsAdapterAsync]. [buffer_size] should be used for the synchronous API.
296pub const fn buffer_size_async_rgbw(num_leds: usize) -> usize {
297    // 1 byte end delimiter for each transfer.
298    num_leds * (RMT_RAM_ONE_RBGW_LED + 1)
299}
300
301/// Adapter taking an RMT channel and a specific pin and providing RGB LED
302/// interaction functionality.
303pub struct SmartLedsAdapterAsync<'ch, const BUFFER_SIZE: usize, Color = Grb<u8>> {
304    channel: Channel<'ch, Async, Tx>,
305    rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE],
306    pulses: (PulseCode, PulseCode),
307    color: PhantomData<Color>,
308}
309
310impl<'ch, const BUFFER_SIZE: usize> SmartLedsAdapterAsync<'ch, BUFFER_SIZE, Grb<u8>> {
311    /// Create a new adapter object that drives the pin using the RMT channel.
312    pub fn new<C, O>(channel: C, pin: O, rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE]) -> Self
313    where
314        O: PeripheralOutput<'ch>,
315        C: TxChannelCreator<'ch, Async>,
316    {
317        Self::new_with_color(channel, pin, rmt_buffer)
318    }
319}
320
321impl<'ch, const BUFFER_SIZE: usize, Color> SmartLedsAdapterAsync<'ch, BUFFER_SIZE, Color>
322where
323    Color: rgb::ComponentSlice<u8>,
324{
325    /// Create a new adapter object that drives the pin using the RMT channel.
326    pub fn new_with_color<C, O>(
327        channel: C,
328        pin: O,
329        rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE],
330    ) -> SmartLedsAdapterAsync<'ch, BUFFER_SIZE, Color>
331    where
332        O: PeripheralOutput<'ch>,
333        C: TxChannelCreator<'ch, Async>,
334    {
335        let channel = channel.configure_tx(pin, led_config()).unwrap();
336
337        // Assume the RMT peripheral is set up to use the APB clock
338        let src_clock = Clocks::get().apb_clock.as_mhz();
339
340        Self {
341            channel,
342            rmt_buffer,
343            pulses: led_pulses_for_clock(src_clock),
344            color: PhantomData,
345        }
346    }
347
348    fn prepare_rmt_buffer<I: Into<Color>>(
349        &mut self,
350        iterator: impl IntoIterator<Item = I>,
351    ) -> Result<(), LedAdapterError> {
352        // We always start from the beginning of the buffer
353        let mut seq_iter = self.rmt_buffer.iter_mut();
354
355        // Add all converted iterator items to the buffer.
356        // This will result in an `BufferSizeExceeded` error in case
357        // the iterator provides more elements than the buffer can take.
358        for item in iterator {
359            Self::convert_to_pulses(item.into().as_slice(), &mut seq_iter, self.pulses)?;
360        }
361        Ok(())
362    }
363
364    /// Async sends one pixel at a time so needs a delimiter after each pixel
365    fn convert_to_pulses(
366        value: &[u8],
367        mut_iter: &mut IterMut<PulseCode>,
368        pulses: (PulseCode, PulseCode),
369    ) -> Result<(), LedAdapterError> {
370        for v in value {
371            convert_rgb_channel_to_pulses(*v, mut_iter, pulses)?;
372        }
373        *mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = PulseCode::end_marker();
374        Ok(())
375    }
376}
377
378impl<'ch, const BUFFER_SIZE: usize, Color> SmartLedsWriteAsync
379    for SmartLedsAdapterAsync<'ch, BUFFER_SIZE, Color>
380where
381    Color: rgb::ComponentSlice<u8>,
382{
383    type Error = LedAdapterError;
384    type Color = Color;
385
386    /// Convert all items of the iterator to the RMT format and
387    /// add them to internal buffer, then start perform all asynchronous operations based on
388    /// that buffer.
389    async fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
390    where
391        T: IntoIterator<Item = I>,
392        I: Into<Self::Color>,
393    {
394        self.prepare_rmt_buffer(iterator)?;
395        for chunk in self.rmt_buffer.chunks(RMT_RAM_ONE_LED + 1) {
396            self.channel
397                .transmit(chunk)
398                .await
399                .map_err(LedAdapterError::TransmissionError)?;
400        }
401        Ok(())
402    }
403}