Skip to main content

esp_hal/i2s/pdm/
mod.rs

1//! PDM (pulse-density modulation) configuration for the I2S master driver.
2//!
3//! Default configurations and clock calculations follow Espressif's I2S PDM driver
4//! (<https://github.com/espressif/esp-idf/tree/master/components/esp_driver_i2s>).
5
6mod clock;
7#[cfg(not(i2s_version = "1"))]
8mod hp_filter;
9#[cfg_attr(i2s_version = "1", path = "regs_v1.rs")]
10#[cfg_attr(i2s_version = "2", path = "regs_v2.rs")]
11#[cfg_attr(i2s_version = "3", path = "regs_v2.rs")]
12mod ll;
13
14use super::master::{ConfigError, Instance};
15use crate::{i2s::master::Info, time::Rate};
16
17/// PDM configuration errors.
18#[non_exhaustive]
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21pub enum PdmError {
22    /// PDM is not supported on this I2S peripheral instance.
23    UnsupportedInstance,
24    /// PCM format requested but hardware PCM2PDM/PDM2PCM is unavailable.
25    PcmFormatUnsupported,
26    /// PDM clock configuration is invalid.
27    InvalidClock,
28    /// PDM slot mask selects no active channels.
29    InvalidSlotMask,
30    /// PDM config must enable at least one of TX or RX.
31    DirectionMissing,
32    /// Simultaneous PDM TX and RX is not supported.
33    DuplexUnsupported,
34    /// PDM data line index is not available on this chip.
35    InvalidLine,
36}
37
38impl core::error::Error for PdmError {}
39
40impl core::fmt::Display for PdmError {
41    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42        match self {
43            Self::UnsupportedInstance => {
44                write!(f, "PDM mode is not supported on this I2S instance")
45            }
46            Self::PcmFormatUnsupported => write!(
47                f,
48                "PCM PDM format is not supported on this I2S instance; use raw PDM format"
49            ),
50            Self::InvalidClock => write!(f, "PDM clock configuration is out of supported range"),
51            Self::InvalidSlotMask => {
52                write!(f, "PDM slot mask must select at least one active slot")
53            }
54            Self::DirectionMissing => {
55                write!(f, "PDM configuration must include TX and/or RX settings")
56            }
57            Self::DuplexUnsupported => {
58                write!(f, "PDM full duplex (TX and RX together) is not supported")
59            }
60            Self::InvalidLine => write!(f, "PDM data line index is not available on this chip"),
61        }
62    }
63}
64
65/// A peripheral singleton that supports PDM mode.
66pub trait PdmInstance: Instance {}
67
68for_each_i2s! {
69    (
70        $instance:ident, $sys:ident, $mclk:ident,
71        $bclk:ident, $ws:ident, $bclk_rx:ident, $ws_rx:ident,
72        $dout:tt, $din:tt, true, $pdm_rx:literal, $pcm2pdm:literal, $pdm2pcm:literal
73    ) => {
74        impl PdmInstance for crate::peripherals::$instance<'_> {}
75    };
76    (
77        $instance:ident, $sys:ident, $mclk:ident,
78        $bclk:ident, $ws:ident, $bclk_rx:ident, $ws_rx:ident,
79        $dout:tt, $din:tt, false, true, $pcm2pdm:literal, $pdm2pcm:literal
80    ) => {
81        impl PdmInstance for crate::peripherals::$instance<'_> {}
82    };
83}
84
85/// PDM data format: PCM samples in software vs raw PDM bitstream.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
87#[cfg_attr(feature = "defmt", derive(defmt::Format))]
88pub enum PdmDataFormat {
89    /// Hardware filter converts between PCM and PDM when supported.
90    #[default]
91    Pcm,
92    /// Raw PDM samples; no hardware PCM conversion.
93    Raw,
94}
95
96/// Mono or stereo slot mode (fixed two hardware slots).
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99pub enum PdmSlotMode {
100    /// Single active slot (left by default).
101    #[default]
102    Mono,
103    /// Both slots active (stereo).
104    Stereo,
105}
106
107/// Active PDM slot selection.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109#[cfg_attr(feature = "defmt", derive(defmt::Format))]
110pub struct PdmSlotMask(u16);
111
112impl PdmSlotMask {
113    /// Left slot only.
114    pub const LEFT: Self = Self(1 << 1);
115    /// Right slot only.
116    pub const RIGHT: Self = Self(1 << 0);
117    /// Both slots active.
118    pub const BOTH: Self = Self(0b11);
119
120    #[cfg(all(i2s_supports_pdm_rx, not(esp32)))]
121    /// Line 0, left slot.
122    pub const LINE0_LEFT: Self = Self(1 << 1);
123    #[cfg(all(i2s_supports_pdm_rx, not(esp32)))]
124    /// Line 0, right slot.
125    pub const LINE0_RIGHT: Self = Self(1 << 0);
126    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
127    /// Line 1, left slot.
128    pub const LINE1_LEFT: Self = Self(1 << 3);
129    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
130    /// Line 1, right slot.
131    pub const LINE1_RIGHT: Self = Self(1 << 2);
132    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
133    /// Line 2, left slot.
134    pub const LINE2_LEFT: Self = Self(1 << 5);
135    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
136    /// Line 2, right slot.
137    pub const LINE2_RIGHT: Self = Self(1 << 4);
138    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
139    /// Line 3, left slot.
140    pub const LINE3_LEFT: Self = Self(1 << 7);
141    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
142    /// Line 3, right slot.
143    pub const LINE3_RIGHT: Self = Self(1 << 6);
144
145    /// Create a mask from raw slot bits.
146    pub const fn from_bits(bits: u16) -> Self {
147        Self(bits)
148    }
149
150    /// Raw slot mask bits.
151    pub const fn bits(self) -> u16 {
152        self.0
153    }
154
155    /// Default mask for the given mono/stereo mode.
156    pub fn for_mode(mode: PdmSlotMode) -> Self {
157        match mode {
158            PdmSlotMode::Mono => Self::LEFT,
159            PdmSlotMode::Stereo => Self::BOTH,
160        }
161    }
162}
163
164/// PDM RX downsample rate (PDM2PCM path).
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
166#[cfg_attr(feature = "defmt", derive(defmt::Format))]
167pub enum PdmDownsampleRate {
168    /// 64x downsample (DSR 8s).
169    #[default]
170    Dsr8s,
171    /// 128x downsample (DSR 16s).
172    Dsr16s,
173}
174
175/// PDM TX sigma-delta filter scaling.
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
177#[cfg_attr(feature = "defmt", derive(defmt::Format))]
178pub enum PdmSigScaling {
179    /// Divide input by 2.
180    Div2,
181    /// Multiply input by 1.
182    #[default]
183    Mul1,
184    /// Multiply input by 2.
185    Mul2,
186    /// Multiply input by 4.
187    Mul4,
188}
189
190impl PdmSigScaling {
191    pub(crate) fn to_register(self) -> u8 {
192        match self {
193            Self::Div2 => 0,
194            Self::Mul1 => 1,
195            Self::Mul2 => 2,
196            Self::Mul4 => 3,
197        }
198    }
199}
200
201/// PDM TX line routing (HW v2+).
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
203#[cfg_attr(feature = "defmt", derive(defmt::Format))]
204pub enum PdmTxLineMode {
205    /// Single-line codec output (default).
206    #[default]
207    OneLineCodec,
208    /// Single-line DAC output.
209    OneLineDac,
210    /// Two-line DAC output (stereo).
211    TwoLineDac,
212}
213
214/// PDM TX clock configuration.
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
216#[cfg_attr(feature = "defmt", derive(defmt::Format))]
217pub struct PdmTxClockConfig {
218    /// Target PCM sample rate.
219    pub sample_rate: Rate,
220    /// Upsampling factor numerator (`fp`).
221    pub up_sample_fp: u32,
222    /// Upsampling factor denominator (`fs`).
223    pub up_sample_fs: u32,
224    /// Bit clock divider.
225    pub bclk_div: u32,
226}
227
228/// PDM TX slot / filter configuration.
229#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
230#[cfg_attr(feature = "defmt", derive(defmt::Format))]
231#[non_exhaustive]
232pub struct PdmTxSlotConfig {
233    /// Mono or stereo slot mode.
234    pub slot_mode: PdmSlotMode,
235    /// PCM or raw PDM data format.
236    pub data_format: PdmDataFormat,
237    /// Sigma-delta prescale value.
238    pub sd_prescale: u8,
239    /// Sigma-delta filter input scaling.
240    pub sd_scale: PdmSigScaling,
241    /// High-pass filter input scaling.
242    pub hp_scale: PdmSigScaling,
243    /// Low-pass filter input scaling.
244    pub lp_scale: PdmSigScaling,
245    /// Sinc filter input scaling.
246    pub sinc_scale: PdmSigScaling,
247    #[cfg(not(i2s_version = "1"))]
248    /// Output line routing mode.
249    pub line_mode: PdmTxLineMode,
250    /// Enable the TX high-pass filter.
251    pub hp_en: bool,
252    /// High-pass filter cut-off frequency in Hz.
253    pub hp_cut_off_freq_hz: f32,
254    #[cfg(not(i2s_version = "1"))]
255    /// Sigma-delta dither bit 0.
256    pub sd_dither: u8,
257    #[cfg(not(i2s_version = "1"))]
258    /// Sigma-delta dither bit 1.
259    pub sd_dither2: u8,
260    #[cfg(i2s_version = "1")]
261    /// Active TX slot mask (ESP32 only).
262    pub slot_mask: PdmSlotMask,
263}
264
265impl PdmTxSlotConfig {
266    fn codec_pcm_default(mode: PdmSlotMode) -> Self {
267        Self {
268            slot_mode: mode,
269            data_format: PdmDataFormat::Pcm,
270            sd_prescale: 0,
271            sd_scale: PdmSigScaling::Mul1,
272            hp_scale: PdmSigScaling::Div2,
273            lp_scale: PdmSigScaling::Mul1,
274            sinc_scale: PdmSigScaling::Mul1,
275            #[cfg(not(i2s_version = "1"))]
276            line_mode: PdmTxLineMode::OneLineCodec,
277            hp_en: true,
278            hp_cut_off_freq_hz: 35.5,
279            #[cfg(not(i2s_version = "1"))]
280            sd_dither: 0,
281            #[cfg(not(i2s_version = "1"))]
282            sd_dither2: 1,
283            #[cfg(i2s_version = "1")]
284            slot_mask: PdmSlotMask::BOTH,
285        }
286    }
287
288    #[cfg(not(i2s_version = "1"))]
289    fn dac_pcm_default(mode: PdmSlotMode) -> Self {
290        let mut cfg = Self::codec_pcm_default(mode);
291        cfg.hp_scale = PdmSigScaling::Mul1;
292        cfg.lp_scale = PdmSigScaling::Mul1;
293        cfg.sinc_scale = PdmSigScaling::Mul1;
294        #[cfg(not(i2s_version = "1"))]
295        {
296            cfg.line_mode = if mode == PdmSlotMode::Mono {
297                PdmTxLineMode::OneLineDac
298            } else {
299                PdmTxLineMode::TwoLineDac
300            };
301        }
302        cfg
303    }
304
305    fn raw_default(mode: PdmSlotMode) -> Self {
306        let mut cfg = Self::codec_pcm_default(mode);
307        cfg.data_format = PdmDataFormat::Raw;
308        cfg.hp_en = false;
309        cfg
310    }
311}
312
313/// PDM RX clock configuration.
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
315#[cfg_attr(feature = "defmt", derive(defmt::Format))]
316pub struct PdmRxClockConfig {
317    /// Target PCM sample rate.
318    pub sample_rate: Rate,
319
320    /// PDM2PCM downsample rate.
321    pub downsample_rate: PdmDownsampleRate,
322
323    /// Bit clock divider.
324    pub bclk_div: u32,
325}
326
327/// PDM RX slot configuration.
328#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
329#[cfg_attr(feature = "defmt", derive(defmt::Format))]
330#[non_exhaustive]
331pub struct PdmRxSlotConfig {
332    /// Mono or stereo slot mode.
333    pub slot_mode: PdmSlotMode,
334
335    /// Active RX slot mask.
336    pub slot_mask: PdmSlotMask,
337
338    /// PCM or raw PDM data format.
339    pub data_format: PdmDataFormat,
340
341    #[cfg(i2s_supports_pdm_rx_hp_filter)]
342    /// Enable the RX high-pass filter.
343    pub hp_en: bool,
344
345    #[cfg(i2s_supports_pdm_rx_hp_filter)]
346    /// High-pass filter cut-off frequency in Hz.
347    pub hp_cut_off_freq_hz: f32,
348
349    #[cfg(i2s_supports_pdm_rx_hp_filter)]
350    /// RX amplification factor (1–15).
351    pub amplify_num: u32,
352}
353
354impl PdmRxSlotConfig {
355    fn pcm_default(mode: PdmSlotMode) -> Self {
356        Self {
357            slot_mode: mode,
358            slot_mask: PdmSlotMask::for_mode(mode),
359            data_format: PdmDataFormat::Pcm,
360            #[cfg(i2s_supports_pdm_rx_hp_filter)]
361            hp_en: true,
362            #[cfg(i2s_supports_pdm_rx_hp_filter)]
363            hp_cut_off_freq_hz: 35.5,
364            #[cfg(i2s_supports_pdm_rx_hp_filter)]
365            amplify_num: 1,
366        }
367    }
368
369    fn raw_default(mode: PdmSlotMode) -> Self {
370        let mut cfg = Self::pcm_default(mode);
371        cfg.data_format = PdmDataFormat::Raw;
372        #[cfg(i2s_supports_pdm_rx_hp_filter)]
373        {
374            cfg.hp_en = false;
375        }
376        cfg
377    }
378}
379
380/// Full PDM TX unit configuration.
381#[derive(Debug, Clone, Copy, PartialEq)]
382#[cfg_attr(feature = "defmt", derive(defmt::Format))]
383pub struct PdmTxConfig {
384    /// TX clock settings.
385    pub clock: PdmTxClockConfig,
386
387    /// TX slot and filter settings.
388    pub slot: PdmTxSlotConfig,
389}
390
391impl PdmTxConfig {
392    /// Codec-line defaults (`I2S_PDM_TX_*_DEFAULT_CONFIG`).
393    pub fn new_codec_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
394        Self {
395            clock: PdmTxClockConfig::codec_default(sample_rate),
396            slot: default_tx_slot(mode),
397        }
398    }
399
400    /// Raw PDM TX defaults (no hardware PCM conversion).
401    pub fn new_raw_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
402        Self {
403            clock: PdmTxClockConfig::codec_default(sample_rate),
404            slot: PdmTxSlotConfig::raw_default(mode),
405        }
406    }
407
408    /// DAC-line defaults (`I2S_PDM_TX_*_DAC_DEFAULT_CONFIG`, HW v2+).
409    #[cfg(not(i2s_version = "1"))]
410    pub fn new_dac_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
411        Self {
412            clock: PdmTxClockConfig::dac_default(sample_rate),
413            slot: PdmTxSlotConfig::dac_pcm_default(mode),
414        }
415    }
416
417    /// Validate TX configuration against hardware capabilities.
418    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
419        if self.slot.data_format == PdmDataFormat::Pcm && !info.pcm2pdm {
420            return Err(PdmError::PcmFormatUnsupported);
421        }
422        if self.clock.up_sample_fs > 480 {
423            return Err(PdmError::InvalidClock);
424        }
425        Ok(())
426    }
427}
428
429fn default_tx_slot(mode: PdmSlotMode) -> PdmTxSlotConfig {
430    cfg_select! {
431        i2s_supports_pcm2pdm => PdmTxSlotConfig::codec_pcm_default(mode),
432        _ => PdmTxSlotConfig::raw_default(mode),
433    }
434}
435
436/// Full PDM RX unit configuration.
437#[derive(Debug, Clone, Copy, PartialEq)]
438#[cfg_attr(feature = "defmt", derive(defmt::Format))]
439pub struct PdmRxConfig {
440    /// RX clock settings.
441    pub clock: PdmRxClockConfig,
442    /// RX slot settings.
443    pub slot: PdmRxSlotConfig,
444}
445
446impl PdmRxConfig {
447    /// Default RX config; uses PCM when PDM2PCM is available, otherwise raw.
448    pub fn new_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
449        Self {
450            clock: PdmRxClockConfig::default(sample_rate),
451            slot: default_rx_slot(mode),
452        }
453    }
454
455    /// PCM RX defaults (requires hardware PDM2PCM support).
456    pub fn new_pcm_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
457        Self {
458            clock: PdmRxClockConfig::default(sample_rate),
459            slot: PdmRxSlotConfig::pcm_default(mode),
460        }
461    }
462
463    /// Raw PDM RX defaults (no hardware PCM conversion).
464    pub fn new_raw_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
465        Self {
466            clock: PdmRxClockConfig::default(sample_rate),
467            slot: PdmRxSlotConfig::raw_default(mode),
468        }
469    }
470
471    /// Validate RX configuration against hardware capabilities.
472    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
473        if self.slot.data_format == PdmDataFormat::Pcm && !info.pdm2pcm {
474            return Err(PdmError::PcmFormatUnsupported);
475        }
476        if self.slot.slot_mask.bits() == 0 {
477            return Err(PdmError::InvalidSlotMask);
478        }
479        Ok(())
480    }
481}
482
483fn default_rx_slot(mode: PdmSlotMode) -> PdmRxSlotConfig {
484    cfg_select! {
485        i2s_supports_pdm2pcm => PdmRxSlotConfig::pcm_default(mode),
486        _ => PdmRxSlotConfig::raw_default(mode),
487    }
488}
489
490/// PDM mode configuration (simplex TX and/or RX).
491#[derive(Debug, Clone, Copy, PartialEq)]
492#[cfg_attr(feature = "defmt", derive(defmt::Format))]
493pub struct PdmConfig {
494    /// Optional TX unit configuration.
495    pub tx: Option<PdmTxConfig>,
496    /// Optional RX unit configuration.
497    pub rx: Option<PdmRxConfig>,
498}
499
500impl PdmConfig {
501    /// PDM TX only (recommended simplex setup).
502    #[cfg(i2s_supports_pdm_tx)]
503    pub fn tx_only(tx: PdmTxConfig) -> Self {
504        Self {
505            tx: Some(tx),
506            rx: None,
507        }
508    }
509
510    /// PDM RX only (recommended simplex setup).
511    #[cfg(i2s_supports_pdm_rx)]
512    pub fn rx_only(rx: PdmRxConfig) -> Self {
513        Self {
514            tx: None,
515            rx: Some(rx),
516        }
517    }
518
519    /// Validate that exactly one direction is configured and that the settings
520    /// are valid for the given I2S instance.
521    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
522        if self.tx.is_none() && self.rx.is_none() {
523            return Err(PdmError::DirectionMissing);
524        }
525        if self.tx.is_some() && self.rx.is_some() {
526            return Err(PdmError::DuplexUnsupported);
527        }
528        if self.tx.is_some() && !info.pdm_tx {
529            return Err(PdmError::UnsupportedInstance);
530        }
531        if self.rx.is_some() && !info.pdm_rx {
532            return Err(PdmError::UnsupportedInstance);
533        }
534        if let Some(tx) = &self.tx {
535            tx.validate(info)?;
536        }
537        if let Some(rx) = &self.rx {
538            rx.validate(info)?;
539        }
540        Ok(())
541    }
542}
543
544pub(crate) fn configure_pdm(i2s: &Info, config: &PdmConfig) -> Result<(), ConfigError> {
545    ll::configure_pdm(i2s, config).map_err(ConfigError::Pdm)
546}