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 either the blocking RMT API, or the async one, depending on the given RMT channel.
6//! The [`SmartLedsWrite`] trait (or [`SmartLedsWriteAsync`]) is implemented for [`RmtSmartLeds`] with the corresponding channel mode.
7//!
8//! ## Example
9//!
10//! ```rust,ignore
11//! let rmt = Rmt::new(peripherals.RMT, Rate::from_mhz(80)).unwrap();
12//!
13//! let mut led = RmtSmartLeds::<{ buffer_size::<RGB8>(1) }, _, RGB8, color_order::Rgb, Ws2812Timing>::new(
14//! rmt.channel0, peripherals.GPIO2
15//! );
16//!
17//! led.write(brightness([RED], 10)).unwrap();
18//! ```
19//!
20//! ## Usage overview
21//!
22//! The [`RmtSmartLeds`] struct implements [`SmartLedsWrite`] or [`SmartLedsWriteAsync`]
23//! and can be used to send color data to connected LEDs.
24//! To initialize a [`RmtSmartLeds`], use [`RmtSmartLeds::new`],
25//! which takes an RMT channel and a [`PeripheralOutput`].
26//! If you want to reuse the channel afterwards, you can use [`esp_hal::rmt::ChannelCreator::reborrow`] to create a shorter-lived derived channel.
27//! [`RmtSmartLeds`] is configured at compile-time to support a variety of LED configurations. See the documentation for [`RmtSmartLeds`] for more info.
28//!
29//! ## Features
30//!
31//! - `defmt`: Derive [`defmt::Format`] on some types.
32//!
33//! Other features provided by this crate are not for external use, they are only used for testing and examples.
34#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
35#![deny(missing_docs)]
36#![no_std]
37
38use core::{fmt::Debug, marker::PhantomData};
39
40pub use color_order::ColorOrder;
41use esp_hal::{
42 Async, Blocking, DriverMode,
43 clock::Clocks,
44 gpio::{Level, interconnect::PeripheralOutput},
45 rmt::{Channel, Error as RmtError, PulseCode, Tx, TxChannelConfig, TxChannelCreator},
46};
47use num_traits::Unsigned;
48use smart_leds_trait::{
49 CctWhite, RGB, RGB8, RGBCCT, RGBW, SmartLedsWrite, SmartLedsWriteAsync, White,
50};
51
52/// Common trait for all different smart LED dependent timings.
53///
54/// All common smart LEDs are controlled by sending PWM-like pulses, in two different configurations for high and low.
55/// The required timings (and tolerances) can be found in the relevant datasheets.
56///
57/// Provided timings: [`Sk68xxTiming`], [`Ws2812bTiming`], [`Ws2811Timing`], [`Ws2812Timing`]
58// Implementations of this should be vacant enums so they can’t be constructed.
59pub trait Timing {
60 /// Low time for zero pulse, in nanoseconds.
61 const TIME_0_LOW: u16;
62 /// High time for zero pulse, in nanoseconds.
63 const TIME_0_HIGH: u16;
64 /// Low time for one pulse, in nanoseconds.
65 const TIME_1_LOW: u16;
66 /// High time for one pulse, in nanoseconds.
67 const TIME_1_HIGH: u16;
68}
69
70const SK68XX_CODE_PERIOD: u16 = 1200;
71/// Timing for the SK68 collection of LEDs.
72pub enum Sk68xxTiming {}
73impl Timing for Sk68xxTiming {
74 const TIME_0_HIGH: u16 = 320;
75 const TIME_0_LOW: u16 = SK68XX_CODE_PERIOD - Self::TIME_0_HIGH;
76 const TIME_1_HIGH: u16 = 640;
77 const TIME_1_LOW: u16 = SK68XX_CODE_PERIOD - Self::TIME_1_HIGH;
78}
79
80/// Timing for the WS2812B LEDs.
81pub enum Ws2812bTiming {}
82impl Timing for Ws2812bTiming {
83 const TIME_0_HIGH: u16 = 400;
84 const TIME_0_LOW: u16 = 800;
85 const TIME_1_HIGH: u16 = 850;
86 const TIME_1_LOW: u16 = 450;
87}
88
89/// Timing for the WS2812 LEDs.
90pub enum Ws2812Timing {}
91impl Timing for Ws2812Timing {
92 const TIME_0_HIGH: u16 = 350;
93 const TIME_0_LOW: u16 = 700;
94 const TIME_1_HIGH: u16 = 800;
95 const TIME_1_LOW: u16 = 600;
96}
97
98/// Timing for the WS2811 driver ICs, low-speed mode.
99pub enum Ws2811LowSpeedTiming {}
100impl Timing for Ws2811LowSpeedTiming {
101 const TIME_0_HIGH: u16 = 500;
102 const TIME_0_LOW: u16 = 2000;
103 const TIME_1_HIGH: u16 = 1200;
104 const TIME_1_LOW: u16 = 1300;
105}
106
107/// Timing for the WS2811 driver ICs, high-speed mode.
108pub enum Ws2811Timing {}
109impl Timing for Ws2811Timing {
110 const TIME_0_HIGH: u16 = Ws2811LowSpeedTiming::TIME_0_HIGH / 2;
111 const TIME_0_LOW: u16 = Ws2811LowSpeedTiming::TIME_0_LOW / 2;
112 const TIME_1_HIGH: u16 = Ws2811LowSpeedTiming::TIME_1_HIGH / 2;
113 const TIME_1_LOW: u16 = Ws2811LowSpeedTiming::TIME_1_LOW / 2;
114}
115
116/// All types of errors that can happen during the conversion and transmission
117/// of LED commands.
118#[derive(Debug, Clone, Copy)]
119#[cfg_attr(feature = "defmt", derive(defmt::Format))]
120#[non_exhaustive]
121pub enum AdapterError {
122 /// Raised in the event that the RMT buffer is not large enough.
123 ///
124 /// This almost always points to an issue with the `BUFFER_SIZE` parameter of [`RmtSmartLeds`].
125 /// You should create this parameter using [`buffer_size`], passing in the desired number of LEDs that will be controlled.
126 BufferSizeExceeded,
127 /// Raised if something goes wrong in the transmission. This contains the inner HAL error ([`RmtError`]).
128 TransmissionError(RmtError),
129}
130
131impl From<RmtError> for AdapterError {
132 fn from(value: RmtError) -> Self {
133 Self::TransmissionError(value)
134 }
135}
136
137/// Utility trait that retrieves metadata about all [`smart_leds_trait`] color types.
138pub trait Color {
139 /// The maximum channel number this color supports.
140 ///
141 /// - For RGB (or any permutation thereof), this is 3.
142 /// - For RGBW, this is 4.
143 /// - For RGBCCT, this is 5.
144 /// - For CCT, this is 2.
145 ///
146 /// Note that this channel count is used by users of [`ColorOrder`] to limit the channel number that’s passed into [`ColorOrder::get_channel_data`].
147 const CHANNELS: u8;
148
149 /// Type of a single channel of this color. Usually [`u8`], but [`u16`] is also used for some LEDs.
150 type ChannelType: Unsigned + Into<usize>;
151}
152
153impl<T> Color for RGB<T>
154where
155 T: Unsigned + Into<usize>,
156{
157 const CHANNELS: u8 = 3;
158 type ChannelType = T;
159}
160
161impl<T> Color for RGBW<T>
162where
163 T: Unsigned + Into<usize>,
164{
165 const CHANNELS: u8 = 4;
166 type ChannelType = T;
167}
168
169impl<T> Color for RGBCCT<T>
170where
171 T: Unsigned + Into<usize>,
172{
173 const CHANNELS: u8 = 5;
174 type ChannelType = T;
175}
176
177impl<T> Color for White<T>
178where
179 T: Unsigned + Into<usize>,
180{
181 const CHANNELS: u8 = 1;
182 type ChannelType = T;
183}
184
185impl<T> Color for CctWhite<T>
186where
187 T: Unsigned + Into<usize>,
188{
189 const CHANNELS: u8 = 2;
190 type ChannelType = T;
191}
192
193/// Calculate the required buffer size for a certain number of LEDs.
194/// This should be used to create the `BUFFER_SIZE` parameter of [`RmtSmartLeds`].
195///
196/// Attempting to use more LEDs that the buffer is configured for will result in
197/// an [`AdapterError::BufferSizeExceeded`] error.
198///
199/// You need to specify the correct color and channel type
200// TODO: As soon as generic expressions are more stabilized, we should be able to do this calculation entirely internally in `RmtSmartLeds`. For now, users have to be careful.
201pub const fn buffer_size<C: Color>(led_count: usize) -> usize {
202 // The size we're assigning here is calculated as following
203 // (
204 // Nr. of LEDs
205 // * channels
206 // * pulses per channel (=bitcount)
207 // ) + 1 additional pulse for the end delimiter
208 led_count * (size_of::<C::ChannelType>() * 8) * C::CHANNELS as usize + 1
209}
210
211/// Common [`ColorOrder`] implementations.
212pub mod color_order {
213 use num_traits::Unsigned;
214 use smart_leds_trait::{RGB, RGBW, White};
215
216 use crate::Color;
217
218 /// Order of colors in the physical LEDs.
219 /// The most common color orders for RGB LEDs are [`Rgb`] (most integrated controllers like WS2812) and [`Grb`].
220 /// Note that discrete ICs have generic channels and are often wired up arbitrarily, so you will have to check which order is correct for your hardware.
221 // Implementations of this should be vacant enums so they can’t be constructed.
222 // This should also be a constant trait once that becomes a stable Rust feature.
223 pub trait ColorOrder<C: Color> {
224 /// Retrieve the output value for the provided channel.
225 /// For instance, if color order is RGB, then the red value will be returned for channel 0,
226 /// the green value for channel 1 and the blue value for channel 2.
227 ///
228 /// The maximum channel number users are allowed to pass in is [`Color::CHANNELS`] minus one.
229 /// If this restriction is not upheld, the implementation may panic.
230 fn get_channel_data(color: &C, channel: u8) -> C::ChannelType;
231 }
232
233 macro_rules! color_order_rgb {
234 ($name:ident => $first:ident, $second:ident, $third:ident) => {
235 #[doc = concat!("[`ColorOrder`] ", stringify!($name), ".")]
236 pub enum $name {}
237 impl<T> ColorOrder<RGB<T>> for $name
238 where
239 T: Copy + Unsigned + Into<usize>,
240 {
241 fn get_channel_data(color: &RGB<T>, channel: u8) -> T {
242 match channel {
243 0 => color.$first,
244 1 => color.$second,
245 2 => color.$third,
246 _ => unreachable!(),
247 }
248 }
249 }
250 };
251 }
252
253 color_order_rgb!(Rgb => r, g, b);
254 color_order_rgb!(Rbg => r, b, g);
255 color_order_rgb!(Grb => g, r, b);
256 color_order_rgb!(Gbr => g, b, r);
257 color_order_rgb!(Brg => b, r, g);
258 color_order_rgb!(Bgr => b, g, r);
259
260 /// [`ColorOrder`] RGBW.
261 pub enum Rgbw {}
262 impl<T> ColorOrder<RGBW<T>> for Rgbw
263 where
264 T: Copy + Unsigned + Into<usize>,
265 {
266 fn get_channel_data(color: &RGBW<T>, channel: u8) -> T {
267 match channel {
268 0 => color.r,
269 1 => color.g,
270 2 => color.b,
271 3 => color.a.0,
272 _ => unreachable!(),
273 }
274 }
275 }
276
277 /// [`ColorOrder`] for single-channel smart LEDs, where the order is trivial.
278 pub enum SingleChannel {}
279 impl<T> ColorOrder<White<T>> for SingleChannel
280 where
281 T: Copy + Unsigned + Into<usize>,
282 {
283 fn get_channel_data(color: &White<T>, _channel: u8) -> T {
284 color.0
285 }
286 }
287}
288
289/// [`SmartLedsWrite`] driver implementation using the ESP32’s “remote control” (RMT) peripheral for hardware-offloaded, fast control of smart LEDs.
290///
291/// For usage examples and a general overview see [the crate documentation](`crate`).
292///
293/// 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:
294/// - 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`].
295/// - The `Color`.
296/// This determines the color model and number of channels to be sent.
297/// - The [`ColorOrder`].
298/// This determines what order the LED expects the color values in.
299/// - The [`Timing`].
300/// This determines the smart LED type in use; what kind of signal it expects.
301/// Several implementations for common LED types like WS2812 are provided.
302/// 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.
303/// It is however recommended to use the corresponding LED type, or implement your own when needed.
304///
305/// When the driver mode is [`Blocking`], this type implements the blocking [`SmartLedsWrite`] interface.
306/// When the driver mode is [`Async`], this type implements the [`SmartLedsWriteAsync`] interface instead.
307/// (You usually don’t need to choose this manually, Rust can deduce it from the passed-in RMT channel.)
308///
309/// Some common configurations have predefined aliases: [`Ws2812SmartLeds`], [`Sk68xxRgbwSmartLeds`], [`WhiteSmartLeds`], [`Rgb8RmtSmartLeds`].
310pub struct RmtSmartLeds<'d, const BUFFER_SIZE: usize, Mode, C, Order, Timing>
311where
312 Mode: DriverMode,
313 C: Color,
314 Order: ColorOrder<C>,
315 Timing: crate::Timing,
316{
317 channel: Option<Channel<'d, Mode, Tx>>,
318 rmt_buffer: [PulseCode; BUFFER_SIZE],
319 pulses: (PulseCode, PulseCode),
320 _order: PhantomData<Order>,
321 _timing: PhantomData<Timing>,
322 _color: PhantomData<C>,
323}
324
325/// A [`RmtSmartLeds`] for 8-bit RGB colors, which is what most smart LEDs use.
326///
327/// You still need to pick the `Order` of the three colors as well as the `Timing` and the `BUFFER_SIZE`.
328pub type Rgb8RmtSmartLeds<'d, const BUFFER_SIZE: usize, Mode, Order, Timing> =
329 RmtSmartLeds<'d, BUFFER_SIZE, Mode, RGB8, Order, Timing>;
330
331/// A [`RmtSmartLeds`] for the common WS2812 integrated smart LEDs.
332///
333/// You only need to pick the `BUFFER_SIZE` to use this.
334pub type Ws2812SmartLeds<'d, const BUFFER_SIZE: usize, Mode> =
335 Rgb8RmtSmartLeds<'d, BUFFER_SIZE, Mode, color_order::Grb, Ws2812Timing>;
336
337/// A [`RmtSmartLeds`] for integrated SK8612 (etc.) smart LEDs with RGBW.
338///
339/// You only need to pick the `BUFFER_SIZE` to use this.
340pub type Sk68xxRgbwSmartLeds<'d, const BUFFER_SIZE: usize, Mode> =
341 RmtSmartLeds<'d, BUFFER_SIZE, Mode, RGBW<u8>, color_order::Rgbw, Sk68xxTiming>;
342
343/// A [`RmtSmartLeds`] for smart LEDs with a single (white) channel.
344///
345/// You only need to pick the `BUFFER_SIZE` and `Timing` to use this.
346pub type WhiteSmartLeds<'d, const BUFFER_SIZE: usize, Mode, Timing> =
347 RmtSmartLeds<'d, BUFFER_SIZE, Mode, White<u8>, color_order::SingleChannel, Timing>;
348
349impl<'d, const BUFFER_SIZE: usize, Mode, C, Order, Timing>
350 RmtSmartLeds<'d, BUFFER_SIZE, Mode, C, Order, Timing>
351where
352 Mode: DriverMode,
353 C: Color,
354 Order: ColorOrder<C>,
355 Timing: crate::Timing,
356{
357 /// Creates a new [`RmtSmartLeds`] that drives the provided output using the given RMT channel.
358 ///
359 /// Note that calling this function usually requires you to specify the desired buffer size, [`ColorOrder`] and [`Timing`]. See the struct documentation for details.
360 ///
361 /// If you want to reuse the channel afterwards, you can use [`esp_hal::rmt::ChannelCreator::reborrow`] to create a shorter-lived derived channel.
362 ///
363 /// # Errors
364 ///
365 /// If any configuration issue with the RMT [`Channel`] occurs, the error will be returned.
366 pub fn new<Ch, P>(channel: Ch, pin: P) -> Result<Self, RmtError>
367 where
368 Ch: TxChannelCreator<'d, Mode>,
369 P: PeripheralOutput<'d>,
370 {
371 Self::new_with_memsize(channel, pin, 1)
372 }
373 /// Creates a new [`RmtSmartLeds`] that drives the provided output using the given RMT channel.
374 ///
375 /// Note that calling this function usually requires you to specify the desired buffer size, [`ColorOrder`] and [`Timing`]. See the struct documentation for details.
376 ///
377 /// If you want to reuse the channel afterwards, you can use [`esp_hal::rmt::ChannelCreator::reborrow`] to create a shorter-lived derived channel.
378 ///
379 /// The `memsize` parameter determines how many RMT blocks this adapter will use.
380 /// 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.
381 /// However, this can allow you to control many more LEDs without issues.
382 ///
383 /// # Errors
384 ///
385 /// If any configuration issue with the RMT [`Channel`] occurs, the error will be returned.
386 pub fn new_with_memsize<Ch, P>(channel: Ch, pin: P, memsize: u8) -> Result<Self, RmtError>
387 where
388 Ch: TxChannelCreator<'d, Mode>,
389 P: PeripheralOutput<'d>,
390 {
391 let config = TxChannelConfig::default()
392 .with_clk_divider(1)
393 .with_idle_output_level(Level::Low)
394 .with_memsize(memsize)
395 .with_carrier_modulation(false)
396 .with_idle_output(true);
397
398 let channel = channel.configure_tx(pin, config)?;
399
400 // Assume the RMT peripheral is set up to use the APB clock
401 let clocks = Clocks::get();
402 // convert to the MHz value to simplify nanosecond calculations
403 let src_clock = clocks.apb_clock.as_hz() / 1_000_000;
404
405 let zero_pulse = PulseCode::new(
406 Level::High,
407 ((Timing::TIME_0_HIGH as u32 * src_clock) / 1000) as u16,
408 Level::Low,
409 ((Timing::TIME_0_LOW as u32 * src_clock) / 1000) as u16,
410 );
411 let mut rmt_buffer = [zero_pulse; _];
412 rmt_buffer[BUFFER_SIZE - 1] = PulseCode::end_marker();
413 Ok(Self {
414 channel: Some(channel),
415 rmt_buffer,
416 pulses: (
417 zero_pulse,
418 PulseCode::new(
419 Level::High,
420 ((Timing::TIME_1_HIGH as u32 * src_clock) / 1000) as u16,
421 Level::Low,
422 ((Timing::TIME_1_LOW as u32 * src_clock) / 1000) as u16,
423 ),
424 ),
425 _order: PhantomData,
426 _timing: PhantomData,
427 _color: PhantomData,
428 })
429 }
430
431 /// Create and store RMT data from the color information provided.
432 fn create_rmt_data(
433 &mut self,
434 iterator: impl IntoIterator<Item = impl Into<C>>,
435 ) -> Result<(), AdapterError> {
436 // We always start from the beginning of the buffer
437 let mut seq_iter = self.rmt_buffer.iter_mut();
438
439 // Add all converted iterator items to the buffer.
440 // This will result in an `BufferSizeExceeded` error in case
441 // the iterator provides more elements than the buffer can take.
442 for item in iterator {
443 convert_colors_to_pulse::<_, Order>(&item.into(), &mut seq_iter, self.pulses)?;
444 }
445
446 // Finally, add an end element.
447 *seq_iter.next().ok_or(AdapterError::BufferSizeExceeded)? = PulseCode::end_marker();
448
449 Ok(())
450 }
451
452 /// Write pixel buffer data at certain LED index.
453 /// Does not actually write data to the RMT peripheral.
454 #[allow(unused)]
455 pub(crate) fn write_pixel_data(
456 &mut self,
457 index: usize,
458 color: impl Into<C>,
459 ) -> Result<(), AdapterError> {
460 let buffer_start_index = index * C::CHANNELS as usize * (size_of::<C::ChannelType>() * 8);
461 let mut buffer_iter = self
462 .rmt_buffer
463 .get_mut(buffer_start_index..)
464 .ok_or(AdapterError::BufferSizeExceeded)?
465 .iter_mut();
466 convert_colors_to_pulse::<_, Order>(&color.into(), &mut buffer_iter, self.pulses)
467 }
468}
469
470impl<'d, const BUFFER_SIZE: usize, C, Order, Timing>
471 RmtSmartLeds<'d, BUFFER_SIZE, Blocking, C, Order, Timing>
472where
473 C: Color,
474 Order: ColorOrder<C>,
475 Timing: crate::Timing,
476{
477 /// Transmit existing LED data via the RMT peripheral.
478 pub fn flush(&mut self) -> Result<(), AdapterError> {
479 // Perform the actual RMT operation. We use the u32 values here right away.
480 let channel = self.channel.take().unwrap();
481 // TODO: If the transmit fails, we’re in an unsafe state and future calls to write() will panic.
482 // This is currently unavoidable since transmit consumes the channel on error.
483 // This is a known design flaw in the current RMT API and will be fixed soon.
484 // We should adjust our usage accordingly as soon as possible.
485 match channel.transmit(&self.rmt_buffer)?.wait() {
486 Ok(chan) => {
487 self.channel = Some(chan);
488 Ok(())
489 }
490 Err((e, chan)) => {
491 self.channel = Some(chan);
492 Err(AdapterError::TransmissionError(e))
493 }
494 }
495 }
496}
497
498impl<'d, const BUFFER_SIZE: usize, C, Order, Timing> SmartLedsWrite
499 for RmtSmartLeds<'d, BUFFER_SIZE, Blocking, C, Order, Timing>
500where
501 C: Color,
502 Order: ColorOrder<C>,
503 Timing: crate::Timing,
504{
505 type Error = AdapterError;
506 type Color = C;
507
508 /// Convert all Color items of the iterator to the RMT format and
509 /// add them to internal buffer, then start a singular RMT operation
510 /// based on that buffer.
511 fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
512 where
513 T: IntoIterator<Item = I>,
514 I: Into<Self::Color>,
515 {
516 self.create_rmt_data(iterator)?;
517 self.flush()
518 }
519}
520
521impl<'d, const BUFFER_SIZE: usize, C, Order, Timing> SmartLedsWriteAsync
522 for RmtSmartLeds<'d, BUFFER_SIZE, Async, C, Order, Timing>
523where
524 C: Color,
525 Order: ColorOrder<C>,
526 Timing: crate::Timing,
527{
528 type Error = AdapterError;
529 type Color = C;
530
531 /// Convert all Color items of the iterator to the RMT format and
532 /// add them to internal buffer, then start a singular RMT operation
533 /// based on that buffer.
534 fn write<T, I>(&mut self, iterator: T) -> impl Future<Output = Result<(), Self::Error>>
535 where
536 T: IntoIterator<Item = I>,
537 I: Into<Self::Color>,
538 {
539 // we split the future into a creation part and a sending part
540 // so we can prepare multiple futures and send/await then all at the same time
541 let res = self.create_rmt_data(iterator);
542
543 async move {
544 res?;
545 // Perform the actual RMT operation. We use the u32 values here right away.
546 self.channel
547 .as_mut()
548 .unwrap()
549 .transmit(&self.rmt_buffer)
550 .await?;
551 Ok(())
552 }
553 }
554}
555
556fn convert_colors_to_pulse<'a, C, Order>(
557 value: &C,
558 mut_iter: &mut impl Iterator<Item = &'a mut PulseCode>,
559 pulses: (PulseCode, PulseCode),
560) -> Result<(), AdapterError>
561where
562 C: Color,
563 Order: ColorOrder<C>,
564{
565 for channel in 0..C::CHANNELS {
566 convert_channel_to_pulses(Order::get_channel_data(&value, channel), mut_iter, pulses)?;
567 }
568
569 Ok(())
570}
571
572fn convert_channel_to_pulses<'a, N>(
573 channel_value: N,
574 mut_iter: &mut impl Iterator<Item = &'a mut PulseCode>,
575 pulses: (PulseCode, PulseCode),
576) -> Result<(), AdapterError>
577where
578 N: Unsigned + Into<usize>,
579{
580 let channel_value: usize = channel_value.into();
581 for index in (0..size_of::<N>() * 8).rev() {
582 let position = 1 << index;
583 *mut_iter.next().ok_or(AdapterError::BufferSizeExceeded)? = match channel_value & position {
584 0 => pulses.0,
585 _ => pulses.1,
586 }
587 }
588
589 Ok(())
590}