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}