Skip to main content

esp_idf_hal/rmt/
tx_channel.rs

1use core::fmt;
2use core::marker::PhantomData;
3use core::mem;
4use core::ptr;
5use core::sync::atomic::{AtomicUsize, Ordering};
6use core::time::Duration;
7
8use alloc::boxed::Box;
9use esp_idf_sys::*;
10
11use crate::gpio::OutputPin;
12use crate::interrupt::asynch::HalIsrNotification;
13use crate::rmt::config::{Loop, TransmitConfig, TxChannelConfig};
14use crate::rmt::encoder::{into_raw, Encoder, RawEncoder};
15use crate::rmt::tx_queue::TxQueue;
16use crate::rmt::TxDoneEventData;
17use crate::rmt::{assert_not_in_isr, EncoderBuffer, RmtChannel};
18
19struct UserData<'d> {
20    callback: Option<Box<dyn FnMut(TxDoneEventData) + Send + 'd>>,
21    queue_size: AtomicUsize,
22    queue_has_progressed: HalIsrNotification,
23}
24
25pub struct TxChannelDriver<'d> {
26    // SAFETY: The unsafe code relies on this field to accurately reflect the channel state.
27    //         It relies on the fact that in disabled state, the ISR handler will not be called.
28    is_enabled: bool,
29    handle: rmt_channel_handle_t,
30    on_transmit_data: Box<UserData<'d>>,
31    _p: PhantomData<&'d mut ()>,
32}
33
34impl<'d> TxChannelDriver<'d> {
35    const TX_EVENT_CALLBACKS: rmt_tx_event_callbacks_t = rmt_tx_event_callbacks_t {
36        on_trans_done: Some(Self::handle_isr),
37    };
38    const TX_EVENT_CALLBACKS_DISABLE: rmt_tx_event_callbacks_t = rmt_tx_event_callbacks_t {
39        on_trans_done: None,
40    };
41
42    /// Creates a new RMT TX channel.
43    ///
44    /// # Note
45    ///
46    /// When multiple RMT channels are allocated at the same time,
47    /// the group’s prescale is determined based on the resolution of
48    /// the first channel. The driver then selects the appropriate prescale
49    /// from low to high. To avoid prescale conflicts when allocating multiple
50    /// channels, allocate channels in order of their target resolution,
51    /// either from highest to lowest or lowest to highest.
52    ///
53    /// # Panics
54    ///
55    /// This function will panic if called from an ISR context.
56    pub fn new(pin: impl OutputPin + 'd, config: &TxChannelConfig) -> Result<Self, EspError> {
57        assert_not_in_isr();
58
59        let sys_config: rmt_tx_channel_config_t = rmt_tx_channel_config_t {
60            clk_src: config.clock_source.into(),
61            resolution_hz: config.resolution.into(),
62            mem_block_symbols: config.memory_access.symbols(),
63            trans_queue_depth: config.transaction_queue_depth,
64            #[cfg(esp_idf_version_at_least_5_1_2)]
65            intr_priority: config.interrupt_priority,
66            flags: rmt_tx_channel_config_t__bindgen_ty_1 {
67                _bitfield_1: rmt_tx_channel_config_t__bindgen_ty_1::new_bitfield_1(
68                    config.invert_out as u32,
69                    config.memory_access.is_direct() as u32,
70                    #[cfg(not(esp_idf_version_at_least_6_0_0))]
71                    {
72                        config.io_loop_back as u32
73                    },
74                    #[cfg(not(esp_idf_version_at_least_6_0_0))]
75                    {
76                        config.io_od_mode as u32
77                    },
78                    #[cfg(esp_idf_version_at_least_5_4_0)]
79                    {
80                        config.allow_pd as u32
81                    },
82                    // `init_level`
83                    #[cfg(any(
84                        esp_idf_version_patch_at_least_5_3_5,
85                        esp_idf_version_patch_at_least_5_4_3,
86                        esp_idf_version_at_least_5_5_2,
87                        esp_idf_version_at_least_6_0_0
88                    ))]
89                    {
90                        0
91                    },
92                ),
93                ..Default::default()
94            },
95            gpio_num: pin.pin() as _,
96        };
97        let mut handle: rmt_channel_handle_t = ptr::null_mut();
98        esp!(unsafe { rmt_new_tx_channel(&sys_config, &mut handle) })?;
99
100        #[cfg_attr(not(feature = "alloc"), allow(unused_mut))]
101        let mut this = Self {
102            is_enabled: false,
103            handle,
104            on_transmit_data: Box::new(UserData {
105                callback: None,
106                queue_size: AtomicUsize::new(0),
107                queue_has_progressed: HalIsrNotification::new(),
108            }),
109            _p: PhantomData,
110        };
111
112        // The callback is used to detect when a transmission is finished.
113        esp!(unsafe {
114            rmt_tx_register_event_callbacks(
115                handle,
116                &Self::TX_EVENT_CALLBACKS,
117                (&raw mut *this.on_transmit_data) as *mut core::ffi::c_void,
118            )
119        })?;
120
121        Ok(this)
122    }
123
124    /// Wait for all pending TX transactions to finish.
125    ///
126    /// If `timeout` is `None`, it will wait indefinitely. If `timeout` is `Some(duration)`,
127    /// it will wait for at most `duration`.
128    ///
129    /// # Note
130    ///
131    /// This function will block forever if the pending transaction can't
132    /// be finished within a limited time (e.g. an infinite loop transaction).
133    /// See also [`Self::disable`] for how to terminate a working channel.
134    ///
135    /// If the given `timeout` converted to milliseconds is larger than `i32::MAX`,
136    /// it will be treated as `None` (wait indefinitely).
137    ///
138    /// # Errors
139    ///
140    /// - `ESP_ERR_INVALID_ARG`: Flush transactions failed because of invalid argument
141    /// - `ESP_FAIL`: Flush transactions failed because of other error
142    ///
143    /// # Polling
144    ///
145    /// When polling this function (calling with a timeout duration of 0ms),
146    /// esp-idf will log flush timeout errors to the console.
147    /// This issue is tracked in <https://github.com/espressif/esp-idf/issues/17527>
148    /// and should be fixed in future esp-idf versions.
149    pub fn wait_all_done(&mut self, timeout: Option<Duration>) -> Result<(), EspError> {
150        esp!(unsafe {
151            rmt_tx_wait_all_done(
152                self.handle,
153                timeout.map_or(-1, |duration| duration.as_millis().try_into().unwrap_or(-1)),
154            )
155        })
156    }
157
158    /// Define the ISR handler for when a transmission is done.
159    ///
160    /// The callback will be called with the number of transmitted symbols, including one EOF symbol,
161    /// which is appended by the driver to mark the end of the transmission. For a loop transmission,
162    /// this value only counts for one round.
163    ///
164    /// There is only one callback possible, you can not subscribe multiple callbacks.
165    ///
166    /// # Panics
167    ///
168    /// This function will panic if called from an ISR context or while the channel is enabled.
169    ///
170    /// # ISR Safety
171    ///
172    /// Care should be taken not to call std, libc or FreeRTOS APIs (except for a few allowed ones)
173    /// in the callback passed to this function, as it is executed in an ISR context.
174    ///
175    /// You are not allowed to block, but you are allowed to call FreeRTOS APIs with the FromISR suffix.
176    pub fn subscribe(&mut self, callback: impl FnMut(TxDoneEventData) + Send + 'static) {
177        // SAFETY: because of 'static lifetime, it doesn't matter if mem::forget is called on the driver
178        unsafe {
179            self.subscribe_nonstatic(callback);
180        }
181    }
182
183    /// Subscribe a non-'static callback for when a transmission is done.
184    ///
185    /// # Safety
186    ///
187    /// You must not forget the channel driver (for example through [`mem::forget`]),
188    /// while the callback is still subscribed, otherwise this would lead to undefined behavior.
189    pub unsafe fn subscribe_nonstatic(
190        &mut self,
191        callback: impl FnMut(TxDoneEventData) + Send + 'd,
192    ) {
193        assert_not_in_isr();
194        if self.is_enabled() {
195            panic!("Can't subscribe while the channel is enabled");
196        }
197
198        self.on_transmit_data.callback = Some(Box::new(callback));
199    }
200
201    /// Remove the ISR handler for when a transmission is done.
202    ///
203    /// # Panics
204    ///
205    /// This function will panic if called from an ISR context or while the channel is enabled.
206    pub fn unsubscribe(&mut self) {
207        assert_not_in_isr();
208        if self.is_enabled() {
209            panic!("Can't unsubscribe while the channel is enabled");
210        }
211
212        self.on_transmit_data.callback = None;
213    }
214
215    /// Handles the ISR event for when a transmission is done.
216    unsafe extern "C" fn handle_isr(
217        _channel: rmt_channel_handle_t,
218        event_data: *const rmt_tx_done_event_data_t,
219        user_data: *mut core::ffi::c_void,
220    ) -> bool {
221        let event_data = TxDoneEventData::from(event_data.read());
222        let user_data = &mut *(user_data as *mut UserData<'d>);
223
224        user_data.queue_size.fetch_sub(1, Ordering::SeqCst);
225        if let Some(handler) = user_data.callback.as_mut() {
226            handler(event_data);
227        }
228
229        user_data.queue_has_progressed.notify_lsb()
230    }
231
232    /// Starts transmitting the signal using the specified encoder and config.
233    ///
234    /// # Safety
235    ///
236    /// This function is a thin wrapper around the `rmt_transmit` function, it assumes that
237    /// - the encoder (the returned pointer of [`RawEncoder::handle`]) is valid until the transmission
238    ///   is done, if not, it is guaranteed to crash
239    /// - the signal is valid until the transmission is done
240    /// - the encoder and signal are not modified during the transmission
241    ///
242    /// The caller must ensure that the encoder and signal **live long enough** and are **not moved**.
243    pub unsafe fn start_send<E: RawEncoder>(
244        &mut self,
245        encoder: &mut E,
246        signal: &[E::Item],
247        config: &TransmitConfig,
248    ) -> Result<(), EspError> {
249        if !self.is_enabled() {
250            self.enable()?;
251        }
252
253        let sys_config = rmt_transmit_config_t {
254            loop_count: match config.loop_count {
255                Loop::Count(value) => value as i32,
256                Loop::Endless => -1,
257                Loop::None => 0,
258            },
259            flags: rmt_transmit_config_t__bindgen_ty_1 {
260                _bitfield_1: rmt_transmit_config_t__bindgen_ty_1::new_bitfield_1(
261                    config.eot_level as u32,
262                    #[cfg(esp_idf_version_at_least_5_1_3)]
263                    {
264                        config.queue_non_blocking as u32
265                    },
266                ),
267                ..Default::default()
268            },
269        };
270
271        esp!(unsafe {
272            rmt_transmit(
273                self.handle(),
274                encoder.handle(),
275                signal.as_ptr() as *const core::ffi::c_void,
276                // size should be given in bytes:
277                mem::size_of_val::<[E::Item]>(signal),
278                &sys_config,
279            )
280        })?;
281
282        self.on_transmit_data
283            .queue_size
284            .fetch_add(1, Ordering::SeqCst);
285
286        Ok(())
287    }
288
289    /// Transmits the signals provided by the iterator using the specified encoder and config.
290    ///
291    /// This is a convenience function that will create a [`TxQueue`], push all signals from the iterator
292    /// to the queue, and then drop the queue, waiting for all transmissions to finish.
293    ///
294    /// # Non blocking behavior
295    ///
296    /// It is not recommended to use this function with [`TransmitConfig::queue_non_blocking`] set to true,
297    /// because it will drop the queue if it would block, resulting in it blocking until all pending transmissions
298    /// are done.
299    ///
300    /// Therefore, one should use [`TxChannelDriver::queue`] for a non-blocking use case.
301    pub fn send_iter<E: Encoder, S: AsRef<[E::Item]>>(
302        &mut self,
303        encoders: impl IntoIterator<Item = E>,
304        iter: impl Iterator<Item = S>,
305        config: &TransmitConfig,
306    ) -> Result<(), EspError>
307    where
308        E::Item: Clone,
309    {
310        let mut pending = TxQueue::new(
311            encoders
312                .into_iter()
313                .map(|encoder| EncoderBuffer::new(into_raw(encoder)))
314                .collect(),
315            self,
316        );
317
318        for signal in iter {
319            pending.push(signal.as_ref(), config)?;
320        }
321
322        // The remaining pending transmissions will be awaited in the drop of the queue.
323
324        Ok(())
325    }
326
327    /// Creates a new queue for transmitting multiple signals with the given encoders.
328    ///
329    /// For more information, see [`TxQueue`].
330    ///
331    /// # Panics
332    ///
333    /// If no encoders are provided.
334    #[must_use]
335    pub fn queue<E: Encoder>(
336        &mut self,
337        encoders: impl IntoIterator<Item = E>,
338    ) -> TxQueue<'_, 'd, E> {
339        TxQueue::new(
340            encoders
341                .into_iter()
342                .map(|encoder| EncoderBuffer::new(into_raw(encoder)))
343                .collect(),
344            self,
345        )
346    }
347
348    /// Asynchronously waits until the next pending transmission has finished.
349    ///
350    /// If there are no pending transmissions, this function will wait indefinitely.
351    pub async fn wait_for_progress(&self) {
352        self.on_transmit_data.queue_has_progressed.wait().await;
353    }
354
355    /// Returns the number of currently pending transmissions.
356    ///
357    /// This will be updated when a transmission is started or finished.
358    pub fn queue_size(&self) -> usize {
359        self.on_transmit_data.queue_size.load(Ordering::SeqCst)
360    }
361
362    /// Transmits the signal and waits for the transmission to finish.
363    ///
364    /// If the channel is not enabled yet, it will be enabled automatically.
365    ///
366    /// # Queue blocking behavior
367    ///
368    /// This function constructs a transaction descriptor then pushes to a queue. The transaction
369    /// will not start immediately if there's another one under processing. Based on the setting
370    /// of [`TransmitConfig::queue_non_blocking`], if there're too many transactions pending in the
371    /// queue, this function can block until it has free slot, otherwise just return quickly.
372    ///
373    /// # Errors
374    ///
375    /// - `ESP_ERR_INVALID_ARG`: Transmit failed because of invalid argument
376    /// - `ESP_ERR_NOT_SUPPORTED`: Some feature is not supported by hardware e.g. unsupported loop count
377    /// - `ESP_FAIL`: Because of other errors
378    pub fn send_and_wait<E: Encoder>(
379        &mut self,
380        encoder: E,
381        signal: &[E::Item],
382        config: &TransmitConfig,
383    ) -> Result<(), EspError>
384    where
385        E::Item: Clone,
386    {
387        self.send_iter([encoder], core::iter::once(signal), config)
388    }
389}
390
391// SAFETY: The C code doesn't seem to use any thread locals -> it should be safe to send the channel to another thread.
392unsafe impl<'d> Send for TxChannelDriver<'d> {}
393
394// SAFETY: All non-thread-safe methods require exclusive access to self
395// -> it is safe to send &TxChannelDriver to another thread.
396// -> it is safe to implement Sync
397unsafe impl<'d> Sync for TxChannelDriver<'d> {}
398
399impl<'d> RmtChannel for TxChannelDriver<'d> {
400    fn handle(&self) -> rmt_channel_handle_t {
401        self.handle
402    }
403
404    fn is_enabled(&self) -> bool {
405        self.is_enabled
406    }
407
408    unsafe fn set_internal_enabled(&mut self, is_enabled: bool) {
409        self.is_enabled = is_enabled;
410
411        // If the channel was disabled, all pending transmissions are cancelled
412        if !self.is_enabled {
413            self.on_transmit_data.queue_size.store(0, Ordering::SeqCst);
414            self.on_transmit_data.queue_has_progressed.reset();
415        }
416    }
417}
418
419impl<'d> Drop for TxChannelDriver<'d> {
420    fn drop(&mut self) {
421        // Deleting the channel might fail if it is not disabled first.
422        //
423        // The result is ignored here, because there is nothing we can do about it.
424        if self.is_enabled() {
425            let _res = self.disable();
426        }
427
428        // SAFETY: The disable will cancel all pending transmission -> the stored data can be freed
429
430        // Remove the isr handler:
431        let _res = unsafe {
432            rmt_tx_register_event_callbacks(
433                self.handle,
434                &Self::TX_EVENT_CALLBACKS_DISABLE,
435                ptr::null_mut(),
436            )
437        };
438
439        unsafe { rmt_del_channel(self.handle) };
440    }
441}
442
443impl<'d> fmt::Debug for TxChannelDriver<'d> {
444    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445        f.debug_struct("TxChannelDriver")
446            .field("is_enabled", &self.is_enabled)
447            .field("handle", &self.handle)
448            .finish()
449    }
450}