esp_idf_hal/rmt/encoder/simple_encoder.rs
1//! The [`SimpleEncoder`] provides a simpler interface than the [`Encoder`](crate::rmt::encoder::Encoder)
2//! to implement a custom RMT encoder.
3//!
4//! The encoder will take care of allocating a target buffer for the output symbols
5//! and will call a user-provided callback ([`EncoderCallback`]) to fill the buffer with symbols.
6
7use core::ffi::c_void;
8use core::{mem, ptr, slice};
9
10use alloc::boxed::Box;
11
12use esp_idf_sys::*;
13
14use crate::rmt::encoder::RawEncoder;
15use crate::rmt::Symbol;
16
17/// The encoder was unable to encode all the input data into the provided buffer,
18/// it might have encoded some data (or it didn't), but there remains more to encode.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct NotEnoughSpace;
21
22/// Configuration for the [`SimpleEncoder`].
23#[derive(Debug, Clone)]
24pub struct SimpleEncoderConfig {
25 /// Minimum amount of free space, in RMT symbols, the encoder needs in order
26 /// to guarantee it always returns non-zero. Defaults to 64 if zero / not given.
27 pub min_chunk_size: usize,
28 // This field is intentionally hidden to prevent non-exhaustive pattern matching.
29 // You should only construct this struct using the `..Default::default()` pattern.
30 // If you use this field directly, your code might break in future versions.
31 #[doc(hidden)]
32 #[allow(dead_code)]
33 pub __internal: (),
34}
35
36impl Default for SimpleEncoderConfig {
37 fn default() -> Self {
38 Self {
39 min_chunk_size: 64,
40 __internal: (),
41 }
42 }
43}
44
45/// A helper to write symbols into a buffer, tracking how many symbols were written.
46#[derive(Debug)]
47pub struct SymbolBuffer<'a> {
48 symbols: &'a mut [Symbol],
49 written: usize,
50}
51
52impl<'a> SymbolBuffer<'a> {
53 /// Constructs a `SymbolBuffer` from its raw parts.
54 ///
55 /// # Safety
56 ///
57 /// You must ensure that the provided pointer is valid for both reads and writes, not null,
58 /// and properly aligned to construct a slice from it.
59 ///
60 /// The `written` parameter must not exceed the length of the slice.
61 /// The `free` parameter must be such that `written + free` does not exceed the length of the
62 /// slice.
63 #[must_use]
64 pub unsafe fn from_raw_parts(
65 symbols: *mut rmt_symbol_word_t,
66 written: usize,
67 free: usize,
68 ) -> Self {
69 let symbols = slice::from_raw_parts_mut(symbols as *mut Symbol, written + free);
70 Self { symbols, written }
71 }
72
73 /// Returns how many symbols have been written so far.
74 ///
75 /// This can also be interpreted as the position of the next
76 /// element to write into the buffer.
77 ///
78 /// Initially this will be `0`. If you write `n` symbols in the
79 /// callback and then return, the next time the callback is called,
80 /// the position will always be `n`.
81 /// If you then encode `m` more symbols, the next callback will always
82 /// be called with position `n + m`, and so on.
83 ///
84 /// In other words, the position is preserved across callback invocations.
85 ///
86 /// The only exception is when the encoder is reset,
87 /// (e.g. to beging a new transaction) in which case the position
88 /// will always restart at `0`.
89 #[must_use]
90 pub const fn position(&self) -> usize {
91 self.written
92 }
93
94 /// Returns how many more symbols can be written to the buffer.
95 #[must_use]
96 pub fn remaining(&self) -> usize {
97 self.symbols.len() - self.written
98 }
99
100 /// Tries to write all symbols to the buffer.
101 ///
102 /// # Errors
103 ///
104 /// If there is not enough space, it will not write anything and return [`NotEnoughSpace`]
105 /// to indicate that the buffer is too small.
106 pub fn write_all(&mut self, symbols: &[Symbol]) -> Result<(), NotEnoughSpace> {
107 if self.remaining() < symbols.len() {
108 return Err(NotEnoughSpace);
109 }
110
111 for &symbol in symbols {
112 self.symbols[self.written] = symbol;
113 self.written += 1;
114 }
115
116 Ok(())
117 }
118
119 /// Returns the underlying slice of symbols that have been written so far.
120 #[must_use]
121 pub fn as_mut_slice(&mut self) -> &mut [Symbol] {
122 &mut self.symbols[..self.written]
123 }
124}
125
126/// This function implements the C API that the simple encoder expects for the callback
127/// and delegates the call to the rust type `S` which implements `EncoderCallback`.
128unsafe extern "C" fn delegator<S: EncoderCallback>(
129 data: *const c_void,
130 data_size: usize,
131 symbols_written: usize,
132 symbols_free: usize,
133 symbols: *mut rmt_symbol_word_t,
134 done: *mut bool,
135 arg: *mut c_void,
136) -> usize {
137 let callback = &mut *(arg as *mut S);
138 // The size of the data is in bytes -> must be converted to number of T items.
139 let data_slice = slice::from_raw_parts(
140 data as *const S::Item,
141 data_size / mem::size_of::<S::Item>(),
142 );
143 let mut buffer = SymbolBuffer::from_raw_parts(symbols, symbols_written, symbols_free);
144
145 // The function returns the following status:
146 // 1. `0` if the given buffer for writing symbols is too small,
147 // like it needs 8 symbols to encode one data entry, but a buffer
148 // with only 4 symbols is given.
149 // 2. a value `n > 0` indicating that `n` symbols were written in this callback round.
150 //
151 // If it finishes encoding all data, it sets `*done = true`.
152 // This can happen with status 1 or 2 (you don't have to write symbols to indicate that you are done).
153 //
154 // The number of written symbols is tracked by the `SymbolBuffer`.
155 //
156 // Important: It counts the output symbols, not the input data items!
157 // If one processes a byte as an input into 8 symbols, then
158 // the function should return `8`.
159 if let Ok(()) = callback.encode(data_slice, &mut buffer) {
160 *done = true;
161 }
162
163 // Calculate how many symbols were written in this call:
164 buffer.position() - symbols_written
165}
166
167/// Trait that is implemented by types that can be used as callbacks for the [`SimpleEncoder`].
168pub trait EncoderCallback {
169 /// The type of input data that the encoder can encode.
170 type Item;
171
172 /// This function encodes the provided input data into RMT symbols and writes them into the provided
173 /// `SymbolBuffer`.
174 ///
175 /// To do this, a buffer is allocated in which the resulting symbols can be written. It might happen that there
176 /// is not enough space in the buffer to encode all input data. In this case, the function has two options:
177 /// 1. It can immediately return [`NotEnoughSpace`] to indicate that the buffer is too small.
178 /// The function will later be called again with a larger buffer. You should eventually process the data,
179 /// and not return [`NotEnoughSpace`] forever.
180 ///
181 /// 2. It can start encoding the input data, and write symbols into the buffer until it runs out of space.
182 /// It should return with [`NotEnoughSpace`]. The function will later be called again with more
183 /// space, making it possible to continue encoding the remaining input data.
184 ///
185 /// The function takes a slice of input data of your chosen type [`EncoderCallback::Item`], this is the same data
186 /// that is passed to the RMT driver when sending data. The slice will not change between unfinished calls.
187 ///
188 /// For example, if you start processing an `input_data` of 10 elements, and you return with
189 /// [`NotEnoughSpace`] after encoding 4 elements, the next time the function is called,
190 /// the `input_data` will still be the same 10 elements.
191 ///
192 /// It is your responsibility to keep track of how many input elements you have already processed.
193 /// If the number of output symbols are a multiple of the number of input elements, you can use [`SymbolBuffer::position`]
194 /// to track how many input elements have been processed so far:
195 /// 1 input element = 8 output symbols -> position / 8 = number of input elements processed.
196 ///
197 /// Once you have processed all input elements, you should return with [`Ok`].
198 ///
199 /// # ISR Safety
200 ///
201 /// This function is called from an ISR context. Care should be taken not to call std,
202 /// libc or FreeRTOS APIs (except for a few allowed ones).
203 ///
204 /// You are not allowed to block, but you are allowed to call FreeRTOS APIs with the FromISR suffix.
205 fn encode(
206 &mut self,
207 input_data: &[Self::Item],
208 buffer: &mut SymbolBuffer<'_>,
209 ) -> Result<(), NotEnoughSpace>;
210}
211
212/// This type represents the simple encoder, it wraps a [`EncoderCallback`] to delegate
213/// the encoding work to it.
214#[derive(Debug)]
215pub struct SimpleEncoder<T> {
216 _encoder: Box<T>,
217 handle: rmt_encoder_handle_t,
218}
219
220impl<T: EncoderCallback> SimpleEncoder<T> {
221 /// Constructs a new simple encoder with the given callback and configuration.
222 pub fn with_config(encoder: T, config: &SimpleEncoderConfig) -> Result<Self, EspError> {
223 let mut encoder = Box::new(encoder);
224
225 // SAFETY: The reference will only be used to mutate the encoder and never to move it.
226 let reference = encoder.as_mut();
227 let sys_config = rmt_simple_encoder_config_t {
228 callback: Some(delegator::<T>),
229 arg: reference as *mut T as *mut c_void,
230 min_chunk_size: config.min_chunk_size,
231 };
232
233 let mut handle: rmt_encoder_handle_t = ptr::null_mut();
234 // SAFETY: the config is copied by the c code
235 esp!(unsafe { rmt_new_simple_encoder(&sys_config, &mut handle) })?;
236 Ok(Self {
237 handle,
238 _encoder: encoder,
239 })
240 }
241}
242
243impl<T: EncoderCallback> RawEncoder for SimpleEncoder<T> {
244 type Item = T::Item;
245
246 fn handle(&mut self) -> &mut rmt_encoder_t {
247 unsafe { &mut *self.handle }
248 }
249}
250
251impl<T> Drop for SimpleEncoder<T> {
252 fn drop(&mut self) {
253 // This is calling encoder->del(encoder);
254 unsafe { rmt_del_encoder(self.handle) };
255 }
256}