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}