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    /// Creates a new 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    /// Divides 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    /// Source clock for the I2S module clock.
227    pub clock_source: crate::i2s::master::I2sClockSource,
228}
229
230/// PDM TX slot / filter configuration.
231#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
232#[cfg_attr(feature = "defmt", derive(defmt::Format))]
233#[non_exhaustive]
234pub struct PdmTxSlotConfig {
235    /// Mono or stereo slot mode.
236    pub slot_mode: PdmSlotMode,
237    /// PCM or raw PDM data format.
238    pub data_format: PdmDataFormat,
239    /// Sigma-delta prescale value.
240    pub sd_prescale: u8,
241    /// Sigma-delta filter input scaling.
242    pub sd_scale: PdmSigScaling,
243    /// High-pass filter input scaling.
244    pub hp_scale: PdmSigScaling,
245    /// Low-pass filter input scaling.
246    pub lp_scale: PdmSigScaling,
247    /// Sinc filter input scaling.
248    pub sinc_scale: PdmSigScaling,
249    #[cfg(not(i2s_version = "1"))]
250    /// Output line routing mode.
251    pub line_mode: PdmTxLineMode,
252    /// Enables the TX high-pass filter.
253    pub hp_en: bool,
254    /// High-pass filter cut-off frequency in Hz.
255    pub hp_cut_off_freq_hz: f32,
256    #[cfg(not(i2s_version = "1"))]
257    /// Sigma-delta dither bit 0.
258    pub sd_dither: u8,
259    #[cfg(not(i2s_version = "1"))]
260    /// Sigma-delta dither bit 1.
261    pub sd_dither2: u8,
262    #[cfg(i2s_version = "1")]
263    /// Active TX slot mask (ESP32 only).
264    pub slot_mask: PdmSlotMask,
265}
266
267impl PdmTxSlotConfig {
268    fn codec_pcm_default(mode: PdmSlotMode) -> Self {
269        Self {
270            slot_mode: mode,
271            data_format: PdmDataFormat::Pcm,
272            sd_prescale: 0,
273            sd_scale: PdmSigScaling::Mul1,
274            hp_scale: PdmSigScaling::Div2,
275            lp_scale: PdmSigScaling::Mul1,
276            sinc_scale: PdmSigScaling::Mul1,
277            #[cfg(not(i2s_version = "1"))]
278            line_mode: PdmTxLineMode::OneLineCodec,
279            hp_en: true,
280            hp_cut_off_freq_hz: 35.5,
281            #[cfg(not(i2s_version = "1"))]
282            sd_dither: 0,
283            #[cfg(not(i2s_version = "1"))]
284            sd_dither2: 1,
285            #[cfg(i2s_version = "1")]
286            slot_mask: PdmSlotMask::BOTH,
287        }
288    }
289
290    #[cfg(not(i2s_version = "1"))]
291    fn dac_pcm_default(mode: PdmSlotMode) -> Self {
292        let mut cfg = Self::codec_pcm_default(mode);
293        cfg.hp_scale = PdmSigScaling::Mul1;
294        cfg.lp_scale = PdmSigScaling::Mul1;
295        cfg.sinc_scale = PdmSigScaling::Mul1;
296        #[cfg(not(i2s_version = "1"))]
297        {
298            cfg.line_mode = if mode == PdmSlotMode::Mono {
299                PdmTxLineMode::OneLineDac
300            } else {
301                PdmTxLineMode::TwoLineDac
302            };
303        }
304        cfg
305    }
306
307    fn raw_default(mode: PdmSlotMode) -> Self {
308        let mut cfg = Self::codec_pcm_default(mode);
309        cfg.data_format = PdmDataFormat::Raw;
310        cfg.hp_en = false;
311        cfg
312    }
313}
314
315/// PDM RX clock configuration.
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
317#[cfg_attr(feature = "defmt", derive(defmt::Format))]
318pub struct PdmRxClockConfig {
319    /// Target PCM sample rate.
320    pub sample_rate: Rate,
321
322    /// PDM2PCM downsample rate.
323    pub downsample_rate: PdmDownsampleRate,
324
325    /// Bit clock divider.
326    pub bclk_div: u32,
327    /// Source clock for the I2S module clock.
328    pub clock_source: crate::i2s::master::I2sClockSource,
329}
330
331/// PDM RX slot configuration.
332#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
333#[cfg_attr(feature = "defmt", derive(defmt::Format))]
334#[non_exhaustive]
335pub struct PdmRxSlotConfig {
336    /// Mono or stereo slot mode.
337    pub slot_mode: PdmSlotMode,
338
339    /// Active RX slot mask.
340    pub slot_mask: PdmSlotMask,
341
342    /// PCM or raw PDM data format.
343    pub data_format: PdmDataFormat,
344
345    #[cfg(i2s_supports_pdm_rx_hp_filter)]
346    /// Enables the RX high-pass filter.
347    pub hp_en: bool,
348
349    #[cfg(i2s_supports_pdm_rx_hp_filter)]
350    /// High-pass filter cut-off frequency in Hz.
351    pub hp_cut_off_freq_hz: f32,
352
353    #[cfg(i2s_supports_pdm_rx_hp_filter)]
354    /// RX amplification factor (1–15).
355    pub amplify_num: u32,
356}
357
358impl PdmRxSlotConfig {
359    fn pcm_default(mode: PdmSlotMode) -> Self {
360        Self {
361            slot_mode: mode,
362            slot_mask: PdmSlotMask::for_mode(mode),
363            data_format: PdmDataFormat::Pcm,
364            #[cfg(i2s_supports_pdm_rx_hp_filter)]
365            hp_en: true,
366            #[cfg(i2s_supports_pdm_rx_hp_filter)]
367            hp_cut_off_freq_hz: 35.5,
368            #[cfg(i2s_supports_pdm_rx_hp_filter)]
369            amplify_num: 1,
370        }
371    }
372
373    fn raw_default(mode: PdmSlotMode) -> Self {
374        let mut cfg = Self::pcm_default(mode);
375        cfg.data_format = PdmDataFormat::Raw;
376        #[cfg(i2s_supports_pdm_rx_hp_filter)]
377        {
378            cfg.hp_en = false;
379        }
380        cfg
381    }
382}
383
384/// Full PDM TX unit configuration.
385#[derive(Debug, Clone, Copy, PartialEq)]
386#[cfg_attr(feature = "defmt", derive(defmt::Format))]
387pub struct PdmTxConfig {
388    /// TX clock settings.
389    pub clock: PdmTxClockConfig,
390
391    /// TX slot and filter settings.
392    pub slot: PdmTxSlotConfig,
393}
394
395impl PdmTxConfig {
396    /// Codec-line defaults (`I2S_PDM_TX_*_DEFAULT_CONFIG`).
397    pub fn new_codec_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
398        Self {
399            clock: PdmTxClockConfig::codec_default(sample_rate),
400            slot: default_tx_slot(mode),
401        }
402    }
403
404    /// Raw PDM TX defaults (no hardware PCM conversion).
405    pub fn new_raw_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
406        Self {
407            clock: PdmTxClockConfig::codec_default(sample_rate),
408            slot: PdmTxSlotConfig::raw_default(mode),
409        }
410    }
411
412    /// DAC-line defaults (`I2S_PDM_TX_*_DAC_DEFAULT_CONFIG`, HW v2+).
413    #[cfg(not(i2s_version = "1"))]
414    pub fn new_dac_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
415        Self {
416            clock: PdmTxClockConfig::dac_default(sample_rate),
417            slot: PdmTxSlotConfig::dac_pcm_default(mode),
418        }
419    }
420
421    /// Validates TX configuration against hardware capabilities.
422    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
423        if self.slot.data_format == PdmDataFormat::Pcm && !info.pcm2pdm {
424            return Err(PdmError::PcmFormatUnsupported);
425        }
426        if self.clock.up_sample_fs > 480 {
427            return Err(PdmError::InvalidClock);
428        }
429        Ok(())
430    }
431}
432
433fn default_tx_slot(mode: PdmSlotMode) -> PdmTxSlotConfig {
434    cfg_select! {
435        i2s_supports_pcm2pdm => PdmTxSlotConfig::codec_pcm_default(mode),
436        _ => PdmTxSlotConfig::raw_default(mode),
437    }
438}
439
440/// Full PDM RX unit configuration.
441#[derive(Debug, Clone, Copy, PartialEq)]
442#[cfg_attr(feature = "defmt", derive(defmt::Format))]
443pub struct PdmRxConfig {
444    /// RX clock settings.
445    pub clock: PdmRxClockConfig,
446    /// RX slot settings.
447    pub slot: PdmRxSlotConfig,
448}
449
450impl PdmRxConfig {
451    /// Default RX config; uses PCM when PDM2PCM is available, otherwise raw.
452    pub fn new_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
453        Self {
454            clock: PdmRxClockConfig::default(sample_rate),
455            slot: default_rx_slot(mode),
456        }
457    }
458
459    /// PCM RX defaults (requires hardware PDM2PCM support).
460    pub fn new_pcm_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
461        Self {
462            clock: PdmRxClockConfig::default(sample_rate),
463            slot: PdmRxSlotConfig::pcm_default(mode),
464        }
465    }
466
467    /// Raw PDM RX defaults (no hardware PCM conversion).
468    pub fn new_raw_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
469        Self {
470            clock: PdmRxClockConfig::default(sample_rate),
471            slot: PdmRxSlotConfig::raw_default(mode),
472        }
473    }
474
475    /// Validates RX configuration against hardware capabilities.
476    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
477        if self.slot.data_format == PdmDataFormat::Pcm && !info.pdm2pcm {
478            return Err(PdmError::PcmFormatUnsupported);
479        }
480        if self.slot.slot_mask.bits() == 0 {
481            return Err(PdmError::InvalidSlotMask);
482        }
483        Ok(())
484    }
485}
486
487fn default_rx_slot(mode: PdmSlotMode) -> PdmRxSlotConfig {
488    cfg_select! {
489        i2s_supports_pdm2pcm => PdmRxSlotConfig::pcm_default(mode),
490        _ => PdmRxSlotConfig::raw_default(mode),
491    }
492}
493
494/// PDM mode configuration (simplex TX, RX, or both).
495#[derive(Debug, Clone, Copy, PartialEq)]
496#[cfg_attr(feature = "defmt", derive(defmt::Format))]
497pub struct PdmConfig {
498    /// Optional TX unit configuration.
499    pub tx: Option<PdmTxConfig>,
500    /// Optional RX unit configuration.
501    pub rx: Option<PdmRxConfig>,
502    /// Selects which module clock is routed to the MCLK pad.
503    #[cfg(not(i2s_version = "1"))]
504    pub mclk_out: crate::i2s::master::MclkOut,
505}
506
507impl PdmConfig {
508    /// PDM TX only (recommended simplex setup).
509    #[cfg(i2s_supports_pdm_tx)]
510    pub fn tx_only(tx: PdmTxConfig) -> Self {
511        Self {
512            tx: Some(tx),
513            rx: None,
514            #[cfg(not(i2s_version = "1"))]
515            mclk_out: crate::i2s::master::MclkOut::Tx,
516        }
517    }
518
519    /// PDM RX only (recommended simplex setup).
520    #[cfg(i2s_supports_pdm_rx)]
521    pub fn rx_only(rx: PdmRxConfig) -> Self {
522        Self {
523            tx: None,
524            rx: Some(rx),
525            #[cfg(not(i2s_version = "1"))]
526            mclk_out: crate::i2s::master::MclkOut::Rx,
527        }
528    }
529
530    /// Validates that exactly one direction is configured and that the settings
531    /// are valid for the given I2S instance.
532    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
533        if self.tx.is_none() && self.rx.is_none() {
534            return Err(PdmError::DirectionMissing);
535        }
536        if self.tx.is_some() && self.rx.is_some() {
537            return Err(PdmError::DuplexUnsupported);
538        }
539        if self.tx.is_some() && !info.pdm_tx {
540            return Err(PdmError::UnsupportedInstance);
541        }
542        if self.rx.is_some() && !info.pdm_rx {
543            return Err(PdmError::UnsupportedInstance);
544        }
545        if let Some(tx) = &self.tx {
546            tx.validate(info)?;
547        }
548        if let Some(rx) = &self.rx {
549            rx.validate(info)?;
550        }
551        Ok(())
552    }
553}
554
555pub(crate) fn configure_pdm(i2s: &Info, config: &PdmConfig) -> Result<(), ConfigError> {
556    ll::configure_pdm(i2s, config).map_err(ConfigError::Pdm)
557}