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