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}