Skip to main content

esp_idf_hal/rmt/
tx_queue.rs

1use core::fmt;
2
3use alloc::vec::Vec;
4
5use alloc::collections::VecDeque;
6use esp_idf_sys::{EspError, ESP_ERR_TIMEOUT};
7
8use super::tx_channel::TxChannelDriver;
9use crate::rmt::config::TransmitConfig;
10use crate::rmt::encoder::{Encoder, EncoderWrapper, RawEncoder};
11
12// What kind of signal is necessary for the TxQueue to be safe?
13//
14// - From the signal, one should be able to get a pointer and a length to the buffer,
15//   e.g. as_ref(&self) -> &[E::Item], from which one could get the ptr and len.
16// - The signal must be guaranteed to be valid until the transmission is done,
17//   this would be the same lifetime for which the TxQueue is valid.
18// - The TxQueue will move the signal around, with some types like [E::Item; N] this would
19//   invalidate the pointer to the data
20//
21// What are the options?
22// - Only allow &'a [E::Item] where 'a is the lifetime of the TxQueue (assuming no mem::forget) or 'a = 'static
23//   if mem::forget is used. This has the downside that one can not pass owned data like Vec or Box.
24// - Keep an owned buffer like Vec<E::Item> or Box<[E::Item]> for each encoder, then clone the passed signal into it.
25//   The benefits are that the user can pass any kind of signal that implements AsRef<[E::Item]>, but it would require
26//   an initial allocation, copy the signal into the internal buffer, and E::Item would have to be Clone.
27// - Implementing a custom queue where the stored elements are not moved until they are removed from the queue.
28//   This is the likely the most performant option, but will require a lot of code to implement a custom queue
29//   and might perform worse than the other options (another issue is fragmentation).
30// - Define an unsafe trait that is implemented for types that are safe to move around like &[E::Item],
31//   Vec<E::Item>, Box<[E::Item]>, ...
32//   This allows the user to pass both owned and borrowed data. A disadvantage in comparison to the internal buffers
33//   is that the user has to ensure that the data is valid until the transmission is done, and it might perform worse
34//   when the buffers are not reused.
35
36#[derive(Debug)]
37pub(crate) struct EncoderBuffer<E: RawEncoder> {
38    encoder: E,
39    buffer: Vec<E::Item>,
40}
41
42impl<E: RawEncoder> EncoderBuffer<E> {
43    pub fn new(encoder: E) -> Self {
44        Self {
45            encoder,
46            buffer: Vec::new(),
47        }
48    }
49
50    /// Updates the internal buffer to contain the given signal.
51    ///
52    /// It returns a mutable reference to the stored encoder and a reference to the now
53    /// internally stored signal.
54    fn update_from_slice(&mut self, signal: &[E::Item]) -> (&mut E, &[E::Item])
55    where
56        E::Item: Clone,
57    {
58        // Ensure that the buffer is large enough to hold the signal.
59        //
60        // The value will be overwritten anyway, so we can just clone the first element
61        // to resize the buffer.
62        self.buffer.resize(signal.len(), signal[0].clone());
63
64        // Copy the signal into the buffer, ensuring that the signal will be valid for the entire
65        // transmission.
66        self.buffer[..signal.len()].clone_from_slice(signal);
67
68        (&mut self.encoder, &self.buffer[..signal.len()])
69    }
70}
71
72/// A queue to efficiently transmit multiple signals, reusing encoders when possible.
73///
74/// The driver has an internal queue from which it will copy the signals into the peripheral
75/// memory. The signal and encoder must be valid until the transmission is done,
76/// which can not be guaranteed when directly using the queue through [`TxChannelDriver::start_send`].
77///
78/// This type provides a safe interface to fill up the queue, reusing encoders when possible.
79/// To do this, it keeps an allocated buffer for each encoder, to which the signal will be copied
80/// before starting the transmission. This ensures that both the signal and encoder are valid
81/// until the transmission is done.
82///
83/// # Drop behavior
84///
85/// When the `TxQueue` is dropped, it will wait for all transmissions to finish.
86/// This ensures that the internal buffers are not dropped while they are still in use
87/// by the peripheral.
88pub struct TxQueue<'c, 'd, E: Encoder> {
89    queue: VecDeque<EncoderBuffer<EncoderWrapper<E>>>,
90    channel: &'c mut TxChannelDriver<'d>,
91}
92
93impl<'c, 'd, E: Encoder> TxQueue<'c, 'd, E> {
94    pub(crate) fn new(
95        queue: VecDeque<EncoderBuffer<EncoderWrapper<E>>>,
96        channel: &'c mut TxChannelDriver<'d>,
97    ) -> Self {
98        assert!(
99            !queue.is_empty(),
100            "At least one encoder is required to encode a TxQueue"
101        );
102
103        Self { queue, channel }
104    }
105
106    /// Returns a mutable reference to the channel this queue is using.
107    #[must_use]
108    pub fn channel(&mut self) -> &mut TxChannelDriver<'d> {
109        self.channel
110    }
111}
112
113impl<'c, 'd, E: Encoder> TxQueue<'c, 'd, E> {
114    /// Pushes a signal onto the transmission queue.
115    ///
116    /// The signal will be cloned into an internal buffer to ensure that it is valid for the entire
117    /// transmission. These buffers will be reused for future transmissions after the current transmission
118    /// is done.
119    ///
120    /// # Blocking behavior
121    ///
122    /// If the queue is full (i.e. all encoders are busy), this function will behave differently depending
123    /// on whether [`TransmitConfig::queue_non_blocking`] is set or not:
124    /// - If `queue_non_blocking` is `false`, it will block until one encoder is available.
125    /// - If `queue_non_blocking` is `true`, it will return an error with code `ESP_ERR_TIMEOUT`.
126    ///   There will be no state change, so the function can be called again immediately.
127    ///
128    /// If this queue has more encoders available than the channel queue size and `queue_non_blocking` is set,
129    /// it will copy the signal into the internal buffer, but might fail (with a timeout) to start the
130    /// transmission if the channel queue is full.
131    ///
132    /// # Panics
133    ///
134    /// If the signal is empty.
135    pub fn push(&mut self, signal: &[E::Item], config: &TransmitConfig) -> Result<(), EspError>
136    where
137        E::Item: Clone,
138    {
139        assert!(!signal.is_empty(), "Can not send an empty signal");
140
141        // Before a new transmission can be started, an encoder has to be available.
142        //
143        // Assuming the TxQueue has N encoders, it can immediately start sending if there are less than
144        // N transmissions in progress on the channel.
145        //
146        // If there are N transmissions or more in progress, it would have to wait until there are less than
147        // N transmissions in progress to guarantee that one encoder is available.
148        while self.channel.queue_size() >= self.queue.len() {
149            // If we should not block, replicate the error from esp-idf's send function
150            if config.queue_non_blocking {
151                return Err(EspError::from_infallible::<ESP_ERR_TIMEOUT>());
152            }
153
154            // This waits for a transmission to finish
155            crate::task::block_on(self.channel.wait_for_progress());
156        }
157
158        // This returns the next encoder and the buffer of the encoder.
159        //
160        // With the above waiting, it should be guaranteed that one encoder is available
161        // which would be the first one in the queue (they are ordered from oldest first to newest last).
162        let (next_encoder, buffer) = self
163            .queue
164            .front_mut()
165            .expect("queue should never be empty")
166            .update_from_slice(signal);
167
168        unsafe { self.channel.start_send(next_encoder, buffer, config) }?;
169
170        // If the channel queue is shorter than the number of encoders in this TxQueue,
171        // and the non-blocking flag is set, it could happen that the start_send errors.
172        //
173        // To prevent loosing an encoder and its buffer, it will only be removed from
174        // the front of the queue after a successful start_send.
175        let buffer = self.queue.pop_front().unwrap();
176        self.queue.push_back(buffer);
177
178        Ok(())
179    }
180}
181
182impl<'c, 'd, E: Encoder> Drop for TxQueue<'c, 'd, E> {
183    fn drop(&mut self) {
184        // This ensures that all transmissions are done before the internal buffers are dropped.
185        let _ = self.channel.wait_all_done(None);
186    }
187}
188
189impl<'c, 'd, E: Encoder> fmt::Debug for TxQueue<'c, 'd, E>
190where
191    E: fmt::Debug,
192    E::Item: fmt::Debug,
193{
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        f.debug_struct("TxQueue")
196            .field("queue", &self.queue)
197            .field("channel", &self.channel)
198            .finish()
199    }
200}