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}