esp_idf_hal/rmt/config.rs
1use core::time::Duration;
2
3use esp_idf_sys::{EspError, TickType_t, ESP_ERR_INVALID_ARG};
4
5use crate::rmt::ClockSource;
6use crate::units::{FromValueType, Hertz};
7
8/// A percentage from 0 to 100%, used to specify the duty percentage in [`CarrierConfig`].
9#[derive(Debug, Copy, Clone, PartialEq)]
10pub struct DutyPercent(f32);
11
12impl DutyPercent {
13 /// Must be between 0 and 1.0, otherwise an error is returned.
14 pub fn new(value: f32) -> Result<Self, EspError> {
15 if !(0.0..=1.0).contains(&value) {
16 Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>())
17 } else {
18 Ok(Self(value))
19 }
20 }
21
22 /// Returns the percentage as a value between 0.0 and 1.0.
23 #[must_use]
24 pub const fn to_f32(&self) -> f32 {
25 self.0
26 }
27}
28
29/// Configuration setting for looping a signal.
30#[derive(Copy, Clone, Debug, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum Loop {
33 /// No looping, transmit the signal only once.
34 None,
35 /// Repeats the transmission indefinitely.
36 Endless,
37 /// Repeats the transmission for the specified number of times.
38 Count(u32),
39}
40
41/// Controls how the channel accesses memory.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum MemoryAccess {
44 /// Enables the DMA backend for the channel. Using the DMA allows a significant amount of
45 /// the channel's workload to be offloaded from the CPU. However, the DMA backend is not
46 /// available on all ESP chips, please refer to [TRM] before you enable this option.
47 /// Or you might encounter a `ESP_ERR_NOT_SUPPORTED` error.
48 ///
49 /// [TRM]: https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#rmt
50 Direct {
51 /// This field controls the size of the internal DMA buffer. To achieve a better
52 /// throughput and smaller CPU overhead, you can set a larger value, e.g., `1024`.
53 memory_block_symbols: usize,
54 },
55 /// Disables direct memory access. The CPU will have to read/write the data transferred from/to the RMT memory.
56 Indirect {
57 /// This field controls the size of the dedicated memory block owned by the channel,
58 /// which should be at least 64.
59 memory_block_symbols: usize,
60 },
61}
62
63impl MemoryAccess {
64 #[must_use]
65 pub(crate) fn is_direct(&self) -> bool {
66 matches!(self, Self::Direct { .. })
67 }
68
69 #[must_use]
70 pub(crate) fn symbols(&self) -> usize {
71 let (Self::Direct {
72 memory_block_symbols,
73 }
74 | Self::Indirect {
75 memory_block_symbols,
76 }) = self;
77 *memory_block_symbols
78 }
79}
80
81impl Default for MemoryAccess {
82 fn default() -> Self {
83 Self::Indirect {
84 memory_block_symbols: 64,
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
90pub struct TxChannelConfig {
91 /// Selects the source clock for the RMT channel.
92 ///
93 /// Note that, the selected clock is also used by other channels,
94 /// which means the user should ensure this configuration is the
95 /// same when allocating other channels, regardless of TX or RX.
96 /// For the effect on the power consumption of different clock sources,
97 /// please refer to the [Power Management] section.
98 ///
99 /// [Power Management]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/rmt.html#rmt-power-management
100 pub clock_source: ClockSource,
101 /// Sets the resolution of the internal tick counter. The timing parameter
102 /// of the RMT signal is calculated based on this **tick**.
103 pub resolution: Hertz,
104 /// Controls how the channel accesses memory.
105 pub memory_access: MemoryAccess,
106 /// Sets the depth of the internal transaction queue, the deeper the queue,
107 /// the more transactions can be prepared in the backlog.
108 pub transaction_queue_depth: usize,
109 /// Set the priority of the interrupt. If set to 0, then the driver will use a
110 /// interrupt with low or medium priority (priority level may be one of 1, 2 or 3),
111 /// otherwise use the priority indicated by [`TxChannelConfig::interrupt_priority`].
112 ///
113 /// Please pay attention that once the interrupt priority is set, it cannot be changed
114 /// until the channel is dropped.
115 #[cfg(esp_idf_version_at_least_5_1_2)]
116 pub interrupt_priority: i32,
117 /// Is used to decide whether to invert the RMT signal before sending it to the GPIO pad.
118 pub invert_out: bool,
119 /// The signal output from the GPIO will be fed to the input path as well.
120 pub io_loop_back: bool,
121 /// Configure the GPIO as open-drain mode.
122 pub io_od_mode: bool,
123 /// Configures if the driver allows the system to power down the peripheral in light sleep mode.
124 /// Before entering sleep, the system will backup the RMT register context, which will be restored
125 /// later when the system exit the sleep mode. Powering down the peripheral can save more power,
126 /// but at the cost of more memory consumed to save the register context. It's a tradeoff between
127 /// power consumption and memory consumption. This configuration option relies on specific
128 /// hardware feature, if you enable it on an unsupported chip, you will see error message
129 /// like not able to power down in light sleep.
130 #[cfg(esp_idf_version_at_least_5_4_0)]
131 pub allow_pd: bool,
132 // This field is intentionally hidden to prevent non-exhaustive pattern matching.
133 // You should only construct this struct using the `..Default::default()` pattern.
134 // If you use this field directly, your code might break in future versions.
135 #[doc(hidden)]
136 #[allow(dead_code)]
137 pub __internal: (),
138}
139
140impl Default for TxChannelConfig {
141 fn default() -> Self {
142 Self {
143 clock_source: Default::default(),
144 resolution: 1.MHz().into(),
145 memory_access: Default::default(),
146 transaction_queue_depth: 4,
147 #[cfg(esp_idf_version_at_least_5_1_2)]
148 interrupt_priority: 0,
149 invert_out: false,
150 io_loop_back: false,
151 io_od_mode: false,
152 #[cfg(esp_idf_version_at_least_5_4_0)]
153 allow_pd: false,
154 __internal: (),
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
160pub struct TransmitConfig {
161 /// Specify the times of transmission in a loop, -1 means transmitting in an infinite loop
162 pub loop_count: Loop,
163 /// Set the output level for the "End Of Transmission"
164 pub eot_level: bool,
165 /// If set, when the transaction queue is full, driver will not block the thread but return directly.
166 #[cfg(esp_idf_version_at_least_5_1_3)]
167 pub queue_non_blocking: bool,
168 // This field is intentionally hidden to prevent non-exhaustive pattern matching.
169 // You should only construct this struct using the `..Default::default()` pattern.
170 // If you use this field directly, your code might break in future versions.
171 #[doc(hidden)]
172 #[allow(dead_code)]
173 pub __internal: (),
174}
175
176impl Default for TransmitConfig {
177 fn default() -> Self {
178 Self {
179 loop_count: Loop::None,
180 eot_level: false,
181 #[cfg(esp_idf_version_at_least_5_1_3)]
182 queue_non_blocking: false,
183 __internal: (),
184 }
185 }
186}
187
188#[derive(Debug, Clone)]
189pub struct CarrierConfig {
190 /// Carrier wave frequency, in Hz, 0 means disabling the carrier.
191 pub frequency: Hertz,
192 /// Carrier wave duty cycle (0~100%)
193 pub duty_cycle: DutyPercent,
194 /// Specify the polarity of carrier, by default it's modulated to base signal's high level
195 pub polarity_active_low: bool,
196 /// If set, the carrier can always exist even there's not transfer undergoing
197 pub always_on: bool,
198 // This field is intentionally hidden to prevent non-exhaustive pattern matching.
199 // You should only construct this struct using the `..Default::default()` pattern.
200 // If you use this field directly, your code might break in future versions.
201 #[doc(hidden)]
202 #[allow(dead_code)]
203 pub __internal: (),
204}
205
206impl Default for CarrierConfig {
207 fn default() -> Self {
208 Self {
209 frequency: 38000.Hz(),
210 duty_cycle: DutyPercent(0.5),
211 polarity_active_low: false,
212 always_on: false,
213 __internal: (),
214 }
215 }
216}
217
218#[derive(Debug, Clone)]
219pub struct RxChannelConfig {
220 /// Selects the source clock for the RMT channel.
221 ///
222 /// Note that, the selected clock is also used by other channels,
223 /// which means the user should ensure this configuration is the
224 /// same when allocating other channels, regardless of TX or RX.
225 /// For the effect on the power consumption of different clock sources,
226 /// please refer to the [Power Management] section.
227 ///
228 /// [Power Management]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/rmt.html#rmt-power-management
229 pub clock_source: ClockSource,
230 /// Sets the resolution of the internal tick counter. The timing parameter
231 /// of the RMT signal is calculated based on this **tick**.
232 pub resolution: Hertz,
233 /// Controls how the channel accesses memory.
234 pub memory_access: MemoryAccess,
235 /// Set the priority of the interrupt. If set to 0, then the driver will use a
236 /// interrupt with low or medium priority (priority level may be one of 1, 2 or 3),
237 /// otherwise use the priority indicated by [`RxChannelConfig::interrupt_priority`].
238 ///
239 /// Please pay attention that once the interrupt priority is set, it cannot be changed
240 /// until the channel is dropped.
241 #[cfg(esp_idf_version_at_least_5_1_2)]
242 pub interrupt_priority: i32,
243 /// Is used to decide whether to invert the incoming RMT signal.
244 pub invert_in: bool,
245 /// The signal output from the GPIO will be fed to the input path as well.
246 pub io_loop_back: bool,
247 /// Configures if the driver allows the system to power down the peripheral in light sleep mode.
248 /// Before entering sleep, the system will backup the RMT register context, which will be restored
249 /// later when the system exit the sleep mode. Powering down the peripheral can save more power,
250 /// but at the cost of more memory consumed to save the register context. It's a tradeoff between
251 /// power consumption and memory consumption. This configuration option relies on specific
252 /// hardware feature, if you enable it on an unsupported chip, you will see error message
253 /// like not able to power down in light sleep.
254 #[cfg(esp_idf_version_at_least_5_4_0)]
255 pub allow_pd: bool,
256 // This field is intentionally hidden to prevent non-exhaustive pattern matching.
257 // You should only construct this struct using the `..Default::default()` pattern.
258 // If you use this field directly, your code might break in future versions.
259 #[doc(hidden)]
260 #[allow(dead_code)]
261 pub __internal: (),
262}
263
264impl Default for RxChannelConfig {
265 fn default() -> Self {
266 Self {
267 clock_source: Default::default(),
268 resolution: 1.MHz().into(),
269 memory_access: Default::default(),
270 #[cfg(esp_idf_version_at_least_5_1_2)]
271 interrupt_priority: 0,
272 invert_in: false,
273 io_loop_back: false,
274 #[cfg(esp_idf_version_at_least_5_4_0)]
275 allow_pd: false,
276 __internal: (),
277 }
278 }
279}
280
281#[derive(Debug, Clone)]
282pub struct ReceiveConfig {
283 /// Specifies the minimal valid pulse duration in either high or low logic levels.
284 ///
285 /// A pulse width that is smaller than this value is treated as a glitch, and ignored by the hardware.
286 pub signal_range_min: Duration,
287 /// Specifies the maximum valid pulse duration in either high or low logic levels.
288 ///
289 /// A pulse width that is bigger than this value is treated as Stop Signal, and the
290 /// receiver generates receive-complete event immediately.
291 pub signal_range_max: Duration,
292 /// Set this flag if the incoming data is very long, and the driver can only receive the data
293 /// piece by piece, because the user buffer is not sufficient to save all the data.
294 #[cfg(esp_idf_version_at_least_5_3_0)]
295 pub enable_partial_rx: bool,
296 /// Maximum time to wait for data to be received.
297 ///
298 /// If `None`, the driver will wait indefinitely.
299 pub timeout: Option<TickType_t>,
300 // This field is intentionally hidden to prevent non-exhaustive pattern matching.
301 // You should only construct this struct using the `..Default::default()` pattern.
302 // If you use this field directly, your code might break in future versions.
303 #[doc(hidden)]
304 #[allow(dead_code)]
305 pub __internal: (),
306}
307
308impl Default for ReceiveConfig {
309 fn default() -> Self {
310 Self {
311 signal_range_min: Duration::ZERO,
312 // This is the maximum duration that the driver supports.
313 signal_range_max: Duration::from_nanos(u32::MAX as u64),
314 #[cfg(esp_idf_version_at_least_5_3_0)]
315 enable_partial_rx: false,
316 timeout: None,
317 __internal: (),
318 }
319 }
320}