Skip to main content

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}