blinksy_esp/
rmt.rs

1//! # RMT-based LED Driver
2//!
3//! This module provides a driver for clockless LED protocols (like WS2812)
4//! using the ESP32's RMT (Remote Control Module) peripheral. The RMT
5//! peripheral provides hardware acceleration for generating precisely timed
6//! signals, which is ideal for LED protocols.
7//!
8//! ## Features
9//!
10//! - Hardware-accelerated LED control
11//! - Precise timing for WS2812 and similar protocols
12//! - Blocking and async (feature "async") APIs with equivalent behavior
13//!
14//! ## Technical Details
15//!
16//! The RMT peripheral translates LED color data into a sequence of timed
17//! pulses that match the protocol requirements. This implementation converts
18//! each bit of color data into the corresponding high/low pulse durations
19//! required by the specific LED protocol.
20
21#[cfg(feature = "async")]
22use blinksy::driver::ClocklessWriterAsync;
23use blinksy::{
24    driver::{clockless::ClocklessLed, ClocklessWriter},
25    util::bits::{word_to_bits_msb, Word},
26};
27use core::{fmt::Debug, marker::PhantomData};
28#[cfg(feature = "async")]
29use esp_hal::Async;
30use esp_hal::{
31    clock::Clocks,
32    gpio::{interconnect::PeripheralOutput, Level},
33    rmt::{
34        Channel, Error as RmtError, PulseCode, Tx, TxChannelConfig, TxChannelCreator,
35        CHANNEL_RAM_SIZE,
36    },
37    Blocking, DriverMode,
38};
39use heapless::Vec;
40
41use crate::util::chunked;
42
43pub const fn rmt_buffer_size<Led: ClocklessLed>(pixel_count: usize) -> usize {
44    pixel_count * Led::LED_CHANNELS.channel_count() * 8 + 1
45}
46
47/// All types of errors that can happen during the conversion and transmission
48/// of LED commands
49#[derive(Debug)]
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
51pub enum ClocklessRmtError {
52    /// Raised in the event that the provided data container is not large enough
53    BufferSizeExceeded,
54    /// Raised if something goes wrong in the transmission
55    TransmissionError(RmtError),
56}
57
58pub struct ClocklessRmtBuilder<const RMT_BUFFER_SIZE: usize, Led, Chan, Pin> {
59    led: PhantomData<Led>,
60    channel: Chan,
61    pin: Pin,
62}
63
64impl Default for ClocklessRmtBuilder<CHANNEL_RAM_SIZE, (), (), ()> {
65    fn default() -> ClocklessRmtBuilder<CHANNEL_RAM_SIZE, (), (), ()> {
66        ClocklessRmtBuilder {
67            led: PhantomData,
68            channel: (),
69            pin: (),
70        }
71    }
72}
73
74impl<Led, Chan, Pin> ClocklessRmtBuilder<CHANNEL_RAM_SIZE, Led, Chan, Pin> {
75    pub fn with_rmt_buffer_size<const RMT_BUFFER_SIZE: usize>(
76        self,
77    ) -> ClocklessRmtBuilder<RMT_BUFFER_SIZE, Led, Chan, Pin> {
78        ClocklessRmtBuilder {
79            led: self.led,
80            channel: self.channel,
81            pin: self.pin,
82        }
83    }
84}
85
86impl<const RMT_BUFFER_SIZE: usize, Chan, Pin> ClocklessRmtBuilder<RMT_BUFFER_SIZE, (), Chan, Pin> {
87    pub fn with_led<Led>(self) -> ClocklessRmtBuilder<RMT_BUFFER_SIZE, Led, Chan, Pin> {
88        ClocklessRmtBuilder {
89            led: PhantomData,
90            channel: self.channel,
91            pin: self.pin,
92        }
93    }
94}
95
96impl<const RMT_BUFFER_SIZE: usize, Led, Pin> ClocklessRmtBuilder<RMT_BUFFER_SIZE, Led, (), Pin> {
97    pub fn with_channel<Chan>(
98        self,
99        channel: Chan,
100    ) -> ClocklessRmtBuilder<RMT_BUFFER_SIZE, Led, Chan, Pin> {
101        ClocklessRmtBuilder {
102            led: self.led,
103            channel,
104            pin: self.pin,
105        }
106    }
107}
108
109impl<const RMT_BUFFER_SIZE: usize, Led, Chan> ClocklessRmtBuilder<RMT_BUFFER_SIZE, Led, Chan, ()> {
110    pub fn with_pin<Pin>(self, pin: Pin) -> ClocklessRmtBuilder<RMT_BUFFER_SIZE, Led, Chan, Pin> {
111        ClocklessRmtBuilder {
112            led: self.led,
113            channel: self.channel,
114            pin,
115        }
116    }
117}
118
119impl<const RMT_BUFFER_SIZE: usize, Led, Chan, Pin>
120    ClocklessRmtBuilder<RMT_BUFFER_SIZE, Led, Chan, Pin>
121where
122    Led: ClocklessLed,
123    Led::Word: Word,
124{
125    pub fn build<'ch, Dm>(self) -> ClocklessRmt<RMT_BUFFER_SIZE, Led, Channel<'ch, Dm, Tx>>
126    where
127        Chan: TxChannelCreator<'ch, Dm>,
128        Pin: PeripheralOutput<'ch>,
129        Dm: DriverMode,
130    {
131        ClocklessRmt::new(self.channel, self.pin)
132    }
133}
134
135/// RMT-based driver for clockless LED protocols.
136///
137/// This driver uses the ESP32's RMT peripheral to generate precisely timed
138/// signals required by protocols like WS2812.
139///
140/// # Type Parameters
141///
142/// - `RMT_BUFFER_SIZE` - Size of the RMT buffer
143/// - `Led` - The LED protocol implementation (must implement ClocklessLed)
144/// - `TxChannel` - The RMT transmit channel
145pub struct ClocklessRmt<const RMT_BUFFER_SIZE: usize, Led, TxChannel>
146where
147    Led: ClocklessLed,
148{
149    led: PhantomData<Led>,
150    channel: Option<TxChannel>,
151    pulses: (PulseCode, PulseCode, PulseCode),
152}
153
154impl<const RMT_BUFFER_SIZE: usize, Led, TxChannel> ClocklessRmt<RMT_BUFFER_SIZE, Led, TxChannel>
155where
156    Led: ClocklessLed,
157    Led::Word: Word,
158{
159    fn clock_divider() -> u8 {
160        1
161    }
162
163    fn setup_pulses() -> (PulseCode, PulseCode, PulseCode) {
164        let clocks = Clocks::get();
165        let freq_hz = clocks.apb_clock.as_hz() / Self::clock_divider() as u32;
166        let freq_mhz = freq_hz / 1_000_000;
167
168        let t_0h = ((Led::T_0H.to_nanos() * freq_mhz) / 1_000) as u16;
169        let t_0l = ((Led::T_0L.to_nanos() * freq_mhz) / 1_000) as u16;
170        let t_1h = ((Led::T_1H.to_nanos() * freq_mhz) / 1_000) as u16;
171        let t_1l = ((Led::T_1L.to_nanos() * freq_mhz) / 1_000) as u16;
172        let t_reset = ((Led::T_RESET.to_nanos() * freq_mhz) / 1_000) as u16;
173
174        (
175            PulseCode::new(Level::High, t_0h, Level::Low, t_0l),
176            PulseCode::new(Level::High, t_1h, Level::Low, t_1l),
177            PulseCode::new(Level::Low, t_reset, Level::Low, 0),
178        )
179    }
180
181    fn frame_pulses<const FRAME_BUFFER_SIZE: usize>(
182        &self,
183        frame: Vec<Led::Word, FRAME_BUFFER_SIZE>,
184    ) -> impl Iterator<Item = PulseCode> {
185        let pulses = self.pulses;
186        frame.into_iter().flat_map(move |word| {
187            word_to_bits_msb(word).map(move |bit| match bit {
188                false => pulses.0,
189                true => pulses.1,
190            })
191        })
192    }
193}
194
195impl<'ch, const RMT_BUFFER_SIZE: usize, Led, Dm>
196    ClocklessRmt<RMT_BUFFER_SIZE, Led, Channel<'ch, Dm, Tx>>
197where
198    Led: ClocklessLed,
199    Led::Word: Word,
200    Dm: DriverMode,
201{
202    /// Create a new adapter object that drives the pin using the RMT channel.
203    ///
204    /// # Arguments
205    ///
206    /// - `channel` - RMT transmit channel creator
207    /// - `pin` - GPIO pin connected to the LED data line
208    ///
209    /// # Returns
210    ///
211    /// A configured ClocklessRmt instance
212    pub fn new<C, O>(channel: C, pin: O) -> Self
213    where
214        C: TxChannelCreator<'ch, Dm>,
215        O: PeripheralOutput<'ch>,
216    {
217        let config = TxChannelConfig::default()
218            .with_clk_divider(Self::clock_divider())
219            .with_idle_output_level(Level::Low)
220            .with_idle_output(true);
221        let channel = channel.configure_tx(pin, config).unwrap();
222        let pulses = Self::setup_pulses();
223
224        Self {
225            led: PhantomData,
226            channel: Some(channel),
227            pulses,
228        }
229    }
230}
231
232impl<'ch, const RMT_BUFFER_SIZE: usize, Led>
233    ClocklessRmt<RMT_BUFFER_SIZE, Led, Channel<'ch, Blocking, Tx>>
234where
235    Led: ClocklessLed,
236{
237    /// Transmit buffer using RMT, blocking.
238    ///
239    /// # Arguments
240    ///
241    /// - `buffer` - Buffer to be transmitted
242    ///
243    /// # Returns
244    ///
245    /// Result indicating success or an error
246    fn transmit_blocking(&mut self, buffer: &[PulseCode]) -> Result<(), ClocklessRmtError> {
247        let channel = self.channel.take().unwrap();
248        match channel.transmit(buffer).unwrap().wait() {
249            Ok(chan) => {
250                self.channel = Some(chan);
251                Ok(())
252            }
253            Err((e, chan)) => {
254                self.channel = Some(chan);
255                Err(ClocklessRmtError::TransmissionError(e))
256            }
257        }
258    }
259}
260
261#[cfg(feature = "async")]
262impl<'ch, const RMT_BUFFER_SIZE: usize, Led>
263    ClocklessRmt<RMT_BUFFER_SIZE, Led, Channel<'ch, Async, Tx>>
264where
265    Led: ClocklessLed,
266{
267    /// Transmit buffer using RMT, async.
268    ///
269    /// # Arguments
270    ///
271    /// - `buffer` - Buffer to be transmitted
272    ///
273    /// # Returns
274    ///
275    /// Result indicating success or an error
276    async fn transmit_async(&mut self, buffer: &[PulseCode]) -> Result<(), ClocklessRmtError> {
277        let channel = self.channel.as_mut().unwrap();
278        channel
279            .transmit(buffer)
280            .await
281            .map_err(ClocklessRmtError::TransmissionError)
282    }
283}
284
285impl<'ch, const RMT_BUFFER_SIZE: usize, Led> ClocklessWriter<Led>
286    for ClocklessRmt<RMT_BUFFER_SIZE, Led, Channel<'ch, Blocking, Tx>>
287where
288    Led: ClocklessLed,
289    Led::Word: Word,
290{
291    type Error = ClocklessRmtError;
292
293    fn write<const FRAME_BUFFER_SIZE: usize>(
294        &mut self,
295        frame: Vec<Led::Word, FRAME_BUFFER_SIZE>,
296    ) -> Result<(), Self::Error> {
297        let rmt_pulses = self.frame_pulses(frame);
298        for mut rmt_buffer in chunked::<_, RMT_BUFFER_SIZE>(rmt_pulses, RMT_BUFFER_SIZE - 1) {
299            // RMT buffer must end with 0.
300            rmt_buffer.push(PulseCode::end_marker()).unwrap();
301            self.transmit_blocking(&rmt_buffer)?;
302        }
303
304        Ok(())
305    }
306}
307
308#[cfg(feature = "async")]
309impl<'ch, const RMT_BUFFER_SIZE: usize, Led> ClocklessWriterAsync<Led>
310    for ClocklessRmt<RMT_BUFFER_SIZE, Led, Channel<'ch, Async, Tx>>
311where
312    Led: ClocklessLed,
313    Led::Word: Word,
314{
315    type Error = ClocklessRmtError;
316
317    async fn write<const FRAME_BUFFER_SIZE: usize>(
318        &mut self,
319        frame: Vec<Led::Word, FRAME_BUFFER_SIZE>,
320    ) -> Result<(), Self::Error> {
321        let rmt_pulses = self.frame_pulses(frame);
322        for mut rmt_buffer in chunked::<_, RMT_BUFFER_SIZE>(rmt_pulses, RMT_BUFFER_SIZE - 1) {
323            // RMT buffer must end with 0.
324            rmt_buffer.push(PulseCode::end_marker()).unwrap();
325            self.transmit_async(&rmt_buffer).await?;
326        }
327
328        Ok(())
329    }
330}