esp_idf_hal/i2s/pdm.rs
1//! Pulse density modulation (PDM) driver for the ESP32 I2S peripheral.
2//!
3//! # Microcontroller support for PDM mode
4//!
5//! | Microcontroller | PDM Rx | PDM Tx |
6//! |--------------------|------------------|--------------------------|
7//! | ESP32 | I2S0 | I2S0, hardware version 1 |
8//! | ESP32-S2 | _not supported_ | _not supported_ |
9//! | ESP32-S3 | I2S0 | I2S0, hardware version 2 |
10//! | ESP32-C2 (ESP8684) | _not supported_ | _not supported_ |
11//! | ESP32-C3 | _not supported_* | I2S0, hardware version 2 |
12//! | ESP32-C6 | _not supported_* | I2S0, hardware version 2 |
13//! | ESP32-H2 | _not supported_* | I2S0, hardware version 2 |
14//! | ESP32-P4 | I2S0 | I2S0, hardware version 2 | ????
15//!
16//! \* These microcontrollers have PDM Rx capabilities but lack a PDM-to-PCM decoder required by the ESP-IDF SDK.
17//!
18//! ## Hardware versions
19//!
20//! Hardware version 1 (ESP32) provides only a single output line, requiring external hardware to demultiplex stereo
21//! signals in a time-critical manner; it is unlikely you will see accurate results here.
22//!
23//! Harware version 2 (all others with PDM Tx support) provide two output lines, allowing for separate left/right
24//! channels.
25//!
26//! See the [`PdmTxSlotConfig documentation`][PdmTxSlotConfig] for more details.
27
28use super::*;
29use crate::gpio::*;
30
31// Note on cfg settings:
32// esp_idf_soc_i2s_hw_version_1 and esp_idf_soc_i2s_hw_version_2 are defined *only* for ESP-IDF v5.0+.
33// When v4.4 support is needed, actual microcontroller names must be used: esp32 for esp_idf_soc_i2s_hw_version_1,
34// any(esp32s3,esp32c3,esp32c6,esp32h2) for esp_idf_soc_i2s_hw_version_2.
35
36#[cfg(esp_idf_version_major = "4")]
37use esp_idf_sys::*;
38
39pub(super) mod config {
40 #[allow(unused)]
41 use crate::{gpio::*, i2s::config::*};
42 use esp_idf_sys::*;
43
44 /// I2S pulse density modulation (PDM) downsampling mode.
45 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
46 pub enum PdmDownsample {
47 /// Downsample 8 samples.
48 #[default]
49 Samples8,
50
51 /// Downsample 16 samples.
52 Samples16,
53
54 /// Maximum downsample rate.
55 Max,
56 }
57
58 #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))]
59 impl PdmDownsample {
60 /// Convert to the ESP-IDF SDK `i2s_pdm_downsample_t` representation.
61 #[inline(always)]
62 pub(super) fn as_sdk(&self) -> i2s_pdm_dsr_t {
63 match self {
64 Self::Samples8 => 0,
65 Self::Samples16 => 1,
66 Self::Max => 2,
67 }
68 }
69 }
70
71 /// Pulse density modulation (PDM) mode receive clock configuration for the I2S peripheral.
72 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
73 pub struct PdmRxClkConfig {
74 /// The sample rate in Hz.
75 pub(super) sample_rate_hz: u32,
76
77 /// The clock source.
78 clk_src: ClockSource,
79
80 /// The multiple of the MCLK signal to the sample rate.
81 mclk_multiple: MclkMultiple,
82
83 /// Downsampling rate mode.
84 pub(super) downsample_mode: PdmDownsample,
85 }
86
87 impl PdmRxClkConfig {
88 /// Create a PDM clock configuration with the specified sample rate in Hz. This will set the clock source to
89 /// PLL_F160M, the MCLK multiple to 256 times the sample rate, and the downsampling mode to 8 samples.
90 #[inline(always)]
91 pub fn from_sample_rate_hz(rate: u32) -> Self {
92 Self {
93 sample_rate_hz: rate,
94 clk_src: ClockSource::default(),
95 mclk_multiple: MclkMultiple::M256,
96 downsample_mode: PdmDownsample::Samples8,
97 }
98 }
99
100 /// Set the clock source on this PDM receive clock configuration.
101 #[inline(always)]
102 pub fn clk_src(mut self, clk_src: ClockSource) -> Self {
103 self.clk_src = clk_src;
104 self
105 }
106
107 /// Set the MCLK multiple on this PDM receive clock configuration.
108 #[inline(always)]
109 pub fn mclk_multiple(mut self, mclk_multiple: MclkMultiple) -> Self {
110 self.mclk_multiple = mclk_multiple;
111 self
112 }
113
114 /// Set the downsampling mode on this PDM receive clock configuration.
115 #[inline(always)]
116 pub fn downsample_mode(mut self, downsample_mode: PdmDownsample) -> Self {
117 self.downsample_mode = downsample_mode;
118 self
119 }
120
121 /// Convert to the ESP-IDF SDK `i2s_pdm_rx_clk_config_t` representation.
122 #[cfg(all(
123 any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3),
124 not(esp_idf_version_major = "4")
125 ))]
126 #[inline(always)]
127 pub(super) fn as_sdk(&self) -> i2s_pdm_rx_clk_config_t {
128 #[allow(clippy::needless_update)]
129 i2s_pdm_rx_clk_config_t {
130 sample_rate_hz: self.sample_rate_hz,
131 clk_src: self.clk_src.as_sdk(),
132 mclk_multiple: self.mclk_multiple.as_sdk(),
133 dn_sample_mode: self.downsample_mode.as_sdk(),
134 ..Default::default()
135 }
136 }
137 }
138
139 /// Pulse density modulation (PDM) mode receive configuration for the I2S peripheral.
140 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
141 pub struct PdmRxConfig {
142 /// The base channel configuration.
143 pub(super) channel_cfg: Config,
144
145 /// PDM mode channel receive clock configuration.
146 pub(super) clk_cfg: PdmRxClkConfig,
147
148 /// PDM mode channel slot configuration.
149 pub(super) slot_cfg: PdmRxSlotConfig,
150
151 /// PDM mode channel GPIO configuration.
152 #[cfg(not(esp_idf_version_major = "4"))]
153 pub(super) gpio_cfg: PdmRxGpioConfig,
154 }
155
156 impl PdmRxConfig {
157 /// Create a new PDM mode receive configuration from the specified clock, slot, and GPIO configurations.
158 pub fn new(
159 channel_cfg: Config,
160 clk_cfg: PdmRxClkConfig,
161 slot_cfg: PdmRxSlotConfig,
162 #[cfg(not(esp_idf_version_major = "4"))] gpio_cfg: PdmRxGpioConfig,
163 ) -> Self {
164 Self {
165 channel_cfg,
166 clk_cfg,
167 slot_cfg,
168 #[cfg(not(esp_idf_version_major = "4"))]
169 gpio_cfg,
170 }
171 }
172
173 /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_pdm_rx_config_t` representation.
174 #[cfg(esp_idf_soc_i2s_supports_pdm_rx)]
175 #[inline(always)]
176 pub(super) fn as_sdk<'d>(
177 &self,
178 clk: impl OutputPin + 'd,
179 din: impl InputPin + 'd,
180 ) -> i2s_pdm_rx_config_t {
181 i2s_pdm_rx_config_t {
182 clk_cfg: self.clk_cfg.as_sdk(),
183 slot_cfg: self.slot_cfg.as_sdk(),
184 gpio_cfg: self.gpio_cfg.as_sdk(clk, din),
185 }
186 }
187
188 /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_pdm_rx_config_t` representation.
189 ///
190 /// Supported on ESP-IDF 5.1+.
191 #[cfg(all(
192 esp_idf_soc_i2s_supports_pdm_rx, // Implicitly selects 5.0+
193 not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0"))
194 ))]
195 #[inline(always)]
196 pub(super) fn as_sdk_multi<'d, DIN: InputPin + 'd>(
197 &self,
198 clk: impl OutputPin + 'd,
199 dins: &[DIN],
200 ) -> i2s_pdm_rx_config_t {
201 i2s_pdm_rx_config_t {
202 clk_cfg: self.clk_cfg.as_sdk(),
203 slot_cfg: self.slot_cfg.as_sdk(),
204 gpio_cfg: self.gpio_cfg.as_sdk_multi(clk, dins),
205 }
206 }
207
208 /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_driver_config_t` representation.
209 #[cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4"))]
210 #[inline(always)]
211 pub(super) fn as_sdk(&self) -> i2s_driver_config_t {
212 let chan_fmt = match self.slot_cfg.slot_mode {
213 SlotMode::Stereo => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT,
214 SlotMode::Mono => match self.slot_cfg.slot_mask {
215 PdmSlotMask::Both => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT,
216 PdmSlotMask::Left => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_LEFT,
217 PdmSlotMask::Right => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_RIGHT,
218 },
219 };
220
221 i2s_driver_config_t {
222 mode: self.channel_cfg.role.as_sdk()
223 | i2s_mode_t_I2S_MODE_RX
224 | i2s_mode_t_I2S_MODE_PDM,
225 sample_rate: self.clk_cfg.sample_rate_hz,
226 bits_per_sample: 16, // fixed for PDM,
227 channel_format: chan_fmt,
228 communication_format: 0, // ?
229 intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1
230 dma_buf_count: self.channel_cfg.dma_buffer_count as i32,
231 dma_buf_len: self.channel_cfg.frames_per_buffer as i32,
232 #[cfg(any(esp32, esp32s2))]
233 use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll),
234 #[cfg(not(any(esp32, esp32s2)))]
235 use_apll: false,
236 tx_desc_auto_clear: self.channel_cfg.auto_clear,
237 fixed_mclk: 0,
238 mclk_multiple: self.clk_cfg.mclk_multiple.as_sdk(),
239 bits_per_chan: 16, // fixed for PDM
240
241 // The following are TDM-only fields and are not present on chips that don't support TDM mode.
242 // There's no cfg option for this (it's a constant in esp-idf-sys).
243 #[cfg(not(any(esp32, esp32s2)))]
244 chan_mask: 0,
245 #[cfg(not(any(esp32, esp32s2)))]
246 total_chan: 0,
247 #[cfg(not(any(esp32, esp32s2)))]
248 left_align: false,
249 #[cfg(not(any(esp32, esp32s2)))]
250 big_edin: false,
251 #[cfg(not(any(esp32, esp32s2)))]
252 bit_order_msb: false,
253 #[cfg(not(any(esp32, esp32s2)))]
254 skip_msk: true,
255 }
256 }
257 }
258
259 /// PDM mode GPIO (general purpose input/output) receive configuration.
260 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
261 pub struct PdmRxGpioConfig {
262 /// Whether the clock output is inverted.
263 pub(super) clk_inv: bool,
264 }
265
266 /// The maximum number of data input pins that can be used in PDM mode.
267 ///
268 /// This is 1 on the ESP32 and 4 on the ESP32-S3.
269 #[cfg(esp32)]
270 pub const SOC_I2S_PDM_MAX_RX_LINES: usize = 1;
271
272 /// The maximum number of data input pins that can be used in PDM mode.
273 ///
274 /// This is 1 on the ESP32 and 4 on the ESP32-S3.
275 #[cfg(esp32s3)]
276 pub const SOC_I2S_PDM_MAX_RX_LINES: usize = 4;
277
278 impl PdmRxGpioConfig {
279 /// Create a new PDM mode GPIO receive configuration with the specified inversion flag for the clock output.
280 #[inline(always)]
281 pub fn new(clk_inv: bool) -> Self {
282 Self { clk_inv }
283 }
284
285 /// Set the clock inversion flag on this PDM GPIO configuration.
286 #[inline(always)]
287 pub fn clk_invert(mut self, clk_inv: bool) -> Self {
288 self.clk_inv = clk_inv;
289 self
290 }
291
292 /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation.
293 ///
294 /// Note: The bitfields are renamed in ESP-IDF 5.1+.
295 #[cfg(all(
296 esp_idf_soc_i2s_supports_pdm_rx,
297 esp_idf_version_major = "5",
298 esp_idf_version_minor = "0"
299 ))]
300 pub(crate) fn as_sdk<'d>(
301 &self,
302 clk: impl OutputPin + 'd,
303 din: impl InputPin + 'd,
304 ) -> i2s_pdm_rx_gpio_config_t {
305 let invert_flags = i2s_pdm_rx_gpio_config_t__bindgen_ty_1 {
306 _bitfield_1: i2s_pdm_rx_gpio_config_t__bindgen_ty_1::new_bitfield_1(
307 self.clk_inv as u32,
308 ),
309 ..Default::default()
310 };
311
312 i2s_pdm_rx_gpio_config_t {
313 clk: clk.pin(),
314 din: din.pin(),
315 invert_flags,
316 }
317 }
318
319 /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation.
320 #[cfg(all(
321 esp_idf_soc_i2s_supports_pdm_rx,
322 not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0"))
323 ))]
324 pub(crate) fn as_sdk<'d>(
325 &self,
326 clk: impl OutputPin + 'd,
327 din: impl InputPin + 'd,
328 ) -> i2s_pdm_rx_gpio_config_t {
329 #[allow(clippy::unnecessary_cast)]
330 let mut dins: [gpio_num_t; SOC_I2S_PDM_MAX_RX_LINES as usize] =
331 [-1; SOC_I2S_PDM_MAX_RX_LINES as usize];
332 dins[0] = din.pin() as _;
333
334 let pins = i2s_pdm_rx_gpio_config_t__bindgen_ty_1 { dins };
335
336 let invert_flags = i2s_pdm_rx_gpio_config_t__bindgen_ty_2 {
337 _bitfield_1: i2s_pdm_rx_gpio_config_t__bindgen_ty_2::new_bitfield_1(
338 self.clk_inv as u32,
339 ),
340 ..Default::default()
341 };
342
343 i2s_pdm_rx_gpio_config_t {
344 clk: clk.pin() as _,
345 __bindgen_anon_1: pins,
346 invert_flags,
347 }
348 }
349
350 /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation.
351 ///
352 /// This will ignore any din pins beyond [`SOC_I2S_PDM_MAX_RX_LINES`].
353 ///
354 /// Supported on ESP-IDF 5.1+ only.
355 #[cfg(all(
356 esp_idf_soc_i2s_supports_pdm_rx,
357 not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0"))
358 ))]
359 pub(crate) fn as_sdk_multi<'d, DIN: InputPin + 'd>(
360 &self,
361 clk: impl OutputPin + 'd,
362 dins: &[DIN],
363 ) -> i2s_pdm_rx_gpio_config_t {
364 #[allow(clippy::unnecessary_cast)]
365 let mut din_pins: [gpio_num_t; SOC_I2S_PDM_MAX_RX_LINES as usize] =
366 [-1; SOC_I2S_PDM_MAX_RX_LINES as usize];
367
368 #[allow(clippy::unnecessary_cast)]
369 for (i, din) in dins.iter().enumerate() {
370 if i >= SOC_I2S_PDM_MAX_RX_LINES as usize {
371 break;
372 }
373
374 din_pins[i] = din.pin() as _;
375 }
376
377 let pins = i2s_pdm_rx_gpio_config_t__bindgen_ty_1 { dins: din_pins };
378
379 let invert_flags = i2s_pdm_rx_gpio_config_t__bindgen_ty_2 {
380 _bitfield_1: i2s_pdm_rx_gpio_config_t__bindgen_ty_2::new_bitfield_1(
381 self.clk_inv as u32,
382 ),
383 ..Default::default()
384 };
385
386 i2s_pdm_rx_gpio_config_t {
387 clk: clk.pin() as _,
388 __bindgen_anon_1: pins,
389 invert_flags,
390 }
391 }
392 }
393
394 /// PDM mode channel receive slot configuration.
395 ///
396 /// # Note
397 /// The `slot_mode` and `slot_mask` cause data to be interpreted in different ways, as noted below.
398 /// WS is the "word select" signal, sometimes called LRCLK (left/right clock).
399 ///
400 /// Assuming the received data contains the following samples (when converted from PDM to PCM), where a sample may be 8, 16, 24, or 32 bits, depending on `data_bit_width`:
401 ///
402 /// | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | |
403 /// |-------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|-----|
404 /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ... |
405 ///
406 /// The actual data in the buffer will be (1-4 bytes, depending on `data_bit_width`):
407 ///
408 /// <table>
409 /// <thead>
410 /// <tr><th><code>slot_mode</code></th><th><code>slot_mask</code></th><th colspan=8>Buffer Contents</th></tr>
411 /// <tr><th></th><th></th><th><code>d[0]</code></th><th><code>d[1]</code></th><th><code>d[2]</code></th><th><code>d[3]</code></th><th><code>d[4]</code></th><th><code>d[5]</code></th><th><code>d[6]</code></th><th><code>d[7]</code></th></tr>
412 /// </thead>
413 /// <tbody>
414 /// <tr><td rowspan=3><code>Mono</code></td> <td><code>Left</code></td> <td>11</td><td>13</td><td>15</td><td>17</td><td>19</td><td>21</td><td>23</td><td>25</td></tr>
415 /// <tr> <td><code>Right</code></td><td>12</td><td>14</td><td>16</td><td>18</td><td>20</td><td>22</td><td>24</td><td>26</td></tr>
416 /// <tr> <td><code>Both</code></td> <td colspan=8><i>Unspecified behavior</i></td></tr>
417 /// <tr><td><code>Stereo (ESP32)</code></td> <td><i>Any</i></td> <td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td></tr>
418 /// <tr><td><code>Stereo (ESP32-S3)</code></td><td><i>Any</i></td> <td>12</td><td>11</td><td>14</td><td>13</td><td>16</td><td>15</td><td>18</td><td>17</td></tr>
419 /// </tbody>
420 /// </table>
421 ///
422 /// Note that, on the ESP32-S3, the right channel is received first. This can be switched by setting
423 /// [`PdmRxGpioConfig::clk_invert`] to `true` in the merged [`PdmRxConfig`].
424 ///
425 /// For details, refer to the
426 /// _ESP-IDF Programming Guide_ PDM Rx Usage details for your specific microcontroller:
427 /// * [ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#pdm-rx-usage)
428 /// * [ESP32-S3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#pdm-rx-usage)
429 ///
430 /// Other microcontrollers do not support PDM receive mode, or do not have a PDM-to-PCM peripheral that allows for decoding
431 /// the PDM data as required by ESP-IDF.
432 #[derive(Clone, Copy, Debug)]
433 pub struct PdmRxSlotConfig {
434 /// I2S sample data bit width (valid data bits per sample).
435 #[allow(dead_code)]
436 pub(super) data_bit_width: DataBitWidth,
437
438 /// I2s slot bit width (total bits per slot).
439 #[allow(dead_code)]
440 pub(super) slot_bit_width: SlotBitWidth,
441
442 /// Mono or stereo mode operation.
443 #[allow(dead_code)]
444 pub(super) slot_mode: SlotMode,
445
446 /// Are we using the left, right, or both data slots?
447 #[allow(dead_code)]
448 pub(super) slot_mask: PdmSlotMask,
449
450 /// High pass filter
451 #[cfg(esp_idf_soc_i2s_supports_pdm_rx_hp_filter)]
452 pub(super) high_pass: Option<HighPassFilter>,
453 }
454
455 impl PartialEq for PdmRxSlotConfig {
456 #[cfg(not(esp_idf_soc_i2s_supports_pdm_rx_hp_filter))]
457 fn eq(&self, other: &Self) -> bool {
458 self.data_bit_width == other.data_bit_width
459 && self.slot_bit_width == other.slot_bit_width
460 && self.slot_mode == other.slot_mode
461 && self.slot_mask == other.slot_mask
462 }
463
464 /// Note: Ignoring the high_pass filter that contains a f32 for eq compairson and only checking if its set or not.
465 #[cfg(esp_idf_soc_i2s_supports_pdm_rx_hp_filter)]
466 fn eq(&self, other: &Self) -> bool {
467 self.data_bit_width == other.data_bit_width
468 && self.slot_bit_width == other.slot_bit_width
469 && self.slot_mode == other.slot_mode
470 && self.slot_mask == other.slot_mask
471 && self.high_pass.is_some() == other.high_pass.is_some()
472 }
473 }
474
475 impl Eq for PdmRxSlotConfig {}
476
477 impl PdmRxSlotConfig {
478 /// Configure the PDM mode channel receive slot configuration for the specified bits per sample and slot mode
479 /// in 2 slots.
480 pub fn from_bits_per_sample_and_slot_mode(
481 bits_per_sample: DataBitWidth,
482 slot_mode: SlotMode,
483 ) -> Self {
484 let slot_mask = if slot_mode == SlotMode::Mono {
485 PdmSlotMask::Left
486 } else {
487 PdmSlotMask::Both
488 };
489
490 Self {
491 data_bit_width: bits_per_sample,
492 slot_bit_width: SlotBitWidth::Auto,
493 slot_mode,
494 slot_mask,
495 #[cfg(esp_idf_soc_i2s_supports_pdm_rx_hp_filter)]
496 high_pass: None,
497 }
498 }
499
500 /// Update the data bit width on this PDM receive slot configuration.
501 #[inline(always)]
502 pub fn data_bit_width(mut self, data_bit_width: DataBitWidth) -> Self {
503 self.data_bit_width = data_bit_width;
504 self
505 }
506
507 /// Update the slot bit width on this PDM receive slot configuration.
508 #[inline(always)]
509 pub fn slot_bit_width(mut self, slot_bit_width: SlotBitWidth) -> Self {
510 self.slot_bit_width = slot_bit_width;
511 self
512 }
513
514 /// Update the slot mode and mask on this PDM receive slot configuration.
515 #[inline(always)]
516 pub fn slot_mode_mask(mut self, slot_mode: SlotMode, slot_mask: PdmSlotMask) -> Self {
517 self.slot_mode = slot_mode;
518 self.slot_mask = slot_mask;
519 self
520 }
521
522 #[cfg(esp_idf_soc_i2s_supports_pdm_rx_hp_filter)]
523 /// Set the PDM high pass filter
524 pub fn high_pass_filter(mut self, filter: Option<HighPassFilter>) -> Self {
525 self.high_pass = filter;
526 self
527 }
528
529 /// Convert this PDM mode channel receive slot configuration into the ESP-IDF SDK `i2s_pdm_rx_slot_config_t`
530 /// representation.
531 #[cfg(esp_idf_soc_i2s_supports_pdm_rx)]
532 #[inline(always)]
533 #[allow(clippy::needless_update)]
534 pub(super) fn as_sdk(&self) -> i2s_pdm_rx_slot_config_t {
535 i2s_pdm_rx_slot_config_t {
536 data_bit_width: self.data_bit_width.as_sdk(),
537 slot_bit_width: self.slot_bit_width.as_sdk(),
538 slot_mode: self.slot_mode.as_sdk(),
539 slot_mask: self.slot_mask.as_sdk(),
540 #[cfg(esp_idf_soc_i2s_supports_pdm_rx_hp_filter)]
541 hp_en: self.high_pass.is_some(),
542 #[cfg(esp_idf_soc_i2s_supports_pdm_rx_hp_filter)]
543 hp_cut_off_freq_hz: if let Some(filter) = self.high_pass {
544 filter.cut_off_freq
545 } else {
546 185.0
547 },
548 #[cfg(esp_idf_soc_i2s_supports_pdm_rx_hp_filter)]
549 amplify_num: if let Some(filter) = self.high_pass {
550 filter.amplify_num
551 } else {
552 1
553 },
554 ..Default::default()
555 }
556 }
557 }
558
559 /// Pulse density modulation (PDM) transmit signal scaling mode.
560 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
561 pub enum PdmSignalScale {
562 /// Divide the PDM signal by 2.
563 Div2,
564
565 /// No scaling.
566 #[default]
567 None,
568
569 /// Multiply the PDM signal by 2.
570 Mul2,
571
572 /// Multiply the PDM signal by 4.
573 Mul4,
574 }
575
576 impl PdmSignalScale {
577 /// Convert to the ESP-IDF SDK `i2s_pdm_signal_scale_t` representation.
578 #[cfg_attr(esp_idf_version_major = "4", allow(unused))]
579 #[inline(always)]
580 pub(crate) fn as_sdk(&self) -> i2s_pdm_sig_scale_t {
581 match self {
582 Self::Div2 => 0,
583 Self::None => 1,
584 Self::Mul2 => 2,
585 Self::Mul4 => 3,
586 }
587 }
588 }
589
590 /// I2S slot selection in PDM mode.
591 ///
592 /// The default is `PdmSlotMask::Both`.
593 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
594 pub enum PdmSlotMask {
595 /// I2S transmits or receives the left slot.
596 Left,
597
598 /// I2S transmits or receives the right slot.
599 Right,
600
601 /// I2S transmits or receives both slots.
602 Both,
603 }
604
605 impl Default for PdmSlotMask {
606 #[inline(always)]
607 fn default() -> Self {
608 Self::Both
609 }
610 }
611
612 impl PdmSlotMask {
613 /// Convert to the ESP-IDF SDK `i2s_pdm_slot_mask_t` representation.
614 #[cfg(not(esp_idf_version_major = "4"))]
615 #[inline(always)]
616 #[allow(unused)]
617 pub(crate) fn as_sdk(&self) -> i2s_pdm_slot_mask_t {
618 match self {
619 Self::Left => 1 << 0,
620 Self::Right => 1 << 1,
621 Self::Both => (1 << 0) | (1 << 1),
622 }
623 }
624 }
625
626 /// PDM RX High Pass Filter
627 #[derive(Clone, Copy, Debug, PartialEq)]
628 pub struct HighPassFilter {
629 /// High pass filter cut-off frequency, range 23.3Hz ~ 185Hz
630 pub(super) cut_off_freq: f32,
631
632 /// The amplification number of the final conversion result
633 ///
634 /// The data that have converted from PDM to PCM module, will time `amplify_num` additionally to amplify the final result.
635 /// Note that it's only a multiplier of the digital PCM data, not the gain of the analog signal.
636 /// range 1~15, default 1
637 pub(super) amplify_num: u32,
638 }
639
640 impl HighPassFilter {
641 /// Set the Filter cut off Frequency.
642 ///
643 /// Note: Range between 23.3Hz ~ 185Hz
644 pub fn cut_off_freq(cut_off_freq: f32) -> Self {
645 Self {
646 cut_off_freq,
647 amplify_num: 1,
648 }
649 }
650
651 /// Set the amplification number of the final conversion result
652 ///
653 /// Range: 1-15
654 pub fn amplify_number(mut self, amplify_num: u32) -> Self {
655 self.amplify_num = amplify_num;
656 self
657 }
658 }
659
660 /// The I2s pulse density modulation (PDM) mode transmit clock configuration.
661 ///
662 /// # Note
663 /// The PDM transmit clock can only be set to the following two upsampling rate configurations:
664 /// * `upsampling_fp = 960`, `upsampling_fs = sample_rate_hz / 100`. In this case, `Fpdm = 128 * 48000 = 6.144 MHz`.
665 /// * `upsampling_fp = 960`, `upsampling_fs = 480`. In this case, `Fpdm = 128 * sample_rate_hz`.
666 ///
667 /// If the PDM receiver does not use the PDM serial clock, the first configuration should be used. Otherwise,
668 /// use the second configuration.
669 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
670 pub struct PdmTxClkConfig {
671 /// I2S sample rate in Hz.
672 pub(super) sample_rate_hz: u32,
673
674 /// The clock source.
675 pub(super) clk_src: ClockSource,
676
677 /// The multiple of MCLK to the sample rate.
678 pub(super) mclk_multiple: MclkMultiple,
679
680 /// Upsampling `fp` parameter.
681 upsample_fp: u32,
682
683 /// Upsampling `fs` parameter.
684 upsample_fs: u32,
685 }
686
687 impl PdmTxClkConfig {
688 /// Create a new PDM mode transmit clock configuration from the specified sample rate in Hz. This will set the
689 /// clock source to PLL_F160M, the MCLK multiple to 256 times the sample rate, `upsample_fp` to 960, and
690 /// `upsample_fs` to 480.
691 #[inline(always)]
692 pub fn from_sample_rate_hz(sample_rate_hz: u32) -> Self {
693 Self {
694 sample_rate_hz,
695 clk_src: ClockSource::default(),
696 mclk_multiple: MclkMultiple::M256,
697 upsample_fp: 960,
698 upsample_fs: 480,
699 }
700 }
701
702 /// Set the sample rate on this PDM mode transmit clock configuration.
703 #[inline(always)]
704 pub fn sample_rate_hz(mut self, sample_rate_hz: u32) -> Self {
705 self.sample_rate_hz = sample_rate_hz;
706 self
707 }
708
709 /// Set the clock source on this PDM mode transmit clock configuration.
710 #[inline(always)]
711 pub fn clk_src(mut self, clk_src: ClockSource) -> Self {
712 self.clk_src = clk_src;
713 self
714 }
715
716 /// Set the MCLK multiple on this PDM mode transmit clock configuration.
717 #[inline(always)]
718 pub fn mclk_multiple(mut self, mclk_multiple: MclkMultiple) -> Self {
719 self.mclk_multiple = mclk_multiple;
720 self
721 }
722
723 /// Set the upsampling parameters on this PDM mode transmit clock configuration.
724 #[inline(always)]
725 pub fn upsample(mut self, upsample_fp: u32, upsample_fs: u32) -> Self {
726 self.upsample_fp = upsample_fp;
727 self.upsample_fs = upsample_fs;
728 self
729 }
730
731 /// Convert to the ESP-IDF SDK `i2s_pdm_tx_clk_config_t` representation.
732 #[allow(clippy::needless_update)]
733 #[cfg(not(esp_idf_version_major = "4"))]
734 #[inline(always)]
735 pub(super) fn as_sdk(&self) -> i2s_pdm_tx_clk_config_t {
736 i2s_pdm_tx_clk_config_t {
737 sample_rate_hz: self.sample_rate_hz,
738 clk_src: self.clk_src.as_sdk(),
739 mclk_multiple: self.mclk_multiple.as_sdk(),
740 up_sample_fp: self.upsample_fp,
741 up_sample_fs: self.upsample_fs,
742 ..Default::default() // bclk_div in ESP IDF > 5.1
743 }
744 }
745
746 /// Convert to the ESP-IDF SDK `i2s_pdm_tx_upsample_cfg_t` representation.
747 #[cfg(esp_idf_version_major = "4")]
748 #[inline(always)]
749 pub(super) fn as_sdk(&self) -> i2s_pdm_tx_upsample_cfg_t {
750 i2s_pdm_tx_upsample_cfg_t {
751 sample_rate: self.sample_rate_hz as i32,
752 fp: self.upsample_fp as i32,
753 fs: self.upsample_fs as i32,
754 }
755 }
756 }
757
758 /// The I2S pulse density modulation (PDM) mode transmit configuration for the I2S peripheral.
759 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
760 pub struct PdmTxConfig {
761 /// The base channel configuration.
762 pub(super) channel_cfg: Config,
763
764 /// PDM mode channel transmit clock configuration.
765 pub(super) clk_cfg: PdmTxClkConfig,
766
767 /// PDM mode channel transmit slot configuration.
768 pub(super) slot_cfg: PdmTxSlotConfig,
769
770 /// PDM mode channel transmit GPIO configuration.
771 #[cfg(not(esp_idf_version_major = "4"))]
772 pub(super) gpio_cfg: PdmTxGpioConfig,
773 }
774
775 impl PdmTxConfig {
776 /// Create a new PDM mode transmit configuration from the specified clock, slot, and GPIO configurations.
777 pub fn new(
778 channel_cfg: Config,
779 clk_cfg: PdmTxClkConfig,
780 slot_cfg: PdmTxSlotConfig,
781 #[cfg(not(esp_idf_version_major = "4"))] gpio_cfg: PdmTxGpioConfig,
782 ) -> Self {
783 Self {
784 channel_cfg,
785 clk_cfg,
786 slot_cfg,
787 #[cfg(not(esp_idf_version_major = "4"))]
788 gpio_cfg,
789 }
790 }
791
792 /// Convert to the ESP-IDF `i2s_pdm_tx_config_t` representation.
793 #[cfg(all(not(esp_idf_version_major = "4"), not(esp_idf_soc_i2s_hw_version_2)))]
794 #[inline(always)]
795 pub(crate) fn as_sdk<'d>(
796 &self,
797 clk: impl OutputPin + 'd,
798 dout: impl OutputPin + 'd,
799 ) -> i2s_pdm_tx_config_t {
800 i2s_pdm_tx_config_t {
801 clk_cfg: self.clk_cfg.as_sdk(),
802 slot_cfg: self.slot_cfg.as_sdk(),
803 gpio_cfg: self.gpio_cfg.as_sdk(clk, dout),
804 }
805 }
806
807 /// Convert to the ESP-IDF `i2s_pdm_tx_config_t` representation.
808 #[cfg(esp_idf_soc_i2s_hw_version_2)]
809 #[inline(always)]
810 pub(crate) fn as_sdk<'d>(
811 &self,
812 clk: impl OutputPin + 'd,
813 dout: impl OutputPin + 'd,
814 dout2: Option<impl OutputPin + 'd>,
815 ) -> i2s_pdm_tx_config_t {
816 i2s_pdm_tx_config_t {
817 clk_cfg: self.clk_cfg.as_sdk(),
818 slot_cfg: self.slot_cfg.as_sdk(),
819 gpio_cfg: self.gpio_cfg.as_sdk(clk, dout, dout2),
820 }
821 }
822
823 /// Convert to the ESP-IDF `i2s_driver_config_t` representation.
824 #[cfg(esp_idf_version_major = "4")]
825 pub(crate) fn as_sdk(&self) -> i2s_driver_config_t {
826 let chan_fmt = match self.slot_cfg.slot_mode {
827 SlotMode::Stereo => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT,
828 SlotMode::Mono => match self.slot_cfg.slot_mask {
829 PdmSlotMask::Both => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT,
830 PdmSlotMask::Left => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_LEFT,
831 PdmSlotMask::Right => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_RIGHT,
832 },
833 };
834
835 i2s_driver_config_t {
836 mode: self.channel_cfg.role.as_sdk()
837 | i2s_mode_t_I2S_MODE_TX
838 | i2s_mode_t_I2S_MODE_PDM,
839 sample_rate: self.clk_cfg.sample_rate_hz,
840 bits_per_sample: 16, // fixed for PDM,
841 channel_format: chan_fmt,
842 communication_format: 0, // ?
843 intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1
844 dma_buf_count: self.channel_cfg.dma_buffer_count as i32,
845 dma_buf_len: self.channel_cfg.frames_per_buffer as i32,
846 #[cfg(any(esp32, esp32s2))]
847 use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll),
848 #[cfg(not(any(esp32, esp32s2)))]
849 use_apll: false,
850 tx_desc_auto_clear: self.channel_cfg.auto_clear,
851 fixed_mclk: 0,
852 mclk_multiple: self.clk_cfg.mclk_multiple.as_sdk(),
853 bits_per_chan: 16, // fixed for PDM
854
855 // The following are TDM-only fields and are not present on chips that don't support TDM mode.
856 // There's no cfg option for this (it's a constant in esp-idf-sys).
857 #[cfg(not(any(esp32, esp32s2)))]
858 chan_mask: 0,
859 #[cfg(not(any(esp32, esp32s2)))]
860 total_chan: 0,
861 #[cfg(not(any(esp32, esp32s2)))]
862 left_align: false,
863 #[cfg(not(any(esp32, esp32s2)))]
864 big_edin: false,
865 #[cfg(not(any(esp32, esp32s2)))]
866 bit_order_msb: false,
867 #[cfg(not(any(esp32, esp32s2)))]
868 skip_msk: true,
869 }
870 }
871 }
872
873 /// PDM mode GPIO (general purpose input/output) transmit configuration.
874 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
875 pub struct PdmTxGpioConfig {
876 /// Whether the clock output is inverted.
877 pub(super) clk_inv: bool,
878 }
879
880 /// The maximum number of data output pins that can be used in PDM mode.
881 ///
882 /// This is 1 on the ESP32, and 2 on the ESP32-S3, ESP32-C3, ESP32-C6, and ESP32-H2.
883 #[cfg(esp32)]
884 pub const SOC_I2S_PDM_MAX_TX_LINES: usize = 1;
885
886 /// The maximum number of data input pins that can be used in PDM mode.
887 ///
888 /// This is 1 on the ESP32, and 2 on the ESP32-S3, ESP32-C3, ESP32-C6, and ESP32-H2.
889 #[cfg(any(esp32s3, esp32c3, esp32c6, esp32h2))]
890 pub const SOC_I2S_PDM_MAX_TX_LINES: usize = 2;
891
892 impl PdmTxGpioConfig {
893 /// Create a new PDM mode GPIO transmit configuration with the specified inversion flag for the clock output.
894 #[inline(always)]
895 pub fn new(clk_inv: bool) -> Self {
896 Self { clk_inv }
897 }
898
899 /// Set the clock inversion flag on this PDM GPIO transmit configuration.
900 #[inline(always)]
901 pub fn clk_invert(mut self, clk_inv: bool) -> Self {
902 self.clk_inv = clk_inv;
903 self
904 }
905
906 /// Convert to the ESP-IDF SDK `i2s_pdm_tx_gpio_config_t` representation.
907 #[cfg(esp_idf_soc_i2s_hw_version_1)]
908 pub(crate) fn as_sdk<'d>(
909 &self,
910 clk: impl OutputPin + 'd,
911 dout: impl OutputPin + 'd,
912 ) -> i2s_pdm_tx_gpio_config_t {
913 let invert_flags = i2s_pdm_tx_gpio_config_t__bindgen_ty_1 {
914 _bitfield_1: i2s_pdm_tx_gpio_config_t__bindgen_ty_1::new_bitfield_1(
915 self.clk_inv as u32,
916 ),
917 ..Default::default()
918 };
919 i2s_pdm_tx_gpio_config_t {
920 clk: clk.pin() as _,
921 dout: dout.pin() as _,
922 invert_flags,
923 }
924 }
925
926 /// Convert to the ESP-IDF SDK `i2s_pdm_tx_gpio_config_t` representation.
927 #[cfg(esp_idf_soc_i2s_hw_version_2)]
928 pub(crate) fn as_sdk<'d>(
929 &self,
930 clk: impl OutputPin + 'd,
931 dout: impl OutputPin + 'd,
932 dout2: Option<impl OutputPin + 'd>,
933 ) -> i2s_pdm_tx_gpio_config_t {
934 let invert_flags = i2s_pdm_tx_gpio_config_t__bindgen_ty_1 {
935 _bitfield_1: i2s_pdm_tx_gpio_config_t__bindgen_ty_1::new_bitfield_1(
936 self.clk_inv as u32,
937 ),
938 ..Default::default()
939 };
940 let dout2 = if let Some(dout2) = dout2 {
941 dout2.pin() as _
942 } else {
943 -1
944 };
945
946 i2s_pdm_tx_gpio_config_t {
947 clk: clk.pin() as _,
948 dout: dout.pin() as _,
949 dout2,
950 invert_flags,
951 }
952 }
953 }
954
955 /// I2S pulse density modulation (PDM) transmit line mode
956 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
957 pub enum PdmTxLineMode {
958 /// Standard PDM format output: left and right slot data on a single line.
959 #[default]
960 OneLineCodec,
961
962 /// PDM DAC format output: left or right slot data on a single line.
963 OneLineDac,
964
965 /// PDM DAC format output: left and right slot data on separate lines.
966 TwoLineDac,
967 }
968
969 impl PdmTxLineMode {
970 /// Convert this to the ESP-IDF SDK `i2s_pdm_tx_line_mode_t` representation.
971 #[cfg(esp_idf_soc_i2s_hw_version_2)]
972 #[inline(always)]
973 pub(super) fn as_sdk(&self) -> i2s_pdm_tx_line_mode_t {
974 match self {
975 Self::OneLineCodec => i2s_pdm_tx_line_mode_t_I2S_PDM_TX_ONE_LINE_CODEC,
976 Self::OneLineDac => i2s_pdm_tx_line_mode_t_I2S_PDM_TX_ONE_LINE_DAC,
977 Self::TwoLineDac => i2s_pdm_tx_line_mode_t_I2S_PDM_TX_TWO_LINE_DAC,
978 }
979 }
980 }
981
982 /// PDM mode channel transmit slot configuration.
983 ///
984 /// # Note
985 /// The `slot_mode` and `line_mode` (microcontrollers new than ESP32) or `slot_mask` (ESP32) cause data to be
986 /// interpreted in different ways, as noted below.
987 ///
988 /// Assuming the buffered data contains the following samples (where a sample may be 1, 2, 3, or 4 bytes, depending
989 /// on `data_bit_width`):
990 ///
991 /// | **`d[0]`** | **`d[1]`** | **`d[2]`** | **`d[3]`** | **`d[4]`** | **`d[5]`** | **`d[6]`** | **`d[7]`** |
992 /// |------------|------------|------------|------------|------------|------------|------------|------------|
993 /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
994 ///
995 /// The actual data on the line will be:
996 ///
997 /// ## All microcontrollers except ESP32
998 /// <table>
999 /// <thead>
1000 /// <tr><th><code>line_mode</code></th><th><code>slot_mode</code></th><th>Line</th><th colspan=8>Transmitted Data</th></tr>
1001 /// <tr><th></th><th></th><th></th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th></tr>
1002 /// </thead>
1003 /// <tbody>
1004 /// <tr><td rowspan=2><code>OneLineCodec</code></td><td><code>Mono</code></td> <td>dout</td><td>11</td><td><font color="red">0</font></td><td>12</td><td><font color="red">0</font></td><td>13</td><td><font color="red">0</font></td><td>14</td><td><font color="red">0</font></td></tr>
1005 /// <tr> <td><code>Stereo</code></td><td>dout</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td></tr>
1006 /// <tr><td><code>OneLineDac</code></td> <td><code>Mono</code></td> <td>dout</td><td>11</td><td>11</td><td>12</td><td>12</td><td>13</td><td>13</td><td>14</td><td>14</td></tr>
1007 /// <tr><td rowspan=4><code>TwoLineDac</code></td> <td rowspan=2><code>Mono</code></td><td>dout</td><td>12</td><td>12</td><td>14</td><td>14</td><td>16</td><td>16</td><td>18</td><td>18</td></tr>
1008 /// <tr><td>dout2</td><td><font color="red">0</font></td><td><font color="red">0</font></td><td><font color="red">0</font></td><td><font color="red">0</font></td><td><font color="red">0</font></td><td><font color="red">0</font></td><td><font color="red">0</font></td><td><font color="red">0</font></td></tr>
1009 /// <tr><td rowspan=2><code>Stereo</code></td><td>dout</td><td>12</td><td>12</td><td>14</td><td>14</td><td>16</td><td>16</td><td>18</td><td>18</td></tr>
1010 /// <tr><td>dout2</td><td>11</td><td>11</td><td>13</td><td>13</td><td>15</td><td>15</td><td>17</td><td>17</td></tr>
1011 /// </tbody>
1012 /// </table>
1013 ///
1014 /// ## ESP32
1015 /// <table>
1016 /// <thead>
1017 /// <tr><th><code>slot_mode</code></th><th><code>slot_mask</code></th><th colspan=8>Transmitted Data</th></tr>
1018 /// <tr><th></th><th></th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th></tr>
1019 /// </thead>
1020 /// <tbody>
1021 /// <tr><td rowspan=3><code>Mono</code></td> <td><code>Left</code></td> <td>11</td><td><font color="red">0</font></td><td>12</td><td><font color="red">0</font></td><td>13</td><td><font color="red">0</font></td><td>14</td><td><font color="red">0</font></td></tr>
1022 /// <tr> <td><code>Right</code></td><td><font color="red">0</font></td><td>11</td><td><font color="red">0</font></td><td>12</td><td><font color="red">0</font></td><td>13</td><td><font color="red">0</font></td><td>14</td></tr>
1023 /// <tr> <td><code>Both</code></td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td></tr>
1024 /// <tr><td rowspan=3><code>Mono</code></td> <td><code>Left</code></td><td>11</td><td>11</td><td>13</td><td>13</td><td>15</td><td>15</td><td>17</td><td>17</td></tr>
1025 /// <tr> <td><code>Right</code></td><td>12</td><td>12</td><td>14</td><td>14</td><td>16</td><td>16</td><td>18</td><td>18</td></tr>
1026 /// <tr> <td><code>Both</code></td> <td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td></tr>
1027 /// </tbody>
1028 /// </table>
1029 ///
1030 /// Modes combinations other than [`SlotMode::Mono`]/[`PdmSlotMask::Both`],
1031 /// [`SlotMode::Stereo`]/[`PdmSlotMask::Left`], and [`SlotMode::Stereo`]/[`PdmSlotMask::Right`] are unlikely to be
1032 /// useful since it requires precise demutiplexing on the bit stream based on the WS clock.
1033 ///
1034 /// For details, refer to the
1035 /// _ESP-IDF Programming Guide_ PDM Tx Usage details for your specific microcontroller:
1036 /// * [ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#pdm-tx-usage)
1037 /// * [ESP32-S3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#pdm-tx-usage)
1038 /// * [ESP32-C3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#pdm-tx-usage)
1039 /// * [ESP32-C6](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#pdm-tx-usage)
1040 /// * [ESP32-H2](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#pdm-tx-usage)
1041 #[derive(Clone, Copy, Debug, PartialEq)]
1042 pub struct PdmTxSlotConfig {
1043 // data_bit_width and slot_bit_width are omitted; they are always 16 bits.
1044 /// Mono or stereo mode operation.
1045 pub(super) slot_mode: SlotMode,
1046
1047 /// Slot mask to choose the left or right slot.
1048 #[cfg(not(esp_idf_soc_i2s_hw_version_2))]
1049 pub(super) slot_mask: PdmSlotMask,
1050
1051 /// Sigma-delta filter prescale.
1052 sd_prescale: u32,
1053
1054 /// Sigma-delta filter saling value.
1055 sd_scale: PdmSignalScale,
1056
1057 /// High-pass filter scaling value.
1058 hp_scale: PdmSignalScale,
1059
1060 /// Low-pass filter scaling value
1061 lp_scale: PdmSignalScale,
1062
1063 /// Sinc-filter scaling value.
1064 sinc_scale: PdmSignalScale,
1065
1066 /// PDM transmit line mode.
1067 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1068 line_mode: PdmTxLineMode,
1069
1070 /// High-pass filter enable
1071 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1072 hp_enable: bool,
1073
1074 /// High-pass filter cutoff frequence.
1075 /// The range of this is 23.3Hz to 185Hz.
1076 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1077 hp_cutoff_freq: f32,
1078
1079 /// Sigma-delta filter dither.
1080 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1081 sd_dither: u32,
1082
1083 /// Sigma-delta filter dither 2.
1084 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1085 sd_dither2: u32,
1086 }
1087
1088 impl Default for PdmTxSlotConfig {
1089 #[inline(always)]
1090 fn default() -> Self {
1091 Self::from_slot_mode(SlotMode::Stereo)
1092 }
1093 }
1094
1095 // We don't care about NaN in hp_cutoff_freq; go ahead and force it to be Eq.
1096 impl Eq for PdmTxSlotConfig {}
1097
1098 impl PdmTxSlotConfig {
1099 /// Configure the PDM mode channel transmit slot configuration for the specified slot mode in 2 slots.
1100 ///
1101 /// This sets the sigma-delta, low-pass, and sinc scaling to None.
1102 ///
1103 /// For hardware version 1, the high-pass filter scaling is set to None.
1104 ///
1105 /// For hardware version 2, the high-pass filter is enabled, scaled to dividing by 2 and set to 35.5 Hz.
1106 #[inline(always)]
1107 pub fn from_slot_mode(slot_mode: SlotMode) -> Self {
1108 Self {
1109 slot_mode,
1110 #[cfg(not(esp_idf_soc_i2s_hw_version_2))]
1111 slot_mask: PdmSlotMask::Both,
1112 sd_prescale: 0,
1113 sd_scale: PdmSignalScale::None,
1114 #[cfg(not(esp_idf_soc_i2s_hw_version_2))]
1115 hp_scale: PdmSignalScale::None,
1116 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1117 hp_scale: PdmSignalScale::Div2,
1118 lp_scale: PdmSignalScale::None,
1119 sinc_scale: PdmSignalScale::None,
1120 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1121 line_mode: PdmTxLineMode::OneLineCodec,
1122 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1123 hp_enable: true,
1124 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1125 hp_cutoff_freq: 32.5,
1126 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1127 sd_dither: 0,
1128 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1129 sd_dither2: 1,
1130 }
1131 }
1132
1133 /// Sets the slot mode on this PDM transmit slot configuration.
1134 #[inline(always)]
1135 pub fn slot_mode(mut self, slot_mode: SlotMode) -> Self {
1136 self.slot_mode = slot_mode;
1137 self
1138 }
1139
1140 /// Sets the slot mask on this PDM transmit slot configuration.
1141 #[cfg(esp_idf_soc_i2s_hw_version_1)]
1142 #[cfg_attr(
1143 feature = "nightly",
1144 doc(cfg(all(esp32, not(esp_idf_version_major = "4"))))
1145 )]
1146 #[inline(always)]
1147 pub fn slot_mask(mut self, slot_mask: PdmSlotMask) -> Self {
1148 self.slot_mask = slot_mask;
1149 self
1150 }
1151
1152 /// Sets the sigma-delta filter prescale on this PDM transmit slot configuration.
1153 #[inline(always)]
1154 pub fn sd_prescale(mut self, sd_prescale: u32) -> Self {
1155 self.sd_prescale = sd_prescale;
1156 self
1157 }
1158
1159 /// Sets the sigma-delta filter scaling on this PDM transmit slot configuration.
1160 #[inline(always)]
1161 pub fn sd_scale(mut self, sd_scale: PdmSignalScale) -> Self {
1162 self.sd_scale = sd_scale;
1163 self
1164 }
1165
1166 /// Sets the high-pass filter scaling on this PDM transmit slot configuration.
1167 #[inline(always)]
1168 pub fn hp_scale(mut self, hp_scale: PdmSignalScale) -> Self {
1169 self.hp_scale = hp_scale;
1170 self
1171 }
1172
1173 /// Sets the low-pass filter scaling on this PDM transmit slot configuration.
1174 #[inline(always)]
1175 pub fn lp_scale(mut self, lp_scale: PdmSignalScale) -> Self {
1176 self.lp_scale = lp_scale;
1177 self
1178 }
1179
1180 /// Sets the sinc filter scaling on this PDM transmit slot configuration.
1181 #[inline(always)]
1182 pub fn sinc_scale(mut self, sinc_scale: PdmSignalScale) -> Self {
1183 self.sinc_scale = sinc_scale;
1184 self
1185 }
1186
1187 /// Sets the PDM transmit line mode on this PDM transmit slot configuration.
1188 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1189 #[cfg_attr(
1190 feature = "nightly",
1191 doc(cfg(all(
1192 any(esp32s3, esp32c3, esp32c6, esp32h2),
1193 not(esp_idf_version_major = "4")
1194 )))
1195 )]
1196 #[inline(always)]
1197 pub fn line_mode(mut self, line_mode: PdmTxLineMode) -> Self {
1198 self.line_mode = line_mode;
1199 self
1200 }
1201
1202 /// Sets the high-pass filter enable on this PDM transmit slot configuration.
1203 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1204 #[cfg_attr(
1205 feature = "nightly",
1206 doc(cfg(all(
1207 any(esp32s3, esp32c3, esp32c6, esp32h2),
1208 not(esp_idf_version_major = "4")
1209 )))
1210 )]
1211 #[inline(always)]
1212 pub fn hp_enable(mut self, hp_enable: bool) -> Self {
1213 self.hp_enable = hp_enable;
1214 self
1215 }
1216
1217 /// Sets the high-pass filter cutoff frequency on this PDM transmit slot configuration.
1218 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1219 #[cfg_attr(
1220 feature = "nightly",
1221 doc(cfg(all(
1222 any(esp32s3, esp32c3, esp32c6, esp32h2),
1223 not(esp_idf_version_major = "4")
1224 )))
1225 )]
1226 #[inline(always)]
1227 pub fn hp_cutoff_freq(mut self, hp_cutoff_freq: f32) -> Self {
1228 self.hp_cutoff_freq = hp_cutoff_freq;
1229 self
1230 }
1231
1232 /// Sets the sigma-delta filter dither on this PDM transmit slot configuration.
1233 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1234 #[cfg_attr(
1235 feature = "nightly",
1236 doc(cfg(all(
1237 any(esp32s3, esp32c3, esp32c6, esp32h2),
1238 not(esp_idf_version_major = "4")
1239 )))
1240 )]
1241 #[inline(always)]
1242 pub fn sd_dither(mut self, sd_dither: u32, sd_dither2: u32) -> Self {
1243 self.sd_dither = sd_dither;
1244 self.sd_dither2 = sd_dither2;
1245 self
1246 }
1247
1248 /// Convert this to the ESP-IDF SDK `i2s_pdm_tx_slot_config_t` type.
1249 #[cfg(not(esp_idf_version_major = "4"))]
1250 #[inline(always)]
1251 #[allow(clippy::needless_update)]
1252 pub(super) fn as_sdk(&self) -> i2s_pdm_tx_slot_config_t {
1253 i2s_pdm_tx_slot_config_t {
1254 data_bit_width: DataBitWidth::Bits16.as_sdk(),
1255 slot_bit_width: SlotBitWidth::Bits16.as_sdk(),
1256 slot_mode: self.slot_mode.as_sdk(),
1257 #[cfg(esp_idf_soc_i2s_hw_version_1)]
1258 slot_mask: self.slot_mask.as_sdk(),
1259 sd_prescale: self.sd_prescale,
1260 sd_scale: self.sd_scale.as_sdk(),
1261 hp_scale: self.hp_scale.as_sdk(),
1262 lp_scale: self.lp_scale.as_sdk(),
1263 sinc_scale: self.sinc_scale.as_sdk(),
1264 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1265 line_mode: self.line_mode.as_sdk(),
1266 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1267 hp_en: self.hp_enable,
1268 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1269 hp_cut_off_freq_hz: self.hp_cutoff_freq,
1270 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1271 sd_dither: self.sd_dither,
1272 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1273 sd_dither2: self.sd_dither2,
1274 // i2s_pdm_data_fmt_t::I2S_PDM_DATA_FMT_PCM
1275 ..Default::default()
1276 }
1277 }
1278 }
1279}
1280
1281#[cfg(esp_idf_soc_i2s_supports_pdm_rx)]
1282#[cfg_attr(
1283 feature = "nightly",
1284 doc(cfg(all(any(esp32, esp32s3), not(esp_idf_version_major = "4"))))
1285)]
1286impl<'d> I2sDriver<'d, I2sRx> {
1287 /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive
1288 /// channel open.
1289 #[allow(clippy::too_many_arguments)]
1290 pub fn new_pdm_rx<I2S: I2s + 'd>(
1291 _i2s: I2S,
1292 rx_cfg: &config::PdmRxConfig,
1293 clk: impl OutputPin + 'd,
1294 din: impl InputPin + 'd,
1295 ) -> Result<Self, EspError> {
1296 let chan_cfg = rx_cfg.channel_cfg.as_sdk(I2S::port());
1297
1298 let this = Self::internal_new::<I2S>(&chan_cfg, true, false)?;
1299
1300 let rx_cfg = rx_cfg.as_sdk(clk, din);
1301
1302 // Safety: rx.chan_handle is a valid, non-null i2s_chan_handle_t,
1303 // and &rx_cfg is a valid pointer to an i2s_pdm_rx_config_t.
1304 unsafe {
1305 // Open the RX channel.
1306 esp!(esp_idf_sys::i2s_channel_init_pdm_rx_mode(
1307 this.rx_handle,
1308 &rx_cfg
1309 ))?;
1310 }
1311
1312 Ok(this)
1313 }
1314
1315 /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive
1316 /// channel open using multiple DIN pins to receive data.
1317 #[cfg(not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")))]
1318 #[cfg_attr(
1319 feature = "nightly",
1320 doc(cfg(not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0"))))
1321 )]
1322 #[allow(clippy::too_many_arguments)]
1323 pub fn new_pdm_rx_multi<I2S, DIN, const DINC: usize>(
1324 _i2s: I2S,
1325 rx_cfg: &config::PdmRxConfig,
1326 clk: impl OutputPin + 'd,
1327 dins: [DIN; DINC],
1328 ) -> Result<Self, EspError>
1329 where
1330 I2S: I2s + 'd,
1331 DIN: InputPin + 'd,
1332 {
1333 let chan_cfg = rx_cfg.channel_cfg.as_sdk(I2S::port());
1334
1335 let this = Self::internal_new::<I2S>(&chan_cfg, true, true)?;
1336
1337 // Create the channel configuration.
1338 let rx_cfg = rx_cfg.as_sdk_multi(clk, &dins);
1339
1340 // Safety: rx.chan_handle is a valid, non-null i2s_chan_handle_t,
1341 // and &rx_cfg is a valid pointer to an i2s_pdm_rx_config_t.
1342 unsafe {
1343 // Open the RX channel.
1344 esp!(esp_idf_sys::i2s_channel_init_pdm_rx_mode(
1345 this.rx_handle,
1346 &rx_cfg
1347 ))?;
1348 }
1349
1350 Ok(this)
1351 }
1352}
1353
1354#[cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4"))]
1355#[cfg_attr(
1356 feature = "nightly",
1357 doc(cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4")))
1358)]
1359impl<'d> I2sDriver<'d, I2sRx> {
1360 /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive
1361 /// channel open.
1362 #[allow(clippy::too_many_arguments)]
1363 pub fn new_pdm_rx<I2S: I2s + 'd>(
1364 _i2s: I2S,
1365 rx_cfg: &config::PdmRxConfig,
1366 clk: impl OutputPin + 'd,
1367 din: impl InputPin + 'd,
1368 ) -> Result<Self, EspError> {
1369 let driver_cfg = rx_cfg.as_sdk();
1370
1371 let this = Self::internal_new::<I2S>(&driver_cfg)?;
1372
1373 // Set the rate and downsampling configuration.
1374 let downsample = rx_cfg.clk_cfg.downsample_mode.as_sdk();
1375 unsafe {
1376 esp!(i2s_set_pdm_rx_down_sample(I2S::port(), downsample))?;
1377 }
1378
1379 // Set the pin configuration.
1380 let pin_cfg = i2s_pin_config_t {
1381 bck_io_num: clk.pin() as _,
1382 data_in_num: din.pin() as _,
1383 data_out_num: -1,
1384 mck_io_num: -1,
1385 ws_io_num: -1,
1386 };
1387
1388 // Safety: &pin_cfg is a valid pointer to an i2s_pin_config_t.
1389 unsafe {
1390 esp!(i2s_set_pin(I2S::port(), &pin_cfg))?;
1391 }
1392
1393 Ok(this)
1394 }
1395}
1396
1397#[cfg(esp_idf_soc_i2s_supports_pdm_tx)]
1398#[cfg_attr(
1399 feature = "nightly",
1400 doc(cfg(all(
1401 any(esp32, esp32s3, esp32c3, esp32c6, esp32h2),
1402 not(esp_idf_version_major = "4")
1403 )))
1404)]
1405impl<'d> I2sDriver<'d, I2sTx> {
1406 /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the transmit
1407 /// channel open.
1408 #[allow(clippy::too_many_arguments)]
1409 pub fn new_pdm_tx<I2S: I2s + 'd>(
1410 _i2s: I2S,
1411 tx_cfg: &config::PdmTxConfig,
1412 clk: impl OutputPin + 'd,
1413 dout: impl OutputPin + 'd,
1414 #[cfg(esp_idf_soc_i2s_hw_version_2)] dout2: Option<impl OutputPin + 'd>,
1415 ) -> Result<Self, EspError> {
1416 let chan_cfg = tx_cfg.channel_cfg.as_sdk(I2S::port());
1417
1418 let this = Self::internal_new::<I2S>(&chan_cfg, false, true)?;
1419
1420 // Create the channel configuration.
1421 let tx_cfg = tx_cfg.as_sdk(
1422 clk,
1423 dout,
1424 #[cfg(esp_idf_soc_i2s_hw_version_2)]
1425 dout2,
1426 );
1427
1428 // Safety: tx.chan_handle is a valid, non-null i2s_chan_handle_t,
1429 // and &tx_cfg is a valid pointer to an i2s_pdm_tx_config_t.
1430 unsafe {
1431 // Open the TX channel.
1432 esp!(esp_idf_sys::i2s_channel_init_pdm_tx_mode(
1433 this.tx_handle,
1434 &tx_cfg
1435 ))?;
1436 }
1437
1438 Ok(this)
1439 }
1440}
1441
1442#[cfg(all(
1443 esp_idf_version_major = "4",
1444 any(esp32, esp32s3, esp32c3, esp32c6, esp32h2)
1445))]
1446#[cfg_attr(
1447 feature = "nightly",
1448 doc(cfg(all(
1449 any(esp32, esp32s3, esp32c3, esp32c6, esp32h2),
1450 esp_idf_version_major = "4"
1451 )))
1452)]
1453impl<'d> I2sDriver<'d, I2sTx> {
1454 /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the transmit
1455 /// channel open.
1456 #[allow(clippy::too_many_arguments)]
1457 pub fn new_pdm_tx<I2S: I2s + 'd>(
1458 _i2s: I2S,
1459 tx_cfg: &config::PdmTxConfig,
1460 clk: impl OutputPin + 'd,
1461 dout: impl OutputPin + 'd,
1462 ) -> Result<Self, EspError> {
1463 let driver_cfg = tx_cfg.as_sdk();
1464
1465 let this = Self::internal_new::<I2S>(&driver_cfg)?;
1466
1467 // Set the upsampling configuration.
1468 let upsample = tx_cfg.clk_cfg.as_sdk();
1469 unsafe {
1470 esp!(esp_idf_sys::i2s_set_pdm_tx_up_sample(
1471 I2S::port(),
1472 &upsample
1473 ))?;
1474 }
1475
1476 // Set the pin configuration.
1477 let pin_cfg = i2s_pin_config_t {
1478 bck_io_num: clk.pin() as _,
1479 data_in_num: -1,
1480 data_out_num: dout.pin() as _,
1481 mck_io_num: -1,
1482 ws_io_num: -1,
1483 };
1484
1485 // Safety: &pin_cfg is a valid pointer to an i2s_pin_config_t.
1486 unsafe {
1487 esp!(i2s_set_pin(I2S::port(), &pin_cfg))?;
1488 }
1489
1490 Ok(this)
1491 }
1492}