Lines
95.83 %
Functions
100 %
//! PDM register programming for I2S hardware after ESP32 (v2+).
//!
//! Chip-specific differences are handled via `#[cfg(i2s_version = "...")]`.
use super::{
PdmConfig,
PdmDataFormat,
PdmError,
PdmSlotMode,
PdmTxLineMode,
clock,
hp_filter::cut_off_coefficients,
};
use crate::i2s::master::Info;
pub(crate) fn configure_pdm(i2s: &Info, config: &PdmConfig) -> Result<(), PdmError> {
i2s.configure_mclk_pad(config.mclk_out);
if let Some(tx) = &config.tx {
configure_tx(i2s, tx)?;
}
if let Some(rx) = &config.rx {
configure_rx(i2s, rx)?;
Ok(())
fn configure_tx(i2s: &Info, config: &super::PdmTxConfig) -> Result<(), PdmError> {
if !i2s.pdm_tx {
return Err(PdmError::UnsupportedInstance);
let pcm = config.slot.data_format == PdmDataFormat::Pcm;
let clock = clock::calculate_tx_clock(&config.clock, pcm)?;
let is_mono = config.slot.slot_mode == PdmSlotMode::Mono;
// Slot/filter setup before clock enable.
i2s.regs().tx_conf().modify(|_, w| w.tx_reset().set_bit());
i2s.regs().tx_conf().modify(|_, w| {
w.tx_slave_mod().clear_bit();
w.tx_tdm_en().clear_bit();
w.tx_pcm_bypass().clear_bit();
w.tx_mono().bit(is_mono);
w.tx_mono_fst_vld().set_bit();
#[cfg(i2s_version = "3")]
w.tx_msb_shift().clear_bit();
w.tx_ws_idle_pol().clear_bit()
});
i2s.regs()
.tx_conf()
.modify(|_, w| unsafe { w.tx_chan_mod().bits(if is_mono { 3 } else { 0 }) });
#[cfg(i2s_version = "2")]
.tx_conf1()
.modify(|_, w| w.tx_msb_shift().clear_bit());
let slot_bits = if is_mono { 16 } else { 32 };
i2s.regs().tx_conf1().modify(|_, w| unsafe {
w.tx_bits_mod().bits(15);
w.tx_tdm_chan_bits().bits(slot_bits - 1);
w.tx_half_sample_bits().bits(15);
w.tx_tdm_ws_width().bits(0)
if pcm {
let freq_x10 = (config.slot.hp_cut_off_freq_hz * 10.0) as u32;
let (param0, param5) = cut_off_coefficients(freq_x10);
// Set fp/fs before clock enable.
i2s.regs().tx_pcm2pdm_conf1().modify(|_, w| unsafe {
w.tx_pdm_fp().bits(config.clock.up_sample_fp as u16);
w.tx_pdm_fs().bits(config.clock.up_sample_fs as u16);
w.tx_iir_hp_mult12_0().bits(param0 as u8);
w.tx_iir_hp_mult12_5().bits(param5 as u8)
i2s.regs().tx_pcm2pdm_conf().modify(|_, w| unsafe {
w.tx_pdm_sinc_osr2().bits(clock.over_sample_ratio as u8);
w.tx_pdm_prescale().bits(config.slot.sd_prescale);
w.tx_pdm_sigmadelta_in_shift()
.bits(config.slot.sd_scale.to_register());
w.tx_pdm_hp_in_shift()
.bits(config.slot.hp_scale.to_register());
w.tx_pdm_lp_in_shift()
.bits(config.slot.lp_scale.to_register());
w.tx_pdm_sinc_in_shift()
.bits(config.slot.sinc_scale.to_register());
w.tx_pdm_dac_mode_en()
.bit(config.slot.line_mode != PdmTxLineMode::OneLineCodec);
w.tx_pdm_dac_2out_en()
.bit(config.slot.line_mode == PdmTxLineMode::TwoLineDac);
w.tx_pdm_sigmadelta_dither().bit(config.slot.sd_dither != 0);
w.tx_pdm_sigmadelta_dither2()
.bit(config.slot.sd_dither2 != 0);
w.tx_pdm_hp_bypass().bit(!config.slot.hp_en)
set_pdm_tx_clock(i2s, config.clock.clock_source, &clock);
w.tx_pdm_en().set_bit();
w.tx_tdm_en().clear_bit()
.tx_pcm2pdm_conf()
.modify(|_, w| w.pcm2pdm_conv_en().bit(pcm));
/// Expands a stereo slot mask to include both slots on every active PDM line.
fn stereo_pdm_rx_slot_mask(slot_mask: u16) -> u16 {
let mut stereo_mask = 0u16;
for i in 0..8 {
let pair = 0b11u16 << (2 * i);
if slot_mask & pair != 0 {
stereo_mask |= pair;
stereo_mask
fn configure_rx(i2s: &Info, config: &super::PdmRxConfig) -> Result<(), PdmError> {
if !i2s.pdm_rx {
let mut slot_mask = config.slot.slot_mask.bits();
if config.slot.slot_mode == PdmSlotMode::Stereo {
slot_mask = stereo_pdm_rx_slot_mask(slot_mask);
let clock = clock::calculate_rx_clock(&config.clock, pcm, slot_mask)?;
i2s.regs().rx_conf().modify(|_, w| {
w.rx_reset().set_bit();
w.rx_fifo_reset().set_bit()
w.rx_slave_mod().clear_bit();
w.rx_pcm_bypass().set_bit();
// Mono mode stays disabled for PDM RX on HW v2+.
w.rx_mono().clear_bit();
w
.rx_conf()
.modify(|_, w| w.rx_msb_shift().clear_bit());
.modify(|_, w| unsafe { w.rx_stop_mode().bits(2) });
.rx_conf1()
i2s.regs().rx_conf1().modify(|_, w| unsafe {
w.rx_bits_mod().bits(15);
w.rx_tdm_chan_bits().bits(15);
w.rx_half_sample_bits().bits(15)
// PDM always uses a 2-slot frame; mono DMA expects one active slot only.
.rx_tdm_ctrl()
.modify(|_, w| unsafe { w.rx_tdm_tot_chan_num().bits(1) });
.modify(|r, w| unsafe { w.bits((r.bits() & 0xFFFF0000) | u32::from(slot_mask)) });
i2s.configure_rx_mclk(config.clock.clock_source, &clock.dividers);
i2s.set_rx_bclk(clock.dividers.bclk_divider);
#[cfg(all(i2s_supports_pdm2pcm, i2s_supports_pdm_rx_hp_filter))]
{
let dsr16 = config.clock.downsample_rate == super::PdmDownsampleRate::Dsr16s;
i2s.regs().rx_pdm2pcm_conf().modify(|_, w| {
w.rx_pdm2pcm_en().bit(pcm);
w.rx_pdm_sinc_dsr_16_en().bit(dsr16);
w.rx_pdm_hp_bypass().bit(!config.slot.hp_en);
unsafe {
w.rx_pdm2pcm_amplify_num()
.bits(config.slot.amplify_num.clamp(1, 15) as u8);
w.rx_iir_hp_mult12_0().bits(param0 as u8);
w.rx_iir_hp_mult12_5().bits(param5 as u8)
w.rx_pdm_en().set_bit();
w.rx_tdm_en().clear_bit();
#[cfg(all(i2s_supports_pdm2pcm, not(i2s_supports_pdm_rx_hp_filter)))]
w.rx_pdm_sinc_dsr_16_en()
.bit(config.clock.downsample_rate == super::PdmDownsampleRate::Dsr16s);
fn set_pdm_tx_clock(
i2s: &Info,
source: crate::i2s::master::I2sClockSource,
clock: &clock::PdmTxClockResult,
) {
i2s.configure_tx_mclk(source, &clock.dividers);
i2s.set_tx_bclk(clock.dividers.bclk_divider);