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}