diff --git a/src/i2s.rs b/src/i2s.rs index 7c079b36808..112bd0a8730 100644 --- a/src/i2s.rs +++ b/src/i2s.rs @@ -43,6 +43,7 @@ mod std; ))] mod tdm; +/// I2S channel base configuration. pub type I2sConfig = config::Config; /// I2S configuration @@ -84,8 +85,14 @@ pub mod config { i2s_mode_t_I2S_MODE_SLAVE, }; - /// I2S clock source - #[derive(Clone, Copy, Eq, PartialEq)] + /// The default number of DMA buffers to use. + pub const DEFAULT_DMA_BUFFER_COUNT: u32 = 6; + + /// The default number of frames per DMA buffer. + pub const DEFAULT_FRAMES_PER_DMA_BUFFER: u32 = 240; + + /// I2S clock source. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ClockSource { /// Use PLL_F160M as the source clock Pll160M, @@ -117,7 +124,7 @@ pub mod config { } } - /// I2S controller channel configuration. + /// I2S common channel configuration. /// /// To create a custom configuration, use the builder pattern built-in to this struct. For example: /// ``` @@ -126,20 +133,20 @@ pub mod config { /// ``` /// /// The default configuration is: - /// * Role ([Config::role]): [Role::Controller] (master) - /// * DMA buffer number/descriptor number ([Config::dma_desc]): 6 - /// * I2S frames in one DMA buffer ([Config::dma_frame]): 240 - /// * Auto clear ([Config::auto_clear]): false - #[derive(Clone)] + /// * [`role`][Config::role]: [`Role::Controller`] (master) + /// * [`dma_buffer_count`][Config::dma_buffer_count]: 6 ([`DEFAULT_DMA_BUFFER_COUNT`]) + /// * [`frames_per_buffer`][Config::frames_per_buffer]: 240 ([`DEFAULT_FRAMES_PER_DMA_BUFFER`]) + /// * [`auto_clear`][Config::auto_clear]: `false` + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Config { /// The role of this channel: controller (master) or target (slave) pub(super) role: Role, - /// The DMA buffer number to use (also the DMA descriptor number). - pub(super) dma_desc: u32, + /// The number of DMA buffers number to use. + pub(super) dma_buffer_count: u32, /// The number of I2S frames in one DMA buffer. - pub(super) frames: u32, + pub(super) frames_per_buffer: u32, /// If true, the transmit buffer will be automatically cleared upon sending. pub(super) auto_clear: bool, @@ -154,17 +161,17 @@ pub mod config { impl Config { #[inline(always)] - /// Create a new Config + /// Create a new Config with the default settings. pub const fn new() -> Self { Self { role: Role::Controller, - dma_desc: 6, - frames: 240, + dma_buffer_count: DEFAULT_DMA_BUFFER_COUNT, + frames_per_buffer: DEFAULT_FRAMES_PER_DMA_BUFFER, auto_clear: false, } } - /// Set the role of this channel: controller (master) or target (slave) + /// Set the role of this channel: controller (master) or target (slave). #[must_use] #[inline(always)] pub fn role(mut self, role: Role) -> Self { @@ -172,19 +179,19 @@ pub mod config { self } - /// Set the DMA buffer to use. + /// Set the number of DMA buffers to use. #[must_use] #[inline(always)] - pub fn dma_desc(mut self, dma_desc: u32) -> Self { - self.dma_desc = dma_desc; + pub fn dma_buffer_count(mut self, dma_buffer_count: u32) -> Self { + self.dma_buffer_count = dma_buffer_count; self } /// Set the number of I2S frames in one DMA buffer. #[must_use] #[inline(always)] - pub fn frames(mut self, frames: u32) -> Self { - self.frames = frames; + pub fn frames_per_buffer(mut self, frames: u32) -> Self { + self.frames_per_buffer = frames; self } @@ -203,15 +210,15 @@ pub mod config { i2s_chan_config_t { id, role: self.role.as_sdk(), - dma_desc_num: self.dma_desc, - dma_frame_num: self.frames, + dma_desc_num: self.dma_buffer_count, + dma_frame_num: self.frames_per_buffer, auto_clear: self.auto_clear, } } } /// Available data bit width in one slot. - #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum DataBitWidth { /// Channel data bit width is 8 bits. Bits8, @@ -279,7 +286,7 @@ pub mod config { } /// The multiple of MCLK to the sample rate. - #[derive(Clone, Copy, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum MclkMultiple { /// MCLK = sample rate * 128 M128, @@ -323,22 +330,16 @@ pub mod config { } /// I2S channel operating role - #[derive(Clone, Copy, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum Role { /// Controller (master) + #[default] Controller, /// Target (slave) Target, } - impl Default for Role { - #[inline(always)] - fn default() -> Self { - Self::Controller - } - } - /// I2S peripheral in controller (master) role, bclk and ws signal will be set to output. #[cfg(not(esp_idf_version_major = "4"))] const I2S_ROLE_CONTROLLER: i2s_role_t = 0; @@ -371,11 +372,12 @@ pub mod config { /// The total slot bit width in one slot. /// - /// This is not necessarily the number of data bits in one slot. A slot may have additional bits padded - /// to fill out the slot. - #[derive(Clone, Copy, Eq, PartialEq)] + /// This is not necessarily the number of data bits in one slot. A slot may have additional bits padded to fill out + /// the slot. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum SlotBitWidth { /// Slot bit width is automatically set to the data bit width. + #[default] Auto, /// Slot bit width is 8 bits. @@ -391,13 +393,6 @@ pub mod config { Bits32, } - impl Default for SlotBitWidth { - #[inline(always)] - fn default() -> Self { - Self::Auto - } - } - #[cfg(not(esp_idf_version_major = "4"))] type SlotBitWidthSdkType = i2s_slot_bit_width_t; @@ -434,7 +429,13 @@ pub mod config { } /// I2S channel slot mode. - #[derive(Clone, Copy, Eq, PartialEq)] + /// + /// See the documentation for the mode of operation to see how this affects the data layout: + /// * [PDM Rx][PdmRxSlotConfig] + /// * [PDM Tx][PdmTxSlotConfig] + /// * [Standard Rx/Tx][StdSlotConfig] + /// * [TDM Rx/Tx][TdmSlotConfig] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum SlotMode { /// Mono mode: /// * When transmitting, transmit the same data in all slots. @@ -444,16 +445,10 @@ pub mod config { /// Stereo mode: /// * When transmitting, transmit different data in each slot. /// * When receiving, receive data from all slots. + #[default] Stereo, } - impl Default for SlotMode { - #[inline(always)] - fn default() -> Self { - Self::Stereo - } - } - impl SlotMode { /// Convert this to the ESP-IDF SDK `i2s_slot_mode_t` representation. #[cfg(not(esp_idf_version_major = "4"))] @@ -467,20 +462,29 @@ pub mod config { } } -pub trait I2s: Send { +/// Trait implemented by I2S peripherals to obtain their port number. +pub trait I2s: Send + sealed::Sealed { + /// Return the port number for the peripheral. fn port() -> i2s_port_t; } +mod sealed { + pub trait Sealed {} + + impl Sealed for super::I2S0 {} + #[cfg(any(esp32, esp32s3))] + impl Sealed for super::I2S1 {} +} + pub trait I2sPort { /// Returns the I2S port number of this driver. fn port(&self) -> i2s_port_t; } -/// Functions for receive channels. -/// Marker trait indicating that a driver supports the [I2sRx] trait. +/// Marker trait indicating that a driver supports receiving data via the [`I2sRx`] trait. pub trait I2sRxSupported {} -/// Concrete implementation of [I2sRxSupported] for use in clients. +/// Concrete implementation of [`I2sRxSupported`] for use in clients. /// /// Example usage: /// ``` @@ -491,15 +495,15 @@ pub trait I2sRxSupported {} /// let din = peripherals.pins.gpio4; /// let mclk = AnyIOPin::none(); /// let ws = peripherals.pins.gpio2; -/// let i2s = I2sStdModeDriver::::new_rx(periperhals.i2s0, std_config, bclk, Some(din), mclk, ws, None).unwrap(); +/// let i2s = I2sDriver::::new_std_rx(periperhals.i2s0, std_config, bclk, Some(din), mclk, ws, None).unwrap(); /// ``` pub struct I2sRx {} impl I2sRxSupported for I2sRx {} -/// Marker trait indicating that a driver supports the [I2sTx] trait. +/// Marker trait indicating that a driver supports transmitting data via the [`I2sTx`] trait. pub trait I2sTxSupported {} -/// Concrete implementation of [I2sTxSupported] for use in clients. +/// Concrete implementation of [`I2sTxSupported`] for use in clients. /// /// Example usage: /// ``` @@ -510,12 +514,12 @@ pub trait I2sTxSupported {} /// let dout = peripherals.pins.gpio6; /// let mclk = AnyIOPin::none(); /// let ws = peripherals.pins.gpio2; -/// let i2s = I2sStdModeDriver::::new_tx(periperhals.i2s0, std_config, bclk, Some(dout), mclk, ws, None).unwrap(); +/// let i2s = I2sDriver::::new_std_tx(periperhals.i2s0, std_config, bclk, Some(dout), mclk, ws, None).unwrap(); /// ``` pub struct I2sTx {} impl I2sTxSupported for I2sTx {} -/// Concrete implementation of both [I2sRxSupported] and [I2sTxSupported] for use in clients. +/// Concrete implementation of both [`I2sRxSupported`] and [`I2sTxSupported`] for use in clients. /// /// Example usage: /// ``` @@ -527,13 +531,13 @@ impl I2sTxSupported for I2sTx {} /// let dout = peripherals.pins.gpio6; /// let mclk = AnyIOPin::none(); /// let ws = peripherals.pins.gpio2; -/// let i2s = I2sStdModeDriver::::new_bidir(periperhals.i2s0, std_config, bclk, Some(din), Some(dout), mclk, ws, None, None).unwrap(); +/// let i2s = I2sDriver::::new_std_bidir(periperhals.i2s0, std_config, bclk, Some(din), Some(dout), mclk, ws, None, None).unwrap(); /// ``` pub struct I2sBiDir {} impl I2sRxSupported for I2sBiDir {} impl I2sTxSupported for I2sBiDir {} -/// The I2S driver. +/// Inter-IC Sound (I2S) driver. pub struct I2sDriver<'d, Dir> { /// The Rx channel, possibly null. #[cfg(not(esp_idf_version_major = "4"))] @@ -543,7 +547,7 @@ pub struct I2sDriver<'d, Dir> { #[cfg(not(esp_idf_version_major = "4"))] tx_handle: i2s_chan_handle_t, - /// The I2S peripheral number. Either 0 or 1 (ESP32 and ESP32S3 only). + /// The I2S peripheral number. Either 0 (all devices) or 1 (ESP32 and ESP32-S3 only). port: u8, /// Driver lifetime -- mimics the lifetime of the peripheral. @@ -703,14 +707,14 @@ where /// /// # Note /// This can only be called when the channel is in the `READY` state: initialized but not yet started from a driver - /// constructor, or disabled from the `RUNNING` state via [I2sRxChannel::rx_disable]. The channel will enter the - /// `RUNNING` state if it is enabled successfully. + /// constructor, or disabled from the `RUNNING` state via [`rx_disable()`][I2sDriver::rx_disable]. The channel + /// will enter the `RUNNING` state if it is enabled successfully. /// /// Enabling the channel will start I2S communications on the hardware. BCLK and WS signals will be generated if /// this is a controller. MCLK will be generated once initialization is finished. /// /// # Errors - /// This will return an [EspError] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. #[cfg(esp_idf_version_major = "4")] pub fn rx_enable(&mut self) -> Result<(), EspError> { unsafe { esp!(i2s_start(self.port as _)) } @@ -720,14 +724,14 @@ where /// /// # Note /// This can only be called when the channel is in the `READY` state: initialized but not yet started from a driver - /// constructor, or disabled from the `RUNNING` state via [I2sRxChannel::rx_disable]. The channel will enter the - /// `RUNNING` state if it is enabled successfully. + /// constructor, or disabled from the `RUNNING` state via [`rx_enable()`][I2sRxChannel::rx_disable]. The channel + /// will enter the `RUNNING` state if it is enabled successfully. /// /// Enabling the channel will start I2S communications on the hardware. BCLK and WS signals will be generated if /// this is a controller. MCLK will be generated once initialization is finished. /// /// # Errors - /// This will return an [EspError] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. #[cfg(not(esp_idf_version_major = "4"))] pub fn rx_enable(&mut self) -> Result<(), EspError> { unsafe { esp!(i2s_channel_enable(self.rx_handle)) } @@ -737,14 +741,14 @@ where /// /// # Note /// This can only be called when the channel is in the `RUNNING` state: the channel has been previously enabled - /// via a call to [I2sRxChannel::rx_enable]. The channel will enter the `READY` state if it is disabled - /// successfully. + /// via a call to [`rx_enable()`][I2sRxChannel::rx_enable]. The channel will enter the `READY` state if it is + /// disabled successfully. /// /// Disabling the channel will stop I2S communications on the hardware. BCLK and WS signals will stop being /// generated if this is a controller. MCLK will continue to be generated. /// /// # Errors - /// This will return an [EspError] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. #[cfg(esp_idf_version_major = "4")] pub fn rx_disable(&mut self) -> Result<(), EspError> { unsafe { esp!(i2s_stop(self.port as _)) } @@ -754,14 +758,14 @@ where /// /// # Note /// This can only be called when the channel is in the `RUNNING` state: the channel has been previously enabled - /// via a call to [I2sRxChannel::rx_enable]. The channel will enter the `READY` state if it is disabled - /// successfully. + /// via a call to [`rx_enable()`][I2sRxChannel::rx_enable]. The channel will enter the `READY` state if it is + /// disabled successfully. /// /// Disabling the channel will stop I2S communications on the hardware. BCLK and WS signals will stop being /// generated if this is a controller. MCLK will continue to be generated. /// /// # Errors - /// This will return an [EspError] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. #[cfg(not(esp_idf_version_major = "4"))] pub fn rx_disable(&mut self) -> Result<(), EspError> { unsafe { esp!(i2s_channel_disable(self.rx_handle)) } @@ -772,7 +776,7 @@ where /// This may be called only when the channel is in the `RUNNING` state. /// /// # Returns - /// This returns the number of bytes read, or an [EspError] if an error occurred. + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. #[cfg(not(esp_idf_version_major = "4"))] pub async fn read_async(&mut self, buffer: &mut [u8]) -> Result { loop { @@ -790,7 +794,7 @@ where /// This may be called only when the channel is in the `RUNNING` state. /// /// # Returns - /// This returns the number of bytes read, or an [EspError] if an error occurred. + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. #[cfg(esp_idf_version_major = "4")] pub fn read(&mut self, buffer: &mut [u8], timeout: TickType_t) -> Result { if buffer.is_empty() { @@ -818,7 +822,7 @@ where /// This may be called only when the channel is in the `RUNNING` state. /// /// # Returns - /// This returns the number of bytes read, or an [EspError] if an error occurred. + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. #[cfg(not(esp_idf_version_major = "4"))] pub fn read(&mut self, buffer: &mut [u8], timeout: TickType_t) -> Result { if buffer.is_empty() { @@ -870,7 +874,7 @@ where /// This may be called only when the channel is in the `RUNNING` state. /// /// # Returns - /// This returns the number of bytes read, or an [EspError] if an error occurred. + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. /// /// # Safety /// Upon a successful return with `Ok(n_read)`, `buffer[..n_read]` will be initialized. @@ -905,7 +909,7 @@ where /// This may be called only when the channel is in the `RUNNING` state. /// /// # Returns - /// This returns the number of bytes read, or an [EspError] if an error occurred. + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. /// /// # Safety /// Upon a successful return with `Ok(n_read)`, `buffer[..n_read]` will be initialized. @@ -945,14 +949,14 @@ where /// /// # Note /// This can only be called when the channel is in the `READY` state: initialized but not yet started from a driver - /// constructor, or disabled from the `RUNNING` state via [I2sTxChannel::tx_disable]. The channel will enter the - /// `RUNNING` state if it is enabled successfully. + /// constructor, or disabled from the `RUNNING` state via [`tx_disable()`][I2sTxChannel::tx_disable]. The channel + /// will enter the `RUNNING` state if it is enabled successfully. /// /// Enabling the channel will start I2S communications on the hardware. BCLK and WS signals will be generated if /// this is a controller. MCLK will be generated once initialization is finished. /// /// # Errors - /// This will return an [EspError] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. #[cfg(esp_idf_version_major = "4")] pub fn tx_enable(&mut self) -> Result<(), EspError> { unsafe { esp!(i2s_start(self.port as _)) } @@ -962,14 +966,14 @@ where /// /// # Note /// This can only be called when the channel is in the `READY` state: initialized but not yet started from a driver - /// constructor, or disabled from the `RUNNING` state via [I2sTxChannel::tx_disable]. The channel will enter the - /// `RUNNING` state if it is enabled successfully. + /// constructor, or disabled from the `RUNNING` state via [`tx_disable()`][I2sTxChannel::tx_disable]. The channel + /// will enter the `RUNNING` state if it is enabled successfully. /// /// Enabling the channel will start I2S communications on the hardware. BCLK and WS signals will be generated if /// this is a controller. MCLK will be generated once initialization is finished. /// /// # Errors - /// This will return an [EspError] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. #[cfg(not(esp_idf_version_major = "4"))] pub fn tx_enable(&mut self) -> Result<(), EspError> { unsafe { esp!(i2s_channel_enable(self.tx_handle)) } @@ -979,14 +983,14 @@ where /// /// # Note /// This can only be called when the channel is in the `RUNNING` state: the channel has been previously enabled - /// via a call to [I2sTxChannel::tx_enable]. The channel will enter the `READY` state if it is disabled - /// successfully. + /// via a call to [`tx_enable()`][I2sTxChannel::tx_enable]. The channel will enter the `READY` state if it is + /// disabled successfully. /// /// Disabling the channel will stop I2S communications on the hardware. BCLK and WS signals will stop being /// generated if this is a controller. MCLK will continue to be generated. /// /// # Errors - /// This will return an [EspError] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. #[cfg(esp_idf_version_major = "4")] pub fn tx_disable(&mut self) -> Result<(), EspError> { unsafe { esp!(i2s_stop(self.port())) } @@ -996,14 +1000,14 @@ where /// /// # Note /// This can only be called when the channel is in the `RUNNING` state: the channel has been previously enabled - /// via a call to [I2sTxChannel::tx_enable]. The channel will enter the `READY` state if it is disabled + /// via a call to [`tx_enable()`][I2sTxChannel::tx_enable]. The channel will enter the `READY` state if it is disabled /// successfully. /// /// Disabling the channel will stop I2S communications on the hardware. BCLK and WS signals will stop being /// generated if this is a controller. MCLK will continue to be generated. /// /// # Errors - /// This will return an [EspError] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. #[cfg(not(esp_idf_version_major = "4"))] pub fn tx_disable(&mut self) -> Result<(), EspError> { unsafe { esp!(i2s_channel_disable(self.tx_handle)) } @@ -1014,7 +1018,7 @@ where /// This may be called only when the channel is in the `READY` state: initialized but not yet started. /// /// This is used to preload data into the DMA buffer so that valid data can be transmitted immediately after the - /// channel is enabled via [I2sTxChannel::tx_enable]. If this function is not called before enabling the channel, + /// channel is enabled via [`tx_enable()`][I2sTxChannel::tx_enable]. If this function is not called before enabling the channel, /// empty data will be transmitted. /// /// This function can be called multiple times before enabling the channel. Additional calls will concatenate the diff --git a/src/i2s/pdm.rs b/src/i2s/pdm.rs index fe186bd6289..13849851aaf 100644 --- a/src/i2s/pdm.rs +++ b/src/i2s/pdm.rs @@ -1,7 +1,37 @@ //! Pulse density modulation (PDM) driver for the ESP32 I2S peripheral. +//! +//! # Microcontroller support for PDM mode +//! +//! | Microcontroller | PDM Rx | PDM Tx | +//! |--------------------|------------------|--------------------------| +//! | ESP32 | I2S0 | I2S0, hardware version 1 | +//! | ESP32-S2 | _not supported_ | _not supported_ | +//! | ESP32-S3 | I2S0 | I2S0, hardware version 2 | +//! | ESP32-C2 (ESP8684) | _not supported_ | _not supported_ | +//! | ESP32-C3 | _not supported_* | I2S0, hardware version 2 | +//! | ESP32-C6 | _not supported_* | I2S0, hardware version 2 | +//! | ESP32-H2 | _not supported_* | I2S0, hardware version 2 | +//! +//! \* These microcontrollers have PDM Rx capabilities but lack a PDM-to-PCM decoder required by the ESP-IDF SDK. +//! +//! ## Hardware versions +//! +//! Hardware version 1 (ESP32) provides only a single output line, requiring external hardware to demultiplex stereo +//! signals in a time-critical manner; it is unlikely you will see accurate results here. +//! +//! Harware version 2 (all others with PDM Tx support) provide two output lines, allowing for separate left/right +//! channels. +//! +//! See the [`PdmTxSlotConfig documentation`][PdmTxSlotConfig] for more details. + use super::*; use crate::{gpio::*, peripheral::Peripheral}; +// Note on cfg settings: +// esp_idf_soc_i2s_hw_version_1 and esp_idf_soc_i2s_hw_version_2 are defined *only* for ESP-IDF v5.0+. +// When v4.4 support is needed, actual microcontroller names must be used: esp32 for esp_idf_soc_i2s_hw_version_1, +// any(esp32s3,esp32c3,esp32c6,esp32h2) for esp_idf_soc_i2s_hw_version_2. + #[cfg(esp_idf_version_major = "4")] use esp_idf_sys::*; @@ -11,7 +41,6 @@ pub(super) mod config { use esp_idf_sys::*; /// I2S pulse density modulation (PDM) downsampling mode. - #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum PdmDownsample { /// Downsample 8 samples. @@ -38,8 +67,8 @@ pub(super) mod config { } } - /// THe I2S pulse density modulation (PDM) mode receive clock configuration for the I2S peripheral. - #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))] + /// Pulse density modulation (PDM) mode receive clock configuration for the I2S peripheral. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PdmRxClkConfig { /// The sample rate in Hz. pub(super) sample_rate_hz: u32, @@ -54,7 +83,6 @@ pub(super) mod config { pub(super) downsample_mode: PdmDownsample, } - #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))] impl PdmRxClkConfig { /// Create a PDM clock configuration with the specified sample rate in Hz. This will set the clock source to /// PLL_F160M, the MCLK multiple to 256 times the sample rate, and the downsampling mode to 8 samples. @@ -90,7 +118,10 @@ pub(super) mod config { } /// Convert to the ESP-IDF SDK `i2s_pdm_rx_clk_config_t` representation. - #[cfg(not(esp_idf_version_major = "4"))] + #[cfg(all( + any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3), + not(esp_idf_version_major = "4") + ))] #[inline(always)] pub(super) fn as_sdk(&self) -> i2s_pdm_rx_clk_config_t { i2s_pdm_rx_clk_config_t { @@ -102,8 +133,8 @@ pub(super) mod config { } } - /// The I2S pulse density modulation (PDM) mode receive configuration for the I2S peripheral. - #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))] + /// Pulse density modulation (PDM) mode receive configuration for the I2S peripheral. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PdmRxConfig { /// The base channel configuration. pub(super) channel_cfg: Config, @@ -119,7 +150,6 @@ pub(super) mod config { pub(super) gpio_cfg: PdmRxGpioConfig, } - #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))] impl PdmRxConfig { /// Create a new PDM mode receive configuration from the specified clock, slot, and GPIO configurations. pub fn new( @@ -138,7 +168,7 @@ pub(super) mod config { } /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_pdm_rx_config_t` representation. - #[cfg(not(esp_idf_version_major = "4"))] + #[cfg(esp_idf_soc_i2s_supports_pdm_rx)] #[inline(always)] pub(super) fn as_sdk<'d>( &self, @@ -153,7 +183,12 @@ pub(super) mod config { } /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_pdm_rx_config_t` representation. - #[cfg(all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")))] + /// + /// Supported on ESP-IDF 5.1+. + #[cfg(all( + esp_idf_soc_i2s_supports_pdm_rx, // Implicitly selects 5.0+ + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] #[inline(always)] pub(super) fn as_sdk_multi<'d>( &self, @@ -168,7 +203,7 @@ pub(super) mod config { } /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_driver_config_t` representation. - #[cfg(esp_idf_version_major = "4")] + #[cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4"))] #[inline(always)] pub(super) fn as_sdk(&self) -> i2s_driver_config_t { let chan_fmt = match self.slot_cfg.slot_mode { @@ -189,8 +224,8 @@ pub(super) mod config { channel_format: chan_fmt, communication_format: 0, // ? intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1 - dma_buf_count: self.channel_cfg.dma_desc as i32, - dma_buf_len: self.channel_cfg.frames as i32, + dma_buf_count: self.channel_cfg.dma_buffer_count as i32, + dma_buf_len: self.channel_cfg.frames_per_buffer as i32, #[cfg(any(esp32, esp32s2))] use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll), #[cfg(not(any(esp32, esp32s2)))] @@ -219,23 +254,24 @@ pub(super) mod config { } /// PDM mode GPIO (general purpose input/output) receive configuration. - // Not used in v4 - #[cfg(esp_idf_soc_i2s_supports_pdm_rx)] - #[derive(Default)] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PdmRxGpioConfig { /// Whether the clock output is inverted. pub(super) clk_inv: bool, } /// The maximum number of data input pins that can be used in PDM mode. + /// + /// This is 1 on the ESP32 and 4 on the ESP32-S3. #[cfg(esp32)] pub const SOC_I2S_PDM_MAX_RX_LINES: usize = 1; /// The maximum number of data input pins that can be used in PDM mode. + /// + /// This is 1 on the ESP32 and 4 on the ESP32-S3. #[cfg(esp32s3)] pub const SOC_I2S_PDM_MAX_RX_LINES: usize = 4; - #[cfg(esp_idf_soc_i2s_supports_pdm_rx)] impl PdmRxGpioConfig { /// Create a new PDM mode GPIO receive configuration with the specified inversion flag for the clock output. #[inline(always)] @@ -251,9 +287,12 @@ pub(super) mod config { } /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation. - #[cfg(any( - esp_idf_version_major = "4", - all(esp_idf_version_major = "5", esp_idf_version_minor = "0") + /// + /// Note: The bitfields are renamed in ESP-IDF 5.1+. + #[cfg(all( + esp_idf_soc_i2s_supports_pdm_rx, + esp_idf_version_major = "5", + esp_idf_version_minor = "0" ))] pub(crate) fn as_sdk<'d>( &self, @@ -275,7 +314,10 @@ pub(super) mod config { } /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation. - #[cfg(all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")))] + #[cfg(all( + esp_idf_soc_i2s_supports_pdm_rx, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] pub(crate) fn as_sdk<'d>( &self, clk: PeripheralRef<'d, impl OutputPin>, @@ -302,8 +344,13 @@ pub(super) mod config { /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation. /// - /// This will ignore any din pins beyond [SOC_I2S_PDM_MAX_RX_LINES]. - #[cfg(all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")))] + /// This will ignore any din pins beyond [`SOC_I2S_PDM_MAX_RX_LINES`]. + /// + /// Supported on ESP-IDF 5.1+ only. + #[cfg(all( + esp_idf_soc_i2s_supports_pdm_rx, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] pub(crate) fn as_sdk_multi<'d>( &self, clk: PeripheralRef<'d, impl OutputPin>, @@ -338,23 +385,62 @@ pub(super) mod config { } /// PDM mode channel receive slot configuration. - #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))] - #[derive(Clone)] + /// + /// # Note + /// The `slot_mode` and `slot_mask` cause data to be interpreted in different ways, as noted below. + /// WS is the "word select" signal, sometimes called LRCLK (left/right clock). + /// + /// 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`: + /// + /// | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | | + /// |-------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|-----| + /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ... | + /// + /// The actual data in the buffer will be (1-4 bytes, depending on `data_bit_width`): + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
slot_modeslot_maskBuffer Contents
d[0]d[1]d[2]d[3]d[4]d[5]d[6]d[7]
Mono Left 1113151719212325
Right1214161820222426
Both Unspecified behavior
Stereo (ESP32) Any 1112131415161718
Stereo (ESP32-S3)Any 1211141316151817
+ /// + /// Note that, on the ESP32-S3, the right channel is received first. This can be switched by setting + /// [`PdmRxGpioConfig::clk_invert`] to `true` in the merged [`PdmRxConfig`]. + /// + /// For details, refer to the + /// _ESP-IDF Programming Guide_ PDM Rx Usage details for your specific microcontroller: + /// * [ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#pdm-rx-usage) + /// * [ESP32-S3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#pdm-rx-usage) + /// + /// Other microcontrollers do not support PDM receive mode, or do not have a PDM-to-PCM peripheral that allows for decoding + /// the PDM data as required by ESP-IDF. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PdmRxSlotConfig { /// I2S sample data bit width (valid data bits per sample). + #[allow(dead_code)] pub(super) data_bit_width: DataBitWidth, /// I2s slot bit width (total bits per slot). + #[allow(dead_code)] pub(super) slot_bit_width: SlotBitWidth, /// Mono or stereo mode operation. + #[allow(dead_code)] pub(super) slot_mode: SlotMode, /// Are we using the left, right, or both data slots? + #[allow(dead_code)] pub(super) slot_mask: PdmSlotMask, } - #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))] impl PdmRxSlotConfig { /// Configure the PDM mode channel receive slot configuration for the specified bits per sample and slot mode /// in 2 slots. @@ -398,8 +484,9 @@ pub(super) mod config { self } - /// Convert this PDM mode channel receive slot configuration into the ESP-IDF SDK `i2s_pdm_rx_slot_config_t` representation. - #[cfg(not(esp_idf_version_major = "4"))] + /// Convert this PDM mode channel receive slot configuration into the ESP-IDF SDK `i2s_pdm_rx_slot_config_t` + /// representation. + #[cfg(esp_idf_soc_i2s_supports_pdm_rx)] #[inline(always)] pub(super) fn as_sdk(&self) -> i2s_pdm_rx_slot_config_t { i2s_pdm_rx_slot_config_t { @@ -412,13 +499,13 @@ pub(super) mod config { } /// Pulse density modulation (PDM) transmit signal scaling mode. - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] - #[derive(Clone, Copy, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum PdmSignalScale { /// Divide the PDM signal by 2. Div2, /// No scaling. + #[default] None, /// Multiply the PDM signal by 2. @@ -428,19 +515,10 @@ pub(super) mod config { Mul4, } - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] - impl Default for PdmSignalScale { - #[inline(always)] - fn default() -> Self { - Self::None - } - } - - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] impl PdmSignalScale { /// Convert to the ESP-IDF SDK `i2s_pdm_signal_scale_t` representation. + #[cfg_attr(esp_idf_version_major = "4", allow(unused))] #[inline(always)] - #[allow(unused)] // TODO: remove when PDM is implemented. pub(crate) fn as_sdk(&self) -> i2s_pdm_sig_scale_t { match self { Self::Div2 => 0, @@ -454,11 +532,7 @@ pub(super) mod config { /// I2S slot selection in PDM mode. /// /// The default is `PdmSlotMask::Both`. - /// - /// # Note - /// This has different meanings in transmit vs receive mode, and stereo vs mono mode. This may have different - /// behaviors on different targets. For details, refer to the I2S API reference. - #[derive(Clone, Copy, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PdmSlotMask { /// I2S transmits or receives the left slot. Left, @@ -500,7 +574,7 @@ pub(super) mod config { /// /// If the PDM receiver does not use the PDM serial clock, the first configuration should be used. Otherwise, /// use the second configuration. - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PdmTxClkConfig { /// I2S sample rate in Hz. pub(super) sample_rate_hz: u32, @@ -518,7 +592,6 @@ pub(super) mod config { upsample_fs: u32, } - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] impl PdmTxClkConfig { /// Create a new PDM mode transmit clock configuration from the specified sample rate in Hz. This will set the /// clock source to PLL_F160M, the MCLK multiple to 256 times the sample rate, `upsample_fp` to 960, and @@ -591,7 +664,7 @@ pub(super) mod config { } /// The I2S pulse density modulation (PDM) mode transmit configuration for the I2S peripheral. - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PdmTxConfig { /// The base channel configuration. pub(super) channel_cfg: Config, @@ -607,7 +680,6 @@ pub(super) mod config { pub(super) gpio_cfg: PdmTxGpioConfig, } - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] impl PdmTxConfig { /// Create a new PDM mode transmit configuration from the specified clock, slot, and GPIO configurations. pub fn new( @@ -677,8 +749,8 @@ pub(super) mod config { channel_format: chan_fmt, communication_format: 0, // ? intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1 - dma_buf_count: self.channel_cfg.dma_desc as i32, - dma_buf_len: self.channel_cfg.frames as i32, + dma_buf_count: self.channel_cfg.dma_buffer_count as i32, + dma_buf_len: self.channel_cfg.frames_per_buffer as i32, #[cfg(any(esp32, esp32s2))] use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll), #[cfg(not(any(esp32, esp32s2)))] @@ -707,23 +779,24 @@ pub(super) mod config { } /// PDM mode GPIO (general purpose input/output) transmit configuration. - // Not used in v4. - #[cfg(esp_idf_soc_i2s_supports_pdm_tx)] - #[derive(Default)] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PdmTxGpioConfig { /// Whether the clock output is inverted. pub(super) clk_inv: bool, } /// The maximum number of data output pins that can be used in PDM mode. + /// + /// This is 1 on the ESP32, and 2 on the ESP32-S3, ESP32-C3, ESP32-C6, and ESP32-H2. #[cfg(esp32)] pub const SOC_I2S_PDM_MAX_TX_LINES: usize = 1; /// The maximum number of data input pins that can be used in PDM mode. - #[cfg(any(esp32c3, esp32c6, esp32h2))] + /// + /// This is 1 on the ESP32, and 2 on the ESP32-S3, ESP32-C3, ESP32-C6, and ESP32-H2. + #[cfg(any(esp32s3, esp32c3, esp32c6, esp32h2))] pub const SOC_I2S_PDM_MAX_TX_LINES: usize = 2; - #[cfg(esp_idf_soc_i2s_supports_pdm_tx)] impl PdmTxGpioConfig { /// Create a new PDM mode GPIO transmit configuration with the specified inversion flag for the clock output. #[inline(always)] @@ -788,9 +861,10 @@ pub(super) mod config { } /// I2S pulse density modulation (PDM) transmit line mode - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum PdmTxLineMode { /// Standard PDM format output: left and right slot data on a single line. + #[default] OneLineCodec, /// PDM DAC format output: left or right slot data on a single line. @@ -800,13 +874,6 @@ pub(super) mod config { TwoLineDac, } - impl Default for PdmTxLineMode { - #[inline(always)] - fn default() -> Self { - Self::OneLineCodec - } - } - impl PdmTxLineMode { /// Convert this to the ESP-IDF SDK `i2s_pdm_tx_line_mode_t` representation. #[cfg(esp_idf_soc_i2s_hw_version_2)] @@ -820,8 +887,66 @@ pub(super) mod config { } } - /// I2S pulse density modulation (PDM) transmit slot configuration. - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] + /// PDM mode channel transmit slot configuration. + /// + /// # Note + /// The `slot_mode` and `line_mode` (microcontrollers new than ESP32) or `slot_mask` (ESP32) cause data to be + /// interpreted in different ways, as noted below. + /// + /// Assuming the buffered data contains the following samples (where a sample may be 1, 2, 3, or 4 bytes, depending + /// on `data_bit_width`): + /// + /// | **`d[0]`** | **`d[1]`** | **`d[2]`** | **`d[3]`** | **`d[4]`** | **`d[5]`** | **`d[6]`** | **`d[7]`** | + /// |------------|------------|------------|------------|------------|------------|------------|------------| + /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | + /// + /// The actual data on the line will be: + /// + /// ## All microcontrollers except ESP32 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
line_modeslot_modeLineTransmitted Data
WS LowWS HighWS LowWS HighWS LowWS HighWS LowWS High
OneLineCodecMono dout110120130140
Stereodout1112131415161718
OneLineDac Mono dout1111121213131414
TwoLineDac Monodout1212141416161818
dout200000000
Stereodout1212141416161818
dout21111131315151717
+ /// + /// ## ESP32 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
slot_modeslot_maskTransmitted Data
WS LowWS HighWS LowWS HighWS LowWS HighWS LowWS High
Mono Left 110120130140
Right011012013014
Both1112131415161718
Mono Left1111131315151717
Right1212141416161818
Both 1112131415161718
+ /// + /// Modes combinations other than [`SlotMode::Mono`]/[`PdmSlotMask::Both`], + /// [`SlotMode::Stereo`]/[`PdmSlotMask::Left`], and [`SlotMode::Stereo`]/[`PdmSlotMask::Right`] are unlikely to be + /// useful since it requires precise demutiplexing on the bit stream based on the WS clock. + /// + /// For details, refer to the + /// _ESP-IDF Programming Guide_ PDM Tx Usage details for your specific microcontroller: + /// * [ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#pdm-tx-usage) + /// * [ESP32-S3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#pdm-tx-usage) + /// * [ESP32-C3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#pdm-tx-usage) + /// * [ESP32-C6](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#pdm-tx-usage) + /// * [ESP32-H2](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#pdm-tx-usage) + #[derive(Clone, Copy, Debug, PartialEq)] pub struct PdmTxSlotConfig { // data_bit_width and slot_bit_width are omitted; they are always 16 bits. /// Mono or stereo mode operation. @@ -868,7 +993,6 @@ pub(super) mod config { sd_dither2: u32, } - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] impl Default for PdmTxSlotConfig { #[inline(always)] fn default() -> Self { @@ -876,7 +1000,9 @@ pub(super) mod config { } } - #[cfg(any(esp_idf_soc_i2s_supports_pdm_tx, esp32, esp32s3, esp32c3, esp32c6))] + // We don't care about NaN in hp_cutoff_freq; go ahead and force it to be Eq. + impl Eq for PdmTxSlotConfig {} + impl PdmTxSlotConfig { /// Configure the PDM mode channel transmit slot configuration for the specified slot mode in 2 slots. /// @@ -921,6 +1047,10 @@ pub(super) mod config { /// Sets the slot mask on this PDM transmit slot configuration. #[cfg(esp_idf_soc_i2s_hw_version_1)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all(esp32, not(esp_idf_version_major = "4")))) + )] #[inline(always)] pub fn slot_mask(mut self, slot_mask: PdmSlotMask) -> Self { self.slot_mask = slot_mask; @@ -964,6 +1094,13 @@ pub(super) mod config { /// Sets the PDM transmit line mode on this PDM transmit slot configuration. #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) + )] #[inline(always)] pub fn line_mode(mut self, line_mode: PdmTxLineMode) -> Self { self.line_mode = line_mode; @@ -972,6 +1109,13 @@ pub(super) mod config { /// Sets the high-pass filter enable on this PDM transmit slot configuration. #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) + )] #[inline(always)] pub fn hp_enable(mut self, hp_enable: bool) -> Self { self.hp_enable = hp_enable; @@ -980,6 +1124,13 @@ pub(super) mod config { /// Sets the high-pass filter cutoff frequency on this PDM transmit slot configuration. #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) + )] #[inline(always)] pub fn hp_cutoff_freq(mut self, hp_cutoff_freq: f32) -> Self { self.hp_cutoff_freq = hp_cutoff_freq; @@ -988,6 +1139,13 @@ pub(super) mod config { /// Sets the sigma-delta filter dither on this PDM transmit slot configuration. #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) + )] #[inline(always)] pub fn sd_dither(mut self, sd_dither: u32, sd_dither2: u32) -> Self { self.sd_dither = sd_dither; @@ -1025,8 +1183,11 @@ pub(super) mod config { } } -#[cfg(not(esp_idf_version_major = "4"))] #[cfg(esp_idf_soc_i2s_supports_pdm_rx)] +#[cfg_attr( + feature = "nightly", + doc(cfg(all(any(esp32, esp32s3), not(esp_idf_version_major = "4")))) +)] impl<'d> I2sDriver<'d, I2sRx> { /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive /// channel open. @@ -1058,10 +1219,11 @@ impl<'d> I2sDriver<'d, I2sRx> { /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive /// channel open using multiple DIN pins to receive data. - #[cfg(all( - not(esp_idf_version = "4"), - not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) - ))] + #[cfg(not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")))] + #[cfg_attr( + feature = "nightly", + doc(cfg(not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")))) + )] #[allow(clippy::too_many_arguments)] pub fn new_pdm_rx_multi( _i2s: I2SP, @@ -1113,7 +1275,11 @@ impl<'d> I2sDriver<'d, I2sRx> { } } -#[cfg(all(esp_idf_version_major = "4", any(esp32, esp32s3)))] +#[cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4"))] +#[cfg_attr( + feature = "nightly", + doc(cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4"))) +)] impl<'d> I2sDriver<'d, I2sRx> { /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive /// channel open. @@ -1152,8 +1318,14 @@ impl<'d> I2sDriver<'d, I2sRx> { } } -#[cfg(not(esp_idf_version_major = "4"))] #[cfg(esp_idf_soc_i2s_supports_pdm_tx)] +#[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32, esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) +)] impl<'d> I2sDriver<'d, I2sTx> { /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the transmit /// channel open. @@ -1193,7 +1365,17 @@ impl<'d> I2sDriver<'d, I2sTx> { } } -#[cfg(all(esp_idf_version_major = "4", any(esp32, esp32s3, esp32c3, esp32c6)))] +#[cfg(all( + esp_idf_version_major = "4", + any(esp32, esp32s3, esp32c3, esp32c6, esp32h2) +))] +#[cfg_attr( + feature = "nightly", + doc(all( + any(esp32, esp32s3, esp32c3, esp32c6, esp32h2), + esp_idf_version_major = "4" + )) +)] impl<'d> I2sDriver<'d, I2sTx> { /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the transmit /// channel open. diff --git a/src/i2s/std.rs b/src/i2s/std.rs index d122bf5b688..71a3f925194 100644 --- a/src/i2s/std.rs +++ b/src/i2s/std.rs @@ -1,4 +1,17 @@ //! Standard mode driver for the ESP32 I2S peripheral. +//! +//! # Microcontroller support for Standard mode +//! +//! | Microcontroller | Standard Rx | Standard Tx | +//! |--------------------|-----------------|-----------------| +//! | ESP32 | I2S0, I2S1 | I2S0, I2S11 | +//! | ESP32-S2 | I2S0 | I2S0 | +//! | ESP32-S3 | I2S0, I2S1 | I2S0, I2S1 | +//! | ESP32-C2 (ESP8684) | _not supported_ | _not supported_ | +//! | ESP32-C3 | I2S0 | I2S0 | +//! | ESP32-C6 | I2S0 | I2S0 | +//! | ESP32-H2 | I2S0 | I2S0 | + use super::*; use crate::{gpio::*, peripheral::*}; @@ -9,19 +22,23 @@ pub(super) mod config { use crate::{gpio::*, i2s::config::*, peripheral::*}; use esp_idf_sys::*; - /// The standard mode configuration for the I2S peripheral. + /// Standard mode configuration for the I2S peripheral. pub struct StdConfig { /// The base channel configuration. + #[allow(dead_code)] pub(super) channel_cfg: Config, /// Standard mode channel clock configuration. + #[allow(dead_code)] clk_cfg: StdClkConfig, /// Standard mode channel slot configuration. + #[allow(dead_code)] slot_cfg: StdSlotConfig, /// Standard mode channel GPIO configuration. #[cfg(not(esp_idf_version_major = "4"))] + #[allow(dead_code)] gpio_cfg: StdGpioConfig, } @@ -124,8 +141,8 @@ pub(super) mod config { channel_format: chan_fmt, communication_format: self.slot_cfg.comm_fmt.as_sdk(), intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1 - dma_buf_count: self.channel_cfg.dma_desc as i32, - dma_buf_len: self.channel_cfg.frames as i32, + dma_buf_count: self.channel_cfg.dma_buffer_count as i32, + dma_buf_len: self.channel_cfg.frames_per_buffer as i32, #[cfg(any(esp32, esp32s2))] use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll), #[cfg(not(any(esp32, esp32s2)))] @@ -154,7 +171,7 @@ pub(super) mod config { } /// Standard mode channel clock configuration. - #[derive(Clone)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct StdClkConfig { /// I2S sample rate. sample_rate_hz: u32, @@ -222,10 +239,10 @@ pub(super) mod config { } /// The communication format used by the v4 driver. - #[cfg(esp_idf_version_major = "4")] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum StdCommFormat { /// Standard I2S/Philips format. + #[default] Philips, /// MSB-aligned format (data present at first bit clock). @@ -238,8 +255,8 @@ pub(super) mod config { PcmLong, } - #[cfg(esp_idf_version_major = "4")] impl StdCommFormat { + #[cfg(esp_idf_version_major = "4")] #[inline(always)] pub(in crate::i2s) fn as_sdk(&self) -> i2s_comm_format_t { match self { @@ -252,8 +269,7 @@ pub(super) mod config { } /// Standard mode GPIO (general purpose input/output) configuration. - #[cfg(not(esp_idf_version_major = "4"))] - #[derive(Default)] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct StdGpioConfig { /// Invert the BCLK signal. bclk_invert: bool, @@ -265,7 +281,6 @@ pub(super) mod config { ws_invert: bool, } - #[cfg(not(esp_idf_version_major = "4"))] impl StdGpioConfig { /// Create a new standard mode GPIO configuration with the specified inversion flags for BCLK, MCLK, and WS. pub fn new(bclk_invert: bool, mclk_invert: bool, ws_invert: bool) -> Self { @@ -337,8 +352,72 @@ pub(super) mod config { /// Standard mode channel slot configuration. /// - /// To create a slot configuration, use [StdSlotConfig::philips_slot_default], [StdSlotConfig::pcm_slot_default], or - /// [StdSlotConfig::msb_slot_default], then customize it as needed. + /// To create a slot configuration, use [`StdSlotConfig::philips_slot_default`], [`StdSlotConfig::pcm_slot_default`], or + /// [`StdSlotConfig::msb_slot_default`], then customize it as needed. + /// + /// # Note + /// The `slot_mode` and `slot_mask` cause the data to be interpreted in different ways, as noted below. + /// WS is the "word select" signal, sometimes called LRCLK (left/right clock). + /// + /// ## Transmit + /// + /// Assuming the buffered data contains the following samples (where a sample may be 1, 2, 3, or 4 bytes, depending + /// on `data_bit_width`): + /// + /// | **`d[0]`** | **`d[1]`** | **`d[2]`** | **`d[3]`** | **`d[4]`** | **`d[5]`** | **`d[6]`** | **`d[7]`** | + /// |------------|------------|------------|------------|------------|------------|------------|------------| + /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | + /// + /// The actual data on the line will be: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
slot_modeslot_maskTransmitted Data
WS LowWS HighWS LowWS HighWS LowWS HighWS LowWS High
MonoLeft 110120130140
Right011012013014
Both 1111121213131414
StereoLeft 110130150170
Right012014016018
Both 1112131415161718
+ /// + /// + /// ## Receive + /// + /// Assuming the received data contains the following samples (where a sample may be 8, 16, 24, or 32 bits, depending on `data_bit_width`): + /// + /// | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | | + /// |-------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|-----| + /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ... | + /// + /// The actual data in the buffer will be (1-4 bytes, depending on `data_bit_width`): + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
slot_modeslot_maskBuffer Contents
d[0]d[1]d[2]d[3]d[4]d[5]d[6]d[7]
Mono Left 1113151719212325
Right1214161820222426
Both Unspecified behavior
StereoAny 1112131415161718
+ /// + /// For details, refer to the + /// _ESP-IDF Programming Guide_ details for your specific microcontroller: + /// * ESP32: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#std-rx-mode). + /// * ESP32-S2: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/i2s.html#std-tx-mode) + /// * ESP32-S3: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#std-tx-mode) + /// * ESP32-C3: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#std-tx-mode). + /// * ESP32-C6: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#std-tx-mode). + /// * ESP32-H2: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/i2s.html#std-tx-mode). + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct StdSlotConfig { /// I2S sample data bit width (valid data bits per sample). data_bit_width: DataBitWidth, @@ -474,7 +553,7 @@ pub(super) mod config { /// Configure in Philips format in 2 slots. pub fn philips_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self { - let slot_mask = if slot_mode == SlotMode::Mono && cfg!(any(esp32, esp32s2)) { + let slot_mask = if slot_mode == SlotMode::Mono { StdSlotMask::Left } else { StdSlotMask::Both @@ -508,7 +587,7 @@ pub(super) mod config { /// Configure in PCM (short) format in 2 slots. pub fn pcm_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self { - let slot_mask = if slot_mode == SlotMode::Mono && cfg!(any(esp32, esp32s2)) { + let slot_mask = if slot_mode == SlotMode::Mono { StdSlotMask::Left } else { StdSlotMask::Both @@ -542,7 +621,7 @@ pub(super) mod config { /// Configure in MSB format in 2 slots. pub fn msb_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self { - let slot_mask = if slot_mode == SlotMode::Mono && cfg!(any(esp32, esp32s2)) { + let slot_mask = if slot_mode == SlotMode::Mono { StdSlotMask::Left } else { StdSlotMask::Both @@ -597,42 +676,10 @@ pub(super) mod config { } } - // The derived Clone appears to have problems with some of the cfg attributes in rust-analyzer. - impl Clone for StdSlotConfig { - fn clone(&self) -> Self { - Self { - data_bit_width: self.data_bit_width, - slot_bit_width: self.slot_bit_width, - slot_mode: self.slot_mode, - slot_mask: self.slot_mask, - #[cfg(not(esp_idf_version_major = "4"))] - ws_width: self.ws_width, - #[cfg(not(esp_idf_version_major = "4"))] - ws_polarity: self.ws_polarity, - #[cfg(not(esp_idf_version_major = "4"))] - bit_shift: self.bit_shift, - #[cfg(all(any(esp32, esp32s2), not(esp_idf_version_major = "4")))] - msb_right: self.msb_right, - #[cfg(esp_idf_version_major = "4")] - comm_fmt: self.comm_fmt, - #[cfg(not(any(esp32, esp32s2)))] - left_align: self.left_align, - #[cfg(not(any(esp32, esp32s2)))] - big_endian: self.big_endian, - #[cfg(not(any(esp32, esp32s2)))] - bit_order_lsb: self.bit_order_lsb, - } - } - } - /// I2S slot selection in standard mode. /// /// The default is `StdSlotMask::Both`. - /// - /// # Note - /// This has different meanings in transmit vs receive mode, and stereo vs mono mode. This may have different - /// behaviors on different targets. For details, refer to the I2S API reference. - #[derive(Clone, Copy, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum StdSlotMask { /// I2S transmits or receives the left slot. Left, @@ -641,16 +688,10 @@ pub(super) mod config { Right, /// I2S transmits or receives both slots. + #[default] Both, } - impl Default for StdSlotMask { - #[inline(always)] - fn default() -> Self { - Self::Both - } - } - impl StdSlotMask { /// Convert to the ESP-IDF SDK `i2s_std_slot_mask_t` representation. #[cfg(not(esp_idf_version_major = "4"))] diff --git a/src/i2s/tdm.rs b/src/i2s/tdm.rs index 143c83a6e52..05ee338a159 100644 --- a/src/i2s/tdm.rs +++ b/src/i2s/tdm.rs @@ -7,6 +7,10 @@ use esp_idf_sys::*; pub(super) mod config { #[allow(unused)] use crate::{gpio::*, i2s::config::*, peripheral::*}; + use core::{ + convert::TryFrom, + ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}, + }; use esp_idf_sys::*; /// Automatic total number of slots, equivalent to the maximum active slot number. @@ -15,7 +19,8 @@ pub(super) mod config { /// Automatic word-select signal width, equivalent to half the width of a frame. pub const TDM_AUTO_WS_WIDTH: u32 = 0; - /// The time-division multiplexing (TDM) mode configuration for the I2S peripheral. + /// Time-division multiplexing (TDM) mode configuration for the I2S peripheral. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TdmConfig { /// The base channel configuration. pub(super) channel_cfg: Config, @@ -76,20 +81,15 @@ pub(super) mod config { /// `I2S_MODE_ADC_BUILT_IN`, and `I2S_MODE_PDM` should not be used here. #[cfg(esp_idf_version_major = "4")] pub(crate) fn as_sdk(&self) -> i2s_driver_config_t { - let chan_fmt = match self.slot_cfg.slot_mode { - SlotMode::Stereo => i2s_channel_fmt_t_I2S_CHANNEL_FMT_MULTIPLE, - SlotMode::Mono => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_LEFT, // Use channel 0 - }; - i2s_driver_config_t { mode: self.channel_cfg.role.as_sdk(), sample_rate: self.clk_cfg.sample_rate_hz, bits_per_sample: self.slot_cfg.data_bit_width.as_sdk(), - channel_format: chan_fmt, + channel_format: i2s_channel_fmt_t_I2S_CHANNEL_FMT_MULTIPLE, // mono mode doesn't make sense in TDM communication_format: self.slot_cfg.comm_fmt.as_sdk(), intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1 - dma_buf_count: self.channel_cfg.dma_desc as i32, - dma_buf_len: self.channel_cfg.frames as i32, + dma_buf_count: self.channel_cfg.dma_buffer_count as i32, + dma_buf_len: self.channel_cfg.frames_per_buffer as i32, #[cfg(any(esp32, esp32s2))] use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll), #[cfg(not(any(esp32, esp32s2)))] @@ -109,7 +109,7 @@ pub(super) mod config { } /// TDM mode channel clock configuration. - #[derive(Clone)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TdmClkConfig { /// I2S sample rate. sample_rate_hz: u32, @@ -162,7 +162,7 @@ pub(super) mod config { /// PLL_F160M and the MCLK multiple to 256 times the sample rate. /// /// # Note - /// Set the mclk_multiple to [MclkMultiple::M384] when using 24-bit data width. Otherwise, the sample rate + /// Set the mclk_multiple to [`MclkMultiple::M384`] when using 24-bit data width. Otherwise, the sample rate /// might be imprecise since the BCLK division is not an integer. #[cfg(any( esp_idf_version_major = "4", @@ -246,8 +246,7 @@ pub(super) mod config { pub type TdmCommFormat = crate::i2s::std::config::StdCommFormat; /// TDM mode GPIO (general purpose input/output) configuration. - #[cfg(not(esp_idf_version_major = "4"))] - #[derive(Default)] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct TdmGpioConfig { /// Invert the BCLK signal. bclk_invert: bool, @@ -259,7 +258,6 @@ pub(super) mod config { ws_invert: bool, } - #[cfg(not(esp_idf_version_major = "4"))] impl TdmGpioConfig { /// Create a new TDM mode GPIO configuration with the specified inversion flags for BCLK, MCLK, and WS. pub fn new(bclk_invert: bool, mclk_invert: bool, ws_invert: bool) -> Self { @@ -292,6 +290,7 @@ pub(super) mod config { } /// Convert to the ESP-IDF SDK `i2s_tdm_gpio_config_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] pub(crate) fn as_sdk<'d>( &self, bclk: PeripheralRef<'d, impl InputPin + OutputPin>, @@ -328,10 +327,37 @@ pub(super) mod config { } } - /// TDM mode channel slot configuration. + /// TDM mode slot configuration. + /// + /// To create a slot configuration, use [`TdmSlotConfig::philips_slot_default`], + /// [`TdmSlotConfig::pcm_short_slot_default`], [`TdmSlotConfig::pcm_long_slot_default`], or + /// [`TdmSlotConfig::msb_slot_default`], then customize it as needed. + /// + /// In TDM mode, WS (word select, sometimes called LRCLK or left/right clock) becomes a frame synchronization + /// signal that signals the first slot of a frame. The two sides of the TDM link must agree on the number + /// of channels, data bit width, and frame synchronization pattern; this cannot be determined by examining the + /// signal itself. + /// + /// The Philips default pulls the WS line low one BCK period before the first data bit of the first slot is + /// sent and holds it low for 50% of the frame. + /// + #[doc = include_str!("tdm_slot_philips.svg")] /// - /// To create a slot configuration, use [TdmSlotConfig::philips_slot_default], [TdmSlotConfig::pcm_slot_default], or - /// [TdmSlotConfig::msb_slot_default], then customize it as needed. + /// MSB (most-significant bit) mode is similar to Philips mode, except the WS line is pulled low at the same time + /// the first data bit of the first slot is sent. It is held low for 50% of the frame. + /// + #[doc = include_str!("tdm_slot_msb.svg")] + /// + /// PCM (pulse-code modulation) short mode pulls the WS line *high* one BCK period before the first data bit of + /// the first slot is sent, keeps it high for one BCK, then pulls it low for the remainder of the frame. + #[doc = include_str!("tdm_slot_pcm_short.svg")] + /// PCM long mode pulls the WS line *high* one BCK period before the first data bit of the first slot is sent, + /// keeps it high until just before the last data bit of the first slot is sent, then pulls it low for the + /// remainder of the frame. + #[doc = include_str!("tdm_slot_pcm_long.svg")] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + /// + /// Diagrams from _ESP-IDF Programming Guide_; rendered by Wavedrom. pub struct TdmSlotConfig { /// I2S sample data bit width (valid data bits per sample). data_bit_width: DataBitWidth, @@ -339,17 +365,14 @@ pub(super) mod config { /// I2S slot bit width (total bits per slot). slot_bit_width: SlotBitWidth, - /// Mono or stereo mode operation. - slot_mode: SlotMode, - - /// Are we using the left, right, or both data slots? + /// Which slots are active in the TDM frame. slot_mask: TdmSlotMask, /// The word select (WS) signal width, in terms of the bit clock (BCK) periods. #[cfg(not(esp_idf_version_major = "4"))] ws_width: u32, - /// The word select signal polarity; true enables the light lever first. + /// The word select signal polarity; `true` enables the high level first. #[cfg(not(esp_idf_version_major = "4"))] ws_polarity: bool, @@ -381,45 +404,62 @@ pub(super) mod config { impl TdmSlotConfig { /// Update the data bit width on this TDM slot configuration. #[inline(always)] + #[must_use] pub fn data_bit_width(mut self, data_bit_width: DataBitWidth) -> Self { self.data_bit_width = data_bit_width; self } /// Update the slot bit width on this TDM slot configuration. + /// + /// This is normally set to [`SlotBitWidth::Auto`] to match `[data_bit_width][TdmSlotConfig::data_bit_width()]`. #[inline(always)] + #[must_use] pub fn slot_bit_width(mut self, slot_bit_width: SlotBitWidth) -> Self { self.slot_bit_width = slot_bit_width; self } - /// Update the slot mode and mask on this TDM slot configuration. + /// Update the slot mask on this TDM slot configuration. #[inline(always)] - pub fn slot_mode_mask(mut self, slot_mode: SlotMode, slot_mask: TdmSlotMask) -> Self { - self.slot_mode = slot_mode; + #[must_use] + pub fn slot_mask(mut self, slot_mask: TdmSlotMask) -> Self { self.slot_mask = slot_mask; self } /// Update the word select signal width on this TDM slot configuration. + /// + /// This sets the number of bits to keep the word select signal active at the start of each frame. If this is + /// set to 0 ([`TDM_AUTO_WS_WIDTH`]), the word select signal will be kept active for half of the frame. #[cfg(not(esp_idf_version_major = "4"))] #[inline(always)] + #[must_use] pub fn ws_width(mut self, ws_width: u32) -> Self { self.ws_width = ws_width; self } /// Update the word select signal polarity on this TDM slot configuration. + /// + /// Setting this to `true` will make the word select (WS) signal active high at the start (PCM modes). + /// Setting this to `false` will make the WS signal active low at the start (Philips and MSB modes). #[cfg(not(esp_idf_version_major = "4"))] #[inline(always)] + #[must_use] pub fn ws_polarity(mut self, ws_polarity: bool) -> Self { self.ws_polarity = ws_polarity; self } /// Update the bit shift flag on this TDM slot configuration. + /// + /// Setting this to `true` will activate the word select (WS) signal lone BCK period before the first data bit + /// of the first slot is sent (Philips and PCM modes). Setting this to `false` will activate the WS + /// signal at the same time the first data bit of the first slot is sent (MSB mode). #[cfg(not(esp_idf_version_major = "4"))] #[inline(always)] + #[must_use] pub fn bit_shift(mut self, bit_shift: bool) -> Self { self.bit_shift = bit_shift; self @@ -428,41 +468,66 @@ pub(super) mod config { /// Update the communication format on this TDM slot configuration. #[cfg(esp_idf_version_major = "4")] #[inline(always)] + #[must_use] pub fn comm_fmt(mut self, comm_fmt: TdmCommFormat) -> Self { self.comm_fmt = comm_fmt; self } /// Update the left-alignment flag on this TDM slot configuration. + /// + /// This only has an effect when `[slot_bit_width][TdmSlotMask::slot_bit_width()]` is greater than + /// `[data_bit_width][TdmSlotMask::data_bit_width()]`. Setting this to `true` will left-align the data in the slot and + /// fill the right-most bits (usually the least-significant bits) with zeros. Setting this to `false` will right-align the + /// data in the slot and fill the left-most bits (usually the most-significant bits) with zeros. #[inline(always)] + #[must_use] pub fn left_align(mut self, left_align: bool) -> Self { self.left_align = left_align; self } /// Update the big-endian flag on this TDM slot configuration. + /// + /// This affects the interpretation of the data when `[data_bit_width][TdmSlotMask::data_bit_width()]` is + /// greater than 8. Setting this to + /// `true` will interpret the data as big-endian. Setting this to `false` will interpret the data as + /// little-endian (the default, and the native endian-ness of all ESP32 microcontrollers). #[inline(always)] + #[must_use] pub fn big_endian(mut self, big_endian: bool) -> Self { self.big_endian = big_endian; self } /// Update the LSB-first flag on this TDM slot configuration. + /// + /// Setting this to `true` will transmit data LSB-first (no known modes do this). Setting this to `false` + /// will transmit data MSB-first (the default for all known modes). #[inline(always)] + #[must_use] pub fn bit_order_lsb(mut self, bit_order_lsb: bool) -> Self { self.bit_order_lsb = bit_order_lsb; self } /// Update the skip mask flag on this TDM slot configuration. + /// + /// Setting this to `true` will ignore `[slot_mask][TdmSlotMask::slot_mask()]` and transmit all slots. Setting this to `false` will + /// respect the slot mask. #[inline(always)] + #[must_use] pub fn skip_mask(mut self, skip_mask: bool) -> Self { self.skip_mask = skip_mask; self } /// Update the total number of slots on this TDM slot configuration. + /// + /// Setting this to 0 ([`TDM_AUTO_SLOT_NUM`]) will automatically set the total number of slots to the + /// the number of active slots in `[slot_mask][TdmSlotMask::slot_mask()]`. #[inline(always)] + #[must_use] pub fn total_slots(mut self, total_slots: u32) -> Self { self.total_slots = total_slots; self @@ -470,15 +535,11 @@ pub(super) mod config { /// Configure in Philips format with the active slots enabled by the specified mask. #[inline(always)] - pub fn philips_slot_default( - bits_per_sample: DataBitWidth, - slot_mode: SlotMode, - slot_mask: TdmSlotMask, - ) -> Self { + #[must_use] + pub fn philips_slot_default(bits_per_sample: DataBitWidth, slot_mask: TdmSlotMask) -> Self { Self { data_bit_width: bits_per_sample, slot_bit_width: SlotBitWidth::Auto, - slot_mode, slot_mask, #[cfg(not(esp_idf_version_major = "4"))] ws_width: TDM_AUTO_WS_WIDTH, @@ -498,15 +559,11 @@ pub(super) mod config { /// Configure in MSB format with the active slots enabled by the specified mask. #[inline(always)] - pub fn msb_slot_default( - bits_per_sample: DataBitWidth, - slot_mode: SlotMode, - slot_mask: TdmSlotMask, - ) -> Self { + #[must_use] + pub fn msb_slot_default(bits_per_sample: DataBitWidth, slot_mask: TdmSlotMask) -> Self { Self { data_bit_width: bits_per_sample, slot_bit_width: SlotBitWidth::Auto, - slot_mode, slot_mask, #[cfg(not(esp_idf_version_major = "4"))] ws_width: TDM_AUTO_WS_WIDTH, @@ -525,15 +582,15 @@ pub(super) mod config { } /// Configure in PCM (short) format with the active slots enabled by the specified mask. + #[inline(always)] + #[must_use] pub fn pcm_short_slot_default( bits_per_sample: DataBitWidth, - slot_mode: SlotMode, slot_mask: TdmSlotMask, ) -> Self { Self { data_bit_width: bits_per_sample, slot_bit_width: SlotBitWidth::Auto, - slot_mode, slot_mask, #[cfg(not(esp_idf_version_major = "4"))] ws_width: 1, @@ -552,15 +609,15 @@ pub(super) mod config { } /// Configure in PCM (long) format with the active slots enabled by the specified mask. + #[inline(always)] + #[must_use] pub fn pcm_long_slot_default( bits_per_sample: DataBitWidth, - slot_mode: SlotMode, slot_mask: TdmSlotMask, ) -> Self { Self { data_bit_width: bits_per_sample, slot_bit_width: SlotBitWidth::Auto, - slot_mode, slot_mask, #[cfg(not(esp_idf_version_major = "4"))] ws_width: bits_per_sample.into(), @@ -585,7 +642,7 @@ pub(super) mod config { i2s_tdm_slot_config_t { data_bit_width: self.data_bit_width.as_sdk(), slot_bit_width: self.slot_bit_width.as_sdk(), - slot_mode: self.slot_mode.as_sdk(), + slot_mode: SlotMode::Stereo.as_sdk(), // mono mode doesn't make sense in TDM slot_mask: self.slot_mask.as_sdk(), ws_width: self.ws_width, ws_pol: self.ws_polarity, @@ -599,81 +656,275 @@ pub(super) mod config { } } - // The derived Clone appears to have problems with some of the cfg attributes in rust-analyzer. - impl Clone for TdmSlotConfig { - fn clone(&self) -> Self { - Self { - data_bit_width: self.data_bit_width, - slot_bit_width: self.slot_bit_width, - slot_mode: self.slot_mode, - slot_mask: self.slot_mask, - #[cfg(not(esp_idf_version_major = "4"))] - ws_width: self.ws_width, - #[cfg(not(esp_idf_version_major = "4"))] - ws_polarity: self.ws_polarity, - #[cfg(not(esp_idf_version_major = "4"))] - bit_shift: self.bit_shift, - #[cfg(esp_idf_version_major = "4")] - comm_fmt: self.comm_fmt, - left_align: self.left_align, - big_endian: self.big_endian, - bit_order_lsb: self.bit_order_lsb, - skip_mask: self.skip_mask, - total_slots: self.total_slots, - } - } + /// An individual TDM slot. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum TdmSlot { + /// TDM slot #0 + Slot0, + + /// TDM slot #1 + Slot1, + + /// TDM slot #2 + Slot2, + + /// TDM slot #3 + Slot3, + + /// TDM slot #4 + Slot4, + + /// TDM slot #5 + Slot5, + + /// TDM slot #6 + Slot6, + + /// TDM slot #7 + Slot7, + + /// TDM slot #8 + Slot8, + + /// TDM slot #9 + Slot9, + + /// TDM slot #10 + Slot10, + + /// TDM slot #11 + Slot11, + + /// TDM slot #12 + Slot12, + + /// TDM slot #13 + Slot13, + + /// TDM slot #14 + Slot14, + + /// TDM slot #15 + Slot15, } /// Mask of TDM slots to enable. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct TdmSlotMask(u16); - pub const SLOT0: TdmSlotMask = TdmSlotMask(1 << 0); - pub const SLOT1: TdmSlotMask = TdmSlotMask(1 << 1); - pub const SLOT2: TdmSlotMask = TdmSlotMask(1 << 2); - pub const SLOT3: TdmSlotMask = TdmSlotMask(1 << 3); - pub const SLOT4: TdmSlotMask = TdmSlotMask(1 << 4); - pub const SLOT5: TdmSlotMask = TdmSlotMask(1 << 5); - pub const SLOT6: TdmSlotMask = TdmSlotMask(1 << 6); - pub const SLOT7: TdmSlotMask = TdmSlotMask(1 << 7); - pub const SLOT8: TdmSlotMask = TdmSlotMask(1 << 8); - pub const SLOT9: TdmSlotMask = TdmSlotMask(1 << 9); - pub const SLOT10: TdmSlotMask = TdmSlotMask(1 << 10); - pub const SLOT11: TdmSlotMask = TdmSlotMask(1 << 11); - pub const SLOT12: TdmSlotMask = TdmSlotMask(1 << 12); - pub const SLOT13: TdmSlotMask = TdmSlotMask(1 << 13); - pub const SLOT14: TdmSlotMask = TdmSlotMask(1 << 14); - pub const SLOT15: TdmSlotMask = TdmSlotMask(1 << 15); - - impl core::ops::BitAnd for TdmSlotMask { + /// Attempt to convert from a `u8` to a `TdmSlot`. + impl TryFrom for TdmSlot { + type Error = EspError; + + fn try_from(slot: u8) -> Result { + match slot { + 0 => Ok(Self::Slot0), + 1 => Ok(Self::Slot1), + 2 => Ok(Self::Slot2), + 3 => Ok(Self::Slot3), + 4 => Ok(Self::Slot4), + 5 => Ok(Self::Slot5), + 6 => Ok(Self::Slot6), + 7 => Ok(Self::Slot7), + 8 => Ok(Self::Slot8), + 9 => Ok(Self::Slot9), + 10 => Ok(Self::Slot10), + 11 => Ok(Self::Slot11), + 12 => Ok(Self::Slot12), + 13 => Ok(Self::Slot13), + 14 => Ok(Self::Slot14), + 15 => Ok(Self::Slot15), + _ => Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()), + } + } + } + + /// Convert a `TdmSlot` to a `u8`. + impl From for u8 { + fn from(slot: TdmSlot) -> u8 { + match slot { + TdmSlot::Slot0 => 0, + TdmSlot::Slot1 => 1, + TdmSlot::Slot2 => 2, + TdmSlot::Slot3 => 3, + TdmSlot::Slot4 => 4, + TdmSlot::Slot5 => 5, + TdmSlot::Slot6 => 6, + TdmSlot::Slot7 => 7, + TdmSlot::Slot8 => 8, + TdmSlot::Slot9 => 9, + TdmSlot::Slot10 => 10, + TdmSlot::Slot11 => 11, + TdmSlot::Slot12 => 12, + TdmSlot::Slot13 => 13, + TdmSlot::Slot14 => 14, + TdmSlot::Slot15 => 15, + } + } + } + + /// Convert a `TdmSlot` into a `TdmSlotMask`. + impl From for TdmSlotMask { + #[inline(always)] + fn from(slot: TdmSlot) -> TdmSlotMask { + TdmSlotMask(1 << u8::from(slot)) + } + } + + /// Bitwise AND a`TdmSlot` with another `TdmSlot` to produce a `TdmSlotMask`. + /// + /// If the slots are the same, the result is a `TdmSlotMask` containing that slot. + /// Otherwise, the result is an empty slot mask. + impl BitAnd for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitand(self, rhs: Self) -> Self::Output { + TdmSlotMask::from(self) & TdmSlotMask::from(rhs) + } + } + + /// Bitwise AND a `TdmSlot` with a `TdmSlotMask` to produce a `TdmSlotMask`. + /// + /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot. + /// Otherwise, the result is an empty slot mask. + impl BitAnd for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitand(self, rhs: TdmSlotMask) -> Self::Output { + TdmSlotMask::from(self) & rhs + } + } + + /// Bitwise AND a `TdmSlotMask` with a `TdmSlot` to produce a `TdmSlotMask`. + /// + /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot. + /// Otherwise, the result is an empty slot mask. + impl BitAnd for TdmSlotMask { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitand(self, rhs: TdmSlot) -> Self::Output { + self & TdmSlotMask::from(rhs) + } + } + + /// Bitwise AND a `TdmSlotMask` with another `TdmSlotMask` to produce a `TdmSlotMask`. + /// + /// The result is a slot mask containing the slots that are common to both slot masks. + impl BitAnd for TdmSlotMask { type Output = Self; + #[inline(always)] fn bitand(self, rhs: Self) -> Self::Output { Self(self.0 & rhs.0) } } - impl core::ops::BitAndAssign for TdmSlotMask { + /// Bitwise AND a `TdmSlotMask` with a `TdmSlot` and assign the result to `self`. + /// + /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot. + /// Otherwise, the result is an empty slot mask. + impl BitAndAssign for TdmSlotMask { + #[inline(always)] + fn bitand_assign(&mut self, rhs: TdmSlot) { + self.0 &= TdmSlotMask::from(rhs).0; + } + } + + /// Bitwise AND a `TdmSlotMask` with another `TdmSlotMask` and assign the result to `self`. + /// + /// The result is a slot mask containing the slots that are common to both slot masks. + impl BitAndAssign for TdmSlotMask { + #[inline(always)] fn bitand_assign(&mut self, rhs: Self) { self.0 &= rhs.0; } } - impl core::ops::BitOr for TdmSlotMask { + /// Bitwise OR a`TdmSlot` with another `TdmSlot` to produce a `TdmSlotMask`. + /// + /// The result is a `TdmSlotMask` containing both slots. + impl BitOr for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitor(self, rhs: Self) -> Self::Output { + TdmSlotMask::from(self) | TdmSlotMask::from(rhs) + } + } + + /// Bitwise OR a`TdmSlot` with a `TdmSlotMask` to produce a `TdmSlotMask`. + /// + /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask. + impl BitOr for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitor(self, rhs: TdmSlotMask) -> Self::Output { + TdmSlotMask::from(self) | rhs + } + } + + /// Bitwise OR a`TdmSlotMask` with a `TdmSlot` to produce a `TdmSlotMask`. + /// + /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask. + impl BitOr for TdmSlotMask { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitor(self, rhs: TdmSlot) -> Self::Output { + self | TdmSlotMask::from(rhs) + } + } + + /// Bitwise OR a`TdmSlotMask` with another `TdmSlotMask` to produce a `TdmSlotMask`. + /// + /// The result is a `TdmSlotMask` containing the slots in either slot mask. + impl BitOr for TdmSlotMask { type Output = Self; + #[inline(always)] fn bitor(self, rhs: Self) -> Self::Output { Self(self.0 | rhs.0) } } - impl core::ops::BitOrAssign for TdmSlotMask { + /// Bitwise OR a`TdmSlotMask` with a `TdmSlot` and assign the result to `self`. + /// + /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask. + impl BitOrAssign for TdmSlotMask { + #[inline(always)] + fn bitor_assign(&mut self, rhs: TdmSlot) { + self.0 |= TdmSlotMask::from(rhs).0; + } + } + + /// Bitwise OR a`TdmSlotMask` with another `TdmSlotMask` and assign the result to `self. + /// + /// The result is a `TdmSlotMask` containing the slots in either slot mask. + impl BitOrAssign for TdmSlotMask { + #[inline(always)] fn bitor_assign(&mut self, rhs: Self) { self.0 |= rhs.0; } } - impl core::ops::Not for TdmSlotMask { + /// Produce the bitwise NOT of a `TdmSlot` to produce a `TdmSlotMask` containing all slots + /// except the original slot. + impl Not for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn not(self) -> Self::Output { + !TdmSlotMask::from(self) + } + } + + /// Produce the bitwise NOT of a `TdmSlotMask` to produce a `TdmSlotMask` containing all slots + /// except the slots in the original slot mask. + impl Not for TdmSlotMask { type Output = Self; fn not(self) -> Self::Output { @@ -682,6 +933,30 @@ pub(super) mod config { } impl TdmSlotMask { + /// Creates a `TdmSlotMask` from the raw bit mask value. + #[inline(always)] + pub fn from_mask_value(value: u16) -> Self { + Self(value) + } + + /// Indicates whether this slot mask is empty. + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0 == 0 + } + + /// Returns the number of slots in the slot mask. + #[inline(always)] + pub fn len(&self) -> usize { + self.0.count_ones() as usize + } + + /// Returns the mask value as a `u16`. + #[inline(always)] + pub fn mask_value(&self) -> u16 { + self.0 + } + /// Converts this mask to an ESP-IDF SDK `i2s_tdm_slot_mask_t` value. #[cfg(not(esp_idf_version_major = "4"))] #[inline(always)] @@ -787,6 +1062,8 @@ impl<'d, Dir> I2sDriver<'d, Dir> { impl<'d> I2sDriver<'d, I2sBiDir> { /// Create a new TDM mode driver for the given I2S peripheral with both the receive and transmit channels open. + #[cfg(not(any(esp32, esp32s2)))] + #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))] #[allow(clippy::too_many_arguments)] pub fn new_tdm_bidir( i2s: impl Peripheral

+ 'd, @@ -813,6 +1090,8 @@ impl<'d> I2sDriver<'d, I2sBiDir> { impl<'d> I2sDriver<'d, I2sRx> { /// Create a new TDM mode driver for the given I2S peripheral with only the receive channel open. + #[cfg(not(any(esp32, esp32s2)))] + #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))] #[allow(clippy::too_many_arguments)] pub fn new_tdm_rx( i2s: impl Peripheral

+ 'd, @@ -838,6 +1117,8 @@ impl<'d> I2sDriver<'d, I2sRx> { impl<'d> I2sDriver<'d, I2sTx> { /// Create a new TDM mode driver for the given I2S peripheral with only the transmit channel open. + #[cfg(not(any(esp32, esp32s2)))] + #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))] #[allow(clippy::too_many_arguments)] pub fn new_tdm_tx( i2s: impl Peripheral

+ 'd, diff --git a/src/i2s/tdm_slot_msb.svg b/src/i2s/tdm_slot_msb.svg new file mode 100644 index 00000000000..2bb64577ba5 --- /dev/null +++ b/src/i2s/tdm_slot_msb.svg @@ -0,0 +1,4 @@ + + + +TDM MSB FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBLSBMSBLSBMSBFirst (Left) SlotsSecond (Right) SlotsSlot 1Slot 2...Slot nSlot n+1... \ No newline at end of file diff --git a/src/i2s/tdm_slot_msb.wavedrom b/src/i2s/tdm_slot_msb.wavedrom new file mode 100644 index 00000000000..a10541bb698 --- /dev/null +++ b/src/i2s/tdm_slot_msb.wavedrom @@ -0,0 +1,60 @@ +{ + "head": { + "text": "TDM MSB Frame" + }, + "signal": [ + { + "node": ".E.........F.........G", + "phase": -0.35 + }, + { + "name": "BCLK", + "wave": "p.d.p.d.pd.pd.p.d.pd.p" + }, + { + "name": "WS", + "wave": "10dd0.dd0dd1uu1.uu1uu0.", + "node": "TH...................R", + "phase": -0.35 + }, + { + "name": "DIN / DOUT", + "wave": "x2x|22x|2x|2x|22x|2x|2", + "data": [ + "MSB", + "LSB", + "MSB", + "LSB", + "MSB", + "LSB", + "MSB", + "LSB", + "MSB" + ], + "node": ".H...K...I.M...N...P.Q", + "phase": -0.35 + }, + { + "node": "UA...B...C.D...J...L.S", + "phase": -0.35 + } + ], + "edge": [ + "E<->F First (Left) Slots", + "F<->G Second (Right) Slots", + "A<->B Slot 1", + "B<->C Slot 2", + "C<->D ...", + "D<->J Slot n", + "J<->L Slot n+1", + "L<->S ...", + "A-E", + "K-B", + "C-I", + "D-F", + "J-N", + "L-P", + "Q-G", + "S-R" + ] +} \ No newline at end of file diff --git a/src/i2s/tdm_slot_pcm_long.svg b/src/i2s/tdm_slot_pcm_long.svg new file mode 100644 index 00000000000..d00ad67db23 --- /dev/null +++ b/src/i2s/tdm_slot_pcm_long.svg @@ -0,0 +1 @@ +TDM PCM Long FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBFramebit shiftSlot 1...Slot n \ No newline at end of file diff --git a/src/i2s/tdm_slot_pcm_long.wavedrom b/src/i2s/tdm_slot_pcm_long.wavedrom new file mode 100644 index 00000000000..3ef50c266a2 --- /dev/null +++ b/src/i2s/tdm_slot_pcm_long.wavedrom @@ -0,0 +1,52 @@ +{ + "head": { + "text": "TDM PCM Long Frame" + }, + "signal": [ + { + "node": ".UE.........G", + "phase": -0.35 + }, + { + "name": "BCLK", + "wave": "p..d.p.d.pd.pd" + }, + { + "name": "WS", + "wave": "01u.10d...01u.", + "node": ".NH", + "phase": -0.35 + }, + { + "name": "DIN / DOUT", + "wave": "xx2x|2x|2x|22x", + "data": [ + "MSB", + "LSB", + "MSB", + "LSB", + "MSB" + ], + "node": "..H..FK.I...M", + "phase": -0.35 + }, + { + "node": ".ZA...B.C...D...J", + "phase": -0.35 + } + ], + "edge": [ + "E<->G Frame", + "Z<->A bit shift", + "A<->B Slot 1", + "B<->C ...", + "C<->D Slot n", + "U-Z", + "A-E", + "K-B", + "C-I", + "D-M", + "D-G", + "L-F" + ] +} diff --git a/src/i2s/tdm_slot_pcm_short.svg b/src/i2s/tdm_slot_pcm_short.svg new file mode 100644 index 00000000000..d10462f2058 --- /dev/null +++ b/src/i2s/tdm_slot_pcm_short.svg @@ -0,0 +1 @@ +TDM PCM Short FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBFramebit shiftSlot 1...Slot n \ No newline at end of file diff --git a/src/i2s/tdm_slot_pcm_short.wavedrom b/src/i2s/tdm_slot_pcm_short.wavedrom new file mode 100644 index 00000000000..d6048710f88 --- /dev/null +++ b/src/i2s/tdm_slot_pcm_short.wavedrom @@ -0,0 +1,52 @@ +{ + "head": { + "text": "TDM PCM Short Frame" + }, + "signal": [ + { + "node": ".UE.........G", + "phase": -0.35 + }, + { + "name": "BCLK", + "wave": "p..d.p.d.pd.pd" + }, + { + "name": "WS", + "wave": "010........10.", + "node": ".NH", + "phase": -0.35 + }, + { + "name": "DIN / DOUT", + "wave": "xx2x|2x|2x|22x", + "data": [ + "MSB", + "LSB", + "MSB", + "LSB", + "MSB" + ], + "node": "..H..FK.I...M", + "phase": -0.35 + }, + { + "node": ".ZA...B.C...D...J", + "phase": -0.35 + } + ], + "edge": [ + "E<->G Frame", + "Z<->A bit shift", + "A<->B Slot 1", + "B<->C ...", + "C<->D Slot n", + "U-Z", + "A-E", + "K-B", + "C-I", + "D-M", + "D-G", + "L-F" + ] +} diff --git a/src/i2s/tdm_slot_philips.svg b/src/i2s/tdm_slot_philips.svg new file mode 100644 index 00000000000..9eb063321e3 --- /dev/null +++ b/src/i2s/tdm_slot_philips.svg @@ -0,0 +1,4 @@ + + + +TDM Philips FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBLSBMSBLSBMSBFirst (Left) SlotsSecond (Right) Slotsbit shiftSlot 1Slot 2...Slot nSlot n+1... \ No newline at end of file diff --git a/src/i2s/tdm_slot_philips.wavedrom b/src/i2s/tdm_slot_philips.wavedrom new file mode 100644 index 00000000000..49de6d888a3 --- /dev/null +++ b/src/i2s/tdm_slot_philips.wavedrom @@ -0,0 +1,62 @@ +td{ + "head": { + "text": "TDM Philips Frame" + }, + "signal": [ + { + "node": "..E.........F.........G", + "phase": -0.35 + }, + { + "name": "BCLK", + "wave": "p..d.p.d.pd.pd.p.d.pd.p" + }, + { + "name": "WS", + "wave": "10dd0.dd0dd1uu1.uu1uu0.", + "node": ".TH...................R", + "phase": -0.35 + }, + { + "name": "DIN / DOUT", + "wave": "xx2x|22x|2x|2x|22x|2x|2", + "data": [ + "MSB", + "LSB", + "MSB", + "LSB", + "MSB", + "LSB", + "MSB", + "LSB", + "MSB" + ], + "node": "..H...K...I.M...N...P.Q", + "phase": -0.35 + }, + { + "node": ".UA...B...C.D...J...L.S", + "phase": -0.35 + } + ], + "edge": [ + "E<->F First (Left) Slots", + "F<->G Second (Right) Slots", + "U<->A bit shift", + "A<->B Slot 1", + "B<->C Slot 2", + "C<->D ...", + "D<->J Slot n", + "J<->L Slot n+1", + "L<->S ...", + "A-E", + "K-B", + "C-I", + "D-F", + "J-N", + "L-P", + "Q-G", + "S-R", + "T-U" + ] +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 8aa00210e67..11b6d90be4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![cfg_attr( feature = "nightly", feature(async_fn_in_trait), + feature(doc_cfg), feature(impl_trait_projections), allow(incomplete_features) )] @@ -43,7 +44,12 @@ pub mod gpio; pub mod hall; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod i2c; -#[cfg(all(not(feature = "riscv-ulp-hal"), esp_idf_comp_driver_enabled))] +#[cfg(all( + not(feature = "riscv-ulp-hal"), + not(esp32c2), + esp_idf_comp_driver_enabled +))] +#[cfg_attr(feature = "nightly", doc(cfg(not(esp32c2))))] pub mod i2s; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod interrupt;