mod clock;
#[cfg(not(i2s_version = "1"))]
mod hp_filter;
#[cfg_attr(i2s_version = "1", path = "regs_v1.rs")]
#[cfg_attr(i2s_version = "2", path = "regs_v2.rs")]
#[cfg_attr(i2s_version = "3", path = "regs_v2.rs")]
mod ll;
use super::master::{ConfigError, Instance};
use crate::{i2s::master::Info, time::Rate};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PdmError {
UnsupportedInstance,
PcmFormatUnsupported,
InvalidClock,
InvalidSlotMask,
DirectionMissing,
DuplexUnsupported,
InvalidLine,
}
impl core::error::Error for PdmError {}
impl core::fmt::Display for PdmError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnsupportedInstance => {
write!(f, "PDM mode is not supported on this I2S instance")
}
Self::PcmFormatUnsupported => write!(
f,
"PCM PDM format is not supported on this I2S instance; use raw PDM format"
),
Self::InvalidClock => write!(f, "PDM clock configuration is out of supported range"),
Self::InvalidSlotMask => {
write!(f, "PDM slot mask must select at least one active slot")
}
Self::DirectionMissing => {
write!(f, "PDM configuration must include TX and/or RX settings")
}
Self::DuplexUnsupported => {
write!(f, "PDM full duplex (TX and RX together) is not supported")
}
Self::InvalidLine => write!(f, "PDM data line index is not available on this chip"),
}
}
}
pub trait PdmInstance: Instance {}
for_each_i2s! {
(
$instance:ident, $sys:ident, $mclk:ident,
$bclk:ident, $ws:ident, $bclk_rx:ident, $ws_rx:ident,
$dout:tt, $din:tt, true, $pdm_rx:literal, $pcm2pdm:literal, $pdm2pcm:literal
) => {
impl PdmInstance for crate::peripherals::$instance<'_> {}
};
(
$instance:ident, $sys:ident, $mclk:ident,
$bclk:ident, $ws:ident, $bclk_rx:ident, $ws_rx:ident,
$dout:tt, $din:tt, false, true, $pcm2pdm:literal, $pdm2pcm:literal
) => {
impl PdmInstance for crate::peripherals::$instance<'_> {}
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PdmDataFormat {
#[default]
Pcm,
Raw,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PdmSlotMode {
#[default]
Mono,
Stereo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PdmSlotMask(u16);
impl PdmSlotMask {
pub const LEFT: Self = Self(1 << 1);
pub const RIGHT: Self = Self(1 << 0);
pub const BOTH: Self = Self(0b11);
#[cfg(all(i2s_supports_pdm_rx, not(esp32)))]
pub const LINE0_LEFT: Self = Self(1 << 1);
#[cfg(all(i2s_supports_pdm_rx, not(esp32)))]
pub const LINE0_RIGHT: Self = Self(1 << 0);
#[cfg(all(i2s_supports_pdm_rx, esp32s3))]
pub const LINE1_LEFT: Self = Self(1 << 3);
#[cfg(all(i2s_supports_pdm_rx, esp32s3))]
pub const LINE1_RIGHT: Self = Self(1 << 2);
#[cfg(all(i2s_supports_pdm_rx, esp32s3))]
pub const LINE2_LEFT: Self = Self(1 << 5);
#[cfg(all(i2s_supports_pdm_rx, esp32s3))]
pub const LINE2_RIGHT: Self = Self(1 << 4);
#[cfg(all(i2s_supports_pdm_rx, esp32s3))]
pub const LINE3_LEFT: Self = Self(1 << 7);
#[cfg(all(i2s_supports_pdm_rx, esp32s3))]
pub const LINE3_RIGHT: Self = Self(1 << 6);
pub const fn from_bits(bits: u16) -> Self {
Self(bits)
}
pub const fn bits(self) -> u16 {
self.0
}
pub fn for_mode(mode: PdmSlotMode) -> Self {
match mode {
PdmSlotMode::Mono => Self::LEFT,
PdmSlotMode::Stereo => Self::BOTH,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PdmDownsampleRate {
#[default]
Dsr8s,
Dsr16s,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PdmSigScaling {
Div2,
#[default]
Mul1,
Mul2,
Mul4,
}
impl PdmSigScaling {
pub(crate) fn to_register(self) -> u8 {
match self {
Self::Div2 => 0,
Self::Mul1 => 1,
Self::Mul2 => 2,
Self::Mul4 => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PdmTxLineMode {
#[default]
OneLineCodec,
OneLineDac,
TwoLineDac,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PdmTxClockConfig {
pub sample_rate: Rate,
pub up_sample_fp: u32,
pub up_sample_fs: u32,
pub bclk_div: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct PdmTxSlotConfig {
pub slot_mode: PdmSlotMode,
pub data_format: PdmDataFormat,
pub sd_prescale: u8,
pub sd_scale: PdmSigScaling,
pub hp_scale: PdmSigScaling,
pub lp_scale: PdmSigScaling,
pub sinc_scale: PdmSigScaling,
#[cfg(not(i2s_version = "1"))]
pub line_mode: PdmTxLineMode,
pub hp_en: bool,
pub hp_cut_off_freq_hz: f32,
#[cfg(not(i2s_version = "1"))]
pub sd_dither: u8,
#[cfg(not(i2s_version = "1"))]
pub sd_dither2: u8,
#[cfg(i2s_version = "1")]
pub slot_mask: PdmSlotMask,
}
impl PdmTxSlotConfig {
fn codec_pcm_default(mode: PdmSlotMode) -> Self {
Self {
slot_mode: mode,
data_format: PdmDataFormat::Pcm,
sd_prescale: 0,
sd_scale: PdmSigScaling::Mul1,
hp_scale: PdmSigScaling::Div2,
lp_scale: PdmSigScaling::Mul1,
sinc_scale: PdmSigScaling::Mul1,
#[cfg(not(i2s_version = "1"))]
line_mode: PdmTxLineMode::OneLineCodec,
hp_en: true,
hp_cut_off_freq_hz: 35.5,
#[cfg(not(i2s_version = "1"))]
sd_dither: 0,
#[cfg(not(i2s_version = "1"))]
sd_dither2: 1,
#[cfg(i2s_version = "1")]
slot_mask: PdmSlotMask::BOTH,
}
}
#[cfg(not(i2s_version = "1"))]
fn dac_pcm_default(mode: PdmSlotMode) -> Self {
let mut cfg = Self::codec_pcm_default(mode);
cfg.hp_scale = PdmSigScaling::Mul1;
cfg.lp_scale = PdmSigScaling::Mul1;
cfg.sinc_scale = PdmSigScaling::Mul1;
#[cfg(not(i2s_version = "1"))]
{
cfg.line_mode = if mode == PdmSlotMode::Mono {
PdmTxLineMode::OneLineDac
} else {
PdmTxLineMode::TwoLineDac
};
}
cfg
}
fn raw_default(mode: PdmSlotMode) -> Self {
let mut cfg = Self::codec_pcm_default(mode);
cfg.data_format = PdmDataFormat::Raw;
cfg.hp_en = false;
cfg
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PdmRxClockConfig {
pub sample_rate: Rate,
pub downsample_rate: PdmDownsampleRate,
pub bclk_div: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct PdmRxSlotConfig {
pub slot_mode: PdmSlotMode,
pub slot_mask: PdmSlotMask,
pub data_format: PdmDataFormat,
#[cfg(i2s_supports_pdm_rx_hp_filter)]
pub hp_en: bool,
#[cfg(i2s_supports_pdm_rx_hp_filter)]
pub hp_cut_off_freq_hz: f32,
#[cfg(i2s_supports_pdm_rx_hp_filter)]
pub amplify_num: u32,
}
impl PdmRxSlotConfig {
fn pcm_default(mode: PdmSlotMode) -> Self {
Self {
slot_mode: mode,
slot_mask: PdmSlotMask::for_mode(mode),
data_format: PdmDataFormat::Pcm,
#[cfg(i2s_supports_pdm_rx_hp_filter)]
hp_en: true,
#[cfg(i2s_supports_pdm_rx_hp_filter)]
hp_cut_off_freq_hz: 35.5,
#[cfg(i2s_supports_pdm_rx_hp_filter)]
amplify_num: 1,
}
}
fn raw_default(mode: PdmSlotMode) -> Self {
let mut cfg = Self::pcm_default(mode);
cfg.data_format = PdmDataFormat::Raw;
#[cfg(i2s_supports_pdm_rx_hp_filter)]
{
cfg.hp_en = false;
}
cfg
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PdmTxConfig {
pub clock: PdmTxClockConfig,
pub slot: PdmTxSlotConfig,
}
impl PdmTxConfig {
pub fn new_codec_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
Self {
clock: PdmTxClockConfig::codec_default(sample_rate),
slot: default_tx_slot(mode),
}
}
pub fn new_raw_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
Self {
clock: PdmTxClockConfig::codec_default(sample_rate),
slot: PdmTxSlotConfig::raw_default(mode),
}
}
#[cfg(not(i2s_version = "1"))]
pub fn new_dac_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
Self {
clock: PdmTxClockConfig::dac_default(sample_rate),
slot: PdmTxSlotConfig::dac_pcm_default(mode),
}
}
pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
if self.slot.data_format == PdmDataFormat::Pcm && !info.pcm2pdm {
return Err(PdmError::PcmFormatUnsupported);
}
if self.clock.up_sample_fs > 480 {
return Err(PdmError::InvalidClock);
}
Ok(())
}
}
fn default_tx_slot(mode: PdmSlotMode) -> PdmTxSlotConfig {
cfg_select! {
i2s_supports_pcm2pdm => PdmTxSlotConfig::codec_pcm_default(mode),
_ => PdmTxSlotConfig::raw_default(mode),
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PdmRxConfig {
pub clock: PdmRxClockConfig,
pub slot: PdmRxSlotConfig,
}
impl PdmRxConfig {
pub fn new_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
Self {
clock: PdmRxClockConfig::default(sample_rate),
slot: default_rx_slot(mode),
}
}
pub fn new_pcm_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
Self {
clock: PdmRxClockConfig::default(sample_rate),
slot: PdmRxSlotConfig::pcm_default(mode),
}
}
pub fn new_raw_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
Self {
clock: PdmRxClockConfig::default(sample_rate),
slot: PdmRxSlotConfig::raw_default(mode),
}
}
pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
if self.slot.data_format == PdmDataFormat::Pcm && !info.pdm2pcm {
return Err(PdmError::PcmFormatUnsupported);
}
if self.slot.slot_mask.bits() == 0 {
return Err(PdmError::InvalidSlotMask);
}
Ok(())
}
}
fn default_rx_slot(mode: PdmSlotMode) -> PdmRxSlotConfig {
cfg_select! {
i2s_supports_pdm2pcm => PdmRxSlotConfig::pcm_default(mode),
_ => PdmRxSlotConfig::raw_default(mode),
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PdmConfig {
pub tx: Option<PdmTxConfig>,
pub rx: Option<PdmRxConfig>,
}
impl PdmConfig {
#[cfg(i2s_supports_pdm_tx)]
pub fn tx_only(tx: PdmTxConfig) -> Self {
Self {
tx: Some(tx),
rx: None,
}
}
#[cfg(i2s_supports_pdm_rx)]
pub fn rx_only(rx: PdmRxConfig) -> Self {
Self {
tx: None,
rx: Some(rx),
}
}
pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
if self.tx.is_none() && self.rx.is_none() {
return Err(PdmError::DirectionMissing);
}
if self.tx.is_some() && self.rx.is_some() {
return Err(PdmError::DuplexUnsupported);
}
if self.tx.is_some() && !info.pdm_tx {
return Err(PdmError::UnsupportedInstance);
}
if self.rx.is_some() && !info.pdm_rx {
return Err(PdmError::UnsupportedInstance);
}
if let Some(tx) = &self.tx {
tx.validate(info)?;
}
if let Some(rx) = &self.rx {
rx.validate(info)?;
}
Ok(())
}
}
pub(crate) fn configure_pdm(i2s: &Info, config: &PdmConfig) -> Result<(), ConfigError> {
ll::configure_pdm(i2s, config).map_err(ConfigError::Pdm)
}