diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 158c0b4a2..fd345723d 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ESP32: Expose `psram_vaddr_mode` via `PsramConfig` (#3990) - ESP32-S3: Expose more `Camera` config options (#3996) - `ShaBackend, ShaContext`: Work-queue based SHA driver (#4013) +- I2S: `i2s::master::Config` with support for more TDM mode standards (#3985) ### Changed @@ -52,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `RtcSlowClock::RtcFastClock8m` has been renamed to `RtcFastClock::RtcFastClockRcFast` (#3993) - `RtcSlowClock::RtcSlowClockRtc` has been renamed to `RtcSlowClock::RtcSlowClockRcSlow` (#3993) - The `Raw: RawChannelAccess` of `rmt::Channel` has been erased; channel numbers are always dynamic now. (#3980) +- ESP32-S2: `i2s::master::DataFormat` now includes 8-bit and 24-bit data widths (#3985) ### Fixed @@ -72,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `AesFlavour` trait and `AesX` structs have been removed. (#3880) - `Xtal::Other` has been removed (#3983) - ESP32-C3/S3: removed the UHCI1 peripheral singleton (#4007) +- `i2s::master::Standard` has been removed (#3985) ## [v1.0.0-rc.0] - 2025-07-16 diff --git a/esp-hal/MIGRATING-1.0.0-rc.0.md b/esp-hal/MIGRATING-1.0.0-rc.0.md index a9cc2de56..9d9cb024a 100644 --- a/esp-hal/MIGRATING-1.0.0-rc.0.md +++ b/esp-hal/MIGRATING-1.0.0-rc.0.md @@ -221,3 +221,21 @@ let peripherals = esp_hal::init( + .with_data6(peripherals.GPIO16) + .with_data7(peripherals.GPIO15); ``` + +## I2S Changes + +I2S configuration is now done using `i2s::master::Config`. Sample rate and data format, previously passed +to the constructor, have to be assigned to `Config` instead. + +```diff + let i2s = I2s::new( + peripherals.I2S0, +- Standard::Philips, +- DataFormat::Data16Channel16, +- Rate::from_hz(44100), + dma_channel, ++ Config::new_tdm_philips() ++ .with_data_format(DataFormat::Data16Channel16) ++ .with_sample_rate(Rate::from_hz(44100)), + ); +``` diff --git a/esp-hal/src/i2s/master.rs b/esp-hal/src/i2s/master.rs index 35731720f..303ea030f 100644 --- a/esp-hal/src/i2s/master.rs +++ b/esp-hal/src/i2s/master.rs @@ -34,24 +34,60 @@ //! - `DMA` //! - `system` (to configure and enable the I2S peripheral) //! +//! ### Standards +//! +//! I2S supports different standards, which you can access using [Config]`::new_*` methods. You +//! can also configure custom data formats using methods such as [Config::with_msb_shift], +//! [Config::with_ws_width], [Config::with_ws_polarity], etc. +//! +//! 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. +//! +//! #### TDM Philips Standard +//! +//! TDM Philips mode 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")] +//! #### TDM MSB Standard +//! +//! 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")] +//! #### TDM PCM Short Standard +//! +//! 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")] +//! #### TDM PCM Long Standard +//! +//! 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")] +//! Diagrams from _ESP-IDF Programming Guide_; rendered by Wavedrom. +//! //! ## Examples //! //! ### I2S Read //! //! ```rust, no_run //! # {before_snippet} -//! # use esp_hal::i2s::master::{I2s, Standard, DataFormat}; +//! # use esp_hal::i2s::master::{I2s, Channels, DataFormat, Config}; //! # use esp_hal::dma_buffers; //! # {dma_channel} //! let (mut rx_buffer, rx_descriptors, _, _) = dma_buffers!(4 * 4092, 0); //! //! let i2s = I2s::new( //! peripherals.I2S0, -//! Standard::Philips, -//! DataFormat::Data16Channel16, -//! Rate::from_hz(44100), //! dma_channel, -//! ); +//! Config::new_tdm_philips() +//! .with_sample_rate(Rate::from_hz(44100)) +//! .with_data_format(DataFormat::Data16Channel16) +//! .with_channels(Channels::STEREO), +//! )?; //! # {mclk} //! let mut i2s_rx = i2s //! .i2s_rx @@ -75,7 +111,7 @@ //! //! ## Implementation State //! -//! - Only TDM Philips standard is supported. +//! - Only TDM mode is supported. use enumset::{EnumSet, EnumSetType}; use private::*; @@ -161,18 +197,8 @@ impl From for Error { } } -/// Supported standards. -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Standard { - /// The Philips I2S standard. - Philips, - // Tdm, - // Pdm, -} - /// Supported data formats -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg(not(any(esp32, esp32s2)))] pub enum DataFormat { @@ -193,9 +219,24 @@ pub enum DataFormat { } /// Supported data formats -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[cfg(any(esp32, esp32s2))] +#[cfg(esp32s2)] +pub enum DataFormat { + /// 32-bit data width and 32-bit channel width. + Data32Channel32, + /// 24-bit data width and 24-bit channel width. + Data24Channel24, + /// 16-bit data width and 16-bit channel width. + Data16Channel16, + /// 8-bit data width and 8-bit channel width. + Data8Channel8, +} + +/// Supported data formats +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(esp32)] pub enum DataFormat { /// 32-bit data width and 32-bit channel width. Data32Channel32, @@ -232,7 +273,30 @@ impl DataFormat { } } -#[cfg(any(esp32, esp32s2))] +#[cfg(esp32s2)] +impl DataFormat { + /// Returns the number of data bits for the selected data format. + pub fn data_bits(&self) -> u8 { + match self { + DataFormat::Data32Channel32 => 32, + DataFormat::Data24Channel24 => 24, + DataFormat::Data16Channel16 => 16, + DataFormat::Data8Channel8 => 8, + } + } + + /// Returns the number of channel bits for the selected data format. + pub fn channel_bits(&self) -> u8 { + match self { + DataFormat::Data32Channel32 => 32, + DataFormat::Data24Channel24 => 24, + DataFormat::Data16Channel16 => 16, + DataFormat::Data8Channel8 => 8, + } + } +} + +#[cfg(esp32)] impl DataFormat { /// Returns the number of data bits for the selected data format. pub fn data_bits(&self) -> u8 { @@ -251,6 +315,432 @@ impl DataFormat { } } +/// I2S bit order +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BitOrder { + /// Most Significant Bit (MSB) is transmitted first. + #[default] + MsbFirst, + /// Least Significant Bit (LSB) is transmitted first. + LsbFirst, +} + +/// I2S endianness +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Endianness { + /// Most Significant Byte (MSB) is transmitted first. + #[default] + LittleEndian, + /// Least Significant Byte (LSB) is transmitted first. + BigEndian, +} + +/// I2S word select signal width +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WsWidth { + /// Word select signal will be kept active for half of the frame + #[default] + HalfFrame, + /// Word select signal will be kept active for the length of the first channel (PCM long frame + /// standard) + #[cfg(not(any(esp32, esp32s2)))] + OneChannel, + /// Word select signal will be kept active for a single BCLK cycle (PCM short frame standard) + Bit, + /// Word select signal will be kept active for the specified amount of bits(BCLK cycles) + #[cfg(not(any(esp32, esp32s2)))] + Bits(u16), +} + +/// Represents the polarity of a signal +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Polarity { + /// The signal is high when active + #[default] + ActiveHigh, + /// The signal is low when active + ActiveLow, +} + +/// I2S channels configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Channels { + count: u8, + mask: u16, + fill: Option, +} + +impl Channels { + /// Two channels will use different data + pub const STEREO: Channels = Channels::new_impl(2, 0b11, None); + /// Two channels will use the same data + pub const MONO: Channels = Channels::new_impl(2, 0b01, None); + /// Two channels. Left(first) channel will contain data. Right(second) channel will contain + /// zeros. + pub const LEFT: Channels = Channels::new_impl(2, 0b01, Some(0)); + /// Two channels. Right(second) channel will contain data. Left(first) channel will contain + /// zeros. + pub const RIGHT: Channels = Channels::new_impl(2, 0b10, Some(0)); + + #[procmacros::doc_replace] + /// Creates arbitrary configuration for I2S channels. + /// + /// - `count` the total number of channels. Must be at least 1 and no more than 16. + /// - `mask` determines which channels will be active, with the least significant bit + /// representing first channel. Setting the bit at the nth position means nth channel is + /// active. Inactive channels do not consume or write data in the DMA buffer. + /// - `fill` determines the behavior of inactive channels. `Some(n)` will make all inactive + /// channel send out specified value, truncated to the channel width. `None` will make + /// disabled channels repeat the data from the last active channel. This field is ignored in + /// the receiver unit. + /// + /// ## Example + /// + /// The following example prepares configuration for 6 channels. Only 1st and 4th channels + /// are active. Channels 2-3 will use the same data as the 1st, and channels 5-6 will use the + /// data from the 4th. + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2s::master::Channels; + /// + /// let channels = Channels::new(6, 0b_001_001, None); + /// # {after_snippet} + /// ``` + #[cfg(not(any(esp32, esp32s2)))] + pub const fn new(count: u8, mask: u16, fill: Option) -> Self { + Self::new_impl(count, mask, fill) + } + + const fn new_impl(count: u8, mut mask: u16, fill: Option) -> Self { + mask &= (1 << count) - 1; + + Self { count, mask, fill } + } + + #[cfg(not(any(esp32, esp32s2)))] + fn active_count(&self) -> u8 { + self.mask.count_ones() as u8 + } +} + +/// I2S peripheral configuration. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// Receiver unit config + rx_config: UnitConfig, + + /// Transmitter unit config + tx_config: UnitConfig, + + /// The target sample rate + #[cfg(any(esp32, esp32s2))] + sample_rate: Rate, + + /// Format of the data + #[cfg(any(esp32, esp32s2))] + data_format: DataFormat, +} + +impl Config { + /// TDM Philips standard configuration with two 16-bit active channels + pub fn new_tdm_philips() -> Self { + Self { + rx_config: UnitConfig::new_tdm_philips(), + tx_config: UnitConfig::new_tdm_philips(), + ..Default::default() + } + } + + /// TDM MSB standard configuration with two 16-bit active channels + pub fn new_tdm_msb() -> Self { + Self { + rx_config: UnitConfig::new_tdm_msb(), + tx_config: UnitConfig::new_tdm_msb(), + ..Default::default() + } + } + + /// TDM PCM short frame standard configuration with two 16-bit active channels + pub fn new_tdm_pcm_short() -> Self { + Self { + rx_config: UnitConfig::new_tdm_pcm_short(), + tx_config: UnitConfig::new_tdm_pcm_short(), + ..Default::default() + } + } + + /// TDM PCM long frame standard configuration with two 16-bit active channels + #[cfg(not(any(esp32, esp32s2)))] + pub fn new_tdm_pcm_long() -> Self { + Self { + rx_config: UnitConfig::new_tdm_pcm_long(), + tx_config: UnitConfig::new_tdm_pcm_long(), + ..Default::default() + } + } + + /// Assign the given value to the `sample_rate` field in both units. + #[must_use] + #[cfg(not(any(esp32, esp32s2)))] + pub fn with_sample_rate(mut self, sample_rate: Rate) -> Self { + self.rx_config.sample_rate = sample_rate; + self.tx_config.sample_rate = sample_rate; + self + } + + /// Assign the given value to the `channels` field in both units. + #[must_use] + pub fn with_channels(mut self, channels: Channels) -> Self { + self.rx_config.channels = channels; + self.tx_config.channels = channels; + self + } + + /// Assign the given value to the `data_format` field in both units. + #[must_use] + #[cfg(not(any(esp32, esp32s2)))] + pub fn with_data_format(mut self, data_format: DataFormat) -> Self { + self.rx_config.data_format = data_format; + self.tx_config.data_format = data_format; + self + } + + /// Assign the given value to the `ws_width` field in both units. + #[must_use] + pub fn with_ws_width(mut self, ws_width: WsWidth) -> Self { + self.rx_config.ws_width = ws_width; + self.tx_config.ws_width = ws_width; + self + } + + /// Assign the given value to the `ws_polarity` field in both units. + #[must_use] + pub fn with_ws_polarity(mut self, ws_polarity: Polarity) -> Self { + self.rx_config.ws_polarity = ws_polarity; + self.tx_config.ws_polarity = ws_polarity; + self + } + + /// Assign the given value to the `msb_shift` field in both units. + #[must_use] + pub fn with_msb_shift(mut self, msb_shift: bool) -> Self { + self.rx_config.msb_shift = msb_shift; + self.tx_config.msb_shift = msb_shift; + self + } + + /// Assign the given value to the `endianness` field in both units. + #[cfg(not(esp32))] + #[must_use] + pub fn with_endianness(mut self, endianness: Endianness) -> Self { + self.rx_config.endianness = endianness; + self.tx_config.endianness = endianness; + self + } + + /// Assign the given value to the `bit_order` field in both units. + #[cfg(not(any(esp32, esp32s2)))] + #[must_use] + pub fn with_bit_order(mut self, bit_order: BitOrder) -> Self { + self.rx_config.bit_order = bit_order; + self.tx_config.bit_order = bit_order; + self + } + + #[cfg(any(esp32, esp32s2))] + fn calculate_clock(&self) -> I2sClockDividers { + I2sClockDividers::new(self.sample_rate, 2, self.data_format.data_bits()) + } + + fn validate(&self) -> Result<(), ConfigError> { + self.rx_config.validate()?; + self.tx_config.validate()?; + Ok(()) + } +} + +#[allow(clippy::derivable_impls)] +impl Default for Config { + fn default() -> Self { + Self { + rx_config: UnitConfig::new_tdm_philips(), + tx_config: UnitConfig::new_tdm_philips(), + #[cfg(any(esp32, esp32s2))] + sample_rate: Rate::from_hz(44100), + #[cfg(any(esp32, esp32s2))] + data_format: DataFormat::Data16Channel16, + } + } +} + +/// I2S receiver/transmitter unit configuration +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct UnitConfig { + /// The target sample rate + #[cfg(not(any(esp32, esp32s2)))] + sample_rate: Rate, + + /// I2S channels configuration + channels: Channels, + + /// Format of the data + #[cfg(not(any(esp32, esp32s2)))] + data_format: DataFormat, + + /// Duration for which WS signal is kept active + ws_width: WsWidth, + + /// Polarity of WS signal + ws_polarity: Polarity, + + /// Data signal will lag by one bit relative to the WS signal + msb_shift: bool, + + /// Byte order of the data + #[cfg(not(esp32))] + endianness: Endianness, + + /// Bit order of the data + #[cfg(not(any(esp32, esp32s2)))] + bit_order: BitOrder, +} + +impl UnitConfig { + /// TDM Philips standard configuration with two 16-bit active channels + pub fn new_tdm_philips() -> Self { + Self { + #[cfg(not(any(esp32, esp32s2)))] + sample_rate: Rate::from_hz(44100), + channels: Channels::STEREO, + #[cfg(not(any(esp32, esp32s2)))] + data_format: DataFormat::Data16Channel16, + ws_width: WsWidth::HalfFrame, + ws_polarity: Polarity::ActiveLow, + msb_shift: true, + #[cfg(not(esp32))] + endianness: Endianness::LittleEndian, + #[cfg(not(any(esp32, esp32s2)))] + bit_order: BitOrder::MsbFirst, + } + } + + /// TDM MSB standard configuration with two 16-bit active channels + pub fn new_tdm_msb() -> Self { + Self::new_tdm_philips().with_msb_shift(false) + } + + /// TDM PCM short frame standard configuration with two 16-bit active channels + pub fn new_tdm_pcm_short() -> Self { + Self::new_tdm_philips() + .with_ws_width(WsWidth::Bit) + .with_ws_polarity(Polarity::ActiveHigh) + } + + /// TDM PCM long frame standard configuration with two 16-bit active channels + #[cfg(not(any(esp32, esp32s2)))] + pub fn new_tdm_pcm_long() -> Self { + Self::new_tdm_philips() + .with_ws_width(WsWidth::OneChannel) + .with_ws_polarity(Polarity::ActiveHigh) + } + + fn validate(&self) -> Result<(), ConfigError> { + #[cfg(not(any(esp32, esp32s2)))] + if self.channels.active_count() == 0 || self.channels.count > 16 { + return Err(ConfigError::ChannelsOutOfRange); + } + + Ok(()) + } + + #[cfg(not(any(esp32, esp32s2)))] + fn calculate_ws_width(&self) -> Result { + let ws_width = match self.ws_width { + WsWidth::HalfFrame => { + self.data_format.data_bits() as u16 * self.channels.count as u16 / 2 + } + WsWidth::Bit => 1, + WsWidth::OneChannel => self.data_format.data_bits() as u16, + WsWidth::Bits(bits) => bits, + }; + + #[cfg(not(esp32h2))] + const MAX_WS_WIDTH: u16 = 128; + #[cfg(esp32h2)] + const MAX_WS_WIDTH: u16 = 512; + + if !(1..=MAX_WS_WIDTH).contains(&ws_width) + || ws_width > self.data_format.data_bits() as u16 * self.channels.count as u16 + { + return Err(ConfigError::WsWidthOutOfRange); + } + + Ok(ws_width) + } + + #[cfg(not(any(esp32, esp32s2)))] + fn calculate_clock(&self) -> I2sClockDividers { + I2sClockDividers::new( + self.sample_rate, + self.channels.count, + self.data_format.data_bits(), + ) + } +} + +impl Default for UnitConfig { + fn default() -> Self { + Self::new_tdm_philips() + } +} + +/// Configuration errors. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// Provided [Channels] configuration has no active channels or has over 16 total channels + #[cfg(not(any(esp32, esp32s2)))] + ChannelsOutOfRange, + /// Requested WS signal width is out of range + #[cfg(not(any(esp32, esp32s2)))] + WsWidthOutOfRange, +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + #[allow(unused_variables)] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match *self { + #[cfg(not(any(esp32, esp32s2)))] + ConfigError::ChannelsOutOfRange => { + write!( + f, + "Provided channels configuration has no active channels or has over 16 total channels" + ) + } + #[cfg(not(any(esp32, esp32s2)))] + ConfigError::WsWidthOutOfRange => { + write!( + f, + "The requested WS signal width is out of supported range (1..=128)" + ) + } + } + } +} + /// Instance of the I2S peripheral driver #[non_exhaustive] pub struct I2s<'d, Dm> @@ -328,15 +818,12 @@ where } impl<'d> I2s<'d, Blocking> { - /// Construct a new I2S peripheral driver instance for the first I2S - /// peripheral + /// Construct a new I2s instance. pub fn new( i2s: impl Instance + 'd, - standard: Standard, - data_format: DataFormat, - sample_rate: Rate, channel: impl DmaChannelFor>, - ) -> Self { + config: Config, + ) -> Result { let channel = Channel::new(channel.degrade()); channel.runtime_ensure_compatible(&i2s); @@ -351,23 +838,26 @@ impl<'d> I2s<'d, Blocking> { let rx_guard = PeripheralGuard::new(peripheral); let tx_guard = PeripheralGuard::new(peripheral); - i2s.set_clock(calculate_clock(sample_rate, 2, data_format.channel_bits())); - i2s.configure(&standard, &data_format); i2s.set_master(); + i2s.configure(&config)?; i2s.update(); - Self { + Ok(Self { i2s_rx: RxCreator { i2s: unsafe { i2s.clone_unchecked() }, rx_channel: channel.rx, guard: rx_guard, + #[cfg(any(esp32, esp32s2))] + data_format: config.data_format, }, i2s_tx: TxCreator { i2s, tx_channel: channel.tx, guard: tx_guard, + #[cfg(any(esp32, esp32s2))] + data_format: config.data_format, }, - } + }) } /// Converts the I2S instance into async mode. @@ -377,11 +867,15 @@ impl<'d> I2s<'d, Blocking> { i2s: self.i2s_rx.i2s, rx_channel: self.i2s_rx.rx_channel.into_async(), guard: self.i2s_rx.guard, + #[cfg(any(esp32, esp32s2))] + data_format: self.i2s_rx.data_format, }, i2s_tx: TxCreator { i2s: self.i2s_tx.i2s, tx_channel: self.i2s_tx.tx_channel.into_async(), guard: self.i2s_tx.guard, + #[cfg(any(esp32, esp32s2))] + data_format: self.i2s_tx.data_format, }, } } @@ -413,6 +907,8 @@ where tx_channel: ChannelTx>>, tx_chain: DescriptorChain, _guard: PeripheralGuard, + #[cfg(any(esp32, esp32s2))] + data_format: DataFormat, } impl core::fmt::Debug for I2sTx<'_, Dm> @@ -499,6 +995,17 @@ where Ok(()) } + /// Change the I2S Tx unit configuration. + pub fn apply_config(&mut self, tx_config: &UnitConfig) -> Result<(), ConfigError> { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + self.i2s.configure_tx(tx_config, self.data_format) + } else { + self.i2s.configure_tx(tx_config) + } + } + } + /// Writes a slice of data to the I2S peripheral. pub fn write_words(&mut self, words: &[impl AcceptedWord]) -> Result<(), Error> { self.write(unsafe { @@ -543,6 +1050,8 @@ where rx_channel: ChannelRx>>, rx_chain: DescriptorChain, _guard: PeripheralGuard, + #[cfg(any(esp32, esp32s2))] + data_format: DataFormat, } impl core::fmt::Debug for I2sRx<'_, Dm> @@ -630,6 +1139,17 @@ where Ok(()) } + /// Change the I2S Rx unit configuration. + pub fn apply_config(&mut self, rx_config: &UnitConfig) -> Result<(), ConfigError> { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + self.i2s.configure_rx(rx_config, self.data_format) + } else { + self.i2s.configure_rx(rx_config) + } + } + } + /// Reads a slice of data from the I2S peripheral and stores it in the /// provided buffer. pub fn read_words(&mut self, words: &mut [impl AcceptedWord]) -> Result<(), Error> { @@ -714,6 +1234,8 @@ mod private { pub i2s: AnyI2s<'d>, pub tx_channel: ChannelTx>>, pub(crate) guard: PeripheralGuard, + #[cfg(any(esp32, esp32s2))] + pub(crate) data_format: DataFormat, } impl<'d, Dm> TxCreator<'d, Dm> @@ -727,6 +1249,8 @@ mod private { tx_channel: self.tx_channel, tx_chain: DescriptorChain::new(descriptors), _guard: PeripheralGuard::new(peripheral), + #[cfg(any(esp32, esp32s2))] + data_format: self.data_format, } } @@ -771,6 +1295,8 @@ mod private { pub i2s: AnyI2s<'d>, pub rx_channel: ChannelRx>>, pub(crate) guard: PeripheralGuard, + #[cfg(any(esp32, esp32s2))] + pub(crate) data_format: DataFormat, } impl<'d, Dm> RxCreator<'d, Dm> @@ -784,6 +1310,8 @@ mod private { rx_channel: self.rx_channel, rx_chain: DescriptorChain::new(descriptors), _guard: PeripheralGuard::new(peripheral), + #[cfg(any(esp32, esp32s2))] + data_format: self.data_format, } } @@ -901,28 +1429,24 @@ mod private { }); } - fn configure(&self, _standard: &Standard, data_format: &DataFormat) { - let fifo_mod = match data_format { - DataFormat::Data32Channel32 => 2, - DataFormat::Data16Channel16 => 0, - }; + fn configure(&self, config: &Config) -> Result<(), ConfigError> { + config.validate()?; + + self.configure_tx(&config.tx_config, config.data_format)?; + self.configure_rx(&config.rx_config, config.data_format)?; + + self.set_clock(config.calculate_clock()); self.regs().sample_rate_conf().modify(|_, w| unsafe { - w.tx_bits_mod().bits(data_format.channel_bits()); - w.rx_bits_mod().bits(data_format.channel_bits()) + // Having different data formats for each direction would make clock calculations + // more tricky + w.tx_bits_mod().bits(config.data_format.data_bits()); + w.rx_bits_mod().bits(config.data_format.data_bits()) }); self.regs().conf().modify(|_, w| { w.tx_slave_mod().clear_bit(); w.rx_slave_mod().clear_bit(); - // If the I2S_RX_MSB_SHIFT bit and the I2S_TX_MSB_SHIFT bit of register - // I2S_CONF_REG are set to 1, respectively, the I2S module will use the Philips - // standard when receiving and transmitting data. - w.tx_msb_shift().set_bit(); - w.rx_msb_shift().set_bit(); - // Short frame synchronization - w.tx_short_sync().bit(false); - w.rx_short_sync().bit(false); // Send MSB to the right channel to be consistent with ESP32-S3 et al. w.tx_msb_right().set_bit(); w.rx_msb_right().set_bit(); @@ -937,19 +1461,7 @@ mod private { w.sig_loopback().clear_bit() }); - self.regs().fifo_conf().modify(|_, w| unsafe { - w.tx_fifo_mod().bits(fifo_mod); - w.tx_fifo_mod_force_en().set_bit(); - w.dscr_en().set_bit(); - w.rx_fifo_mod().bits(fifo_mod); - w.rx_fifo_mod_force_en().set_bit() - }); - - self.regs().conf_chan().modify(|_, w| unsafe { - // for now only stereo - w.tx_chan_mod().bits(0); - w.rx_chan_mod().bits(0) - }); + self.regs().fifo_conf().modify(|_, w| w.dscr_en().set_bit()); self.regs().conf1().modify(|_, w| { w.tx_pcm_bypass().set_bit(); @@ -965,6 +1477,112 @@ mod private { w.camera_en().clear_bit(); w.lcd_en().clear_bit() }); + + Ok(()) + } + + fn configure_tx( + &self, + config: &UnitConfig, + data_format: DataFormat, + ) -> Result<(), ConfigError> { + config.validate()?; + + let chan_mod = match config.channels { + Channels::STEREO | Channels::MONO => 0, + Channels::LEFT => 3, + Channels::RIGHT => 4, + _ => unreachable!(), + }; + + let fifo_mod = match (data_format.data_bits(), config.channels == Channels::STEREO) { + (8 | 16, true) => 0, + (8 | 16, false) => 1, + (24 | 32, true) => 2, + (24 | 32, false) => 3, + _ => unreachable!(), + }; + + self.regs().conf().modify(|_, w| { + w.tx_msb_shift().bit(config.msb_shift); + // Short frame synchronization + w.tx_short_sync().bit(config.ws_width == WsWidth::Bit) + }); + + #[cfg(not(esp32))] + self.regs().conf().modify(|_, w| { + // Channel configurations other than Stereo should use same data from DMA + // for both channels + w.tx_dma_equal().bit(config.channels != Channels::STEREO); + // Byte endianness + w.tx_big_endian() + .bit(config.endianness == Endianness::BigEndian) + }); + + self.regs().fifo_conf().modify(|_, w| unsafe { + w.tx_fifo_mod().bits(fifo_mod); + w.tx_fifo_mod_force_en().set_bit() + }); + + self.regs() + .conf_sigle_data() + .modify(|_, w| unsafe { w.sigle_data().bits(config.channels.fill.unwrap_or(0)) }); + + self.regs() + .conf_chan() + .modify(|_, w| unsafe { w.tx_chan_mod().bits(chan_mod) }); + + Ok(()) + } + + fn configure_rx( + &self, + config: &UnitConfig, + data_format: DataFormat, + ) -> Result<(), ConfigError> { + config.validate()?; + + let chan_mod = match config.channels { + Channels::STEREO => 0, + Channels::LEFT | Channels::MONO => 1, + Channels::RIGHT => 2, + _ => unreachable!(), + }; + + let fifo_mod = match (data_format.data_bits(), config.channels == Channels::STEREO) { + (8 | 16, true) => 0, + (8 | 16, false) => 1, + (24 | 32, true) => 2, + (24 | 32, false) => 3, + _ => unreachable!(), + }; + + self.regs().conf().modify(|_, w| { + w.rx_msb_shift().bit(config.msb_shift); + // Short frame synchronization + w.rx_short_sync().bit(config.ws_width == WsWidth::Bit) + }); + + #[cfg(not(esp32))] + self.regs().conf().modify(|_, w| { + // Channel configurations other than Stereo should use same data from DMA + // for both channels + w.rx_dma_equal().bit(config.channels != Channels::STEREO); + // Byte endianness + w.rx_big_endian() + .bit(config.endianness == Endianness::BigEndian) + }); + + self.regs().fifo_conf().modify(|_, w| unsafe { + w.rx_fifo_mod().bits(fifo_mod); + w.rx_fifo_mod_force_en().set_bit() + }); + + self.regs() + .conf_chan() + .modify(|_, w| unsafe { w.rx_chan_mod().bits(chan_mod) }); + + Ok(()) } fn set_master(&self) { @@ -1068,7 +1686,7 @@ mod private { } } - #[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))] + #[cfg(any(esp32c3, esp32s3, esp32c6, esp32h2))] pub trait RegisterAccessPrivate: Signals + RegBlock { fn enable_listen(&self, interrupts: EnumSet, enable: bool) { self.regs().int_ena().modify(|_, w| { @@ -1127,59 +1745,22 @@ mod private { } #[cfg(any(esp32c3, esp32s3))] - fn set_clock(&self, clock_settings: I2sClockDividers) { - let clkm_div_x: u32; - let clkm_div_y: u32; - let clkm_div_z: u32; - let clkm_div_yn1: u32; - - if clock_settings.denominator == 0 || clock_settings.numerator == 0 { - clkm_div_x = 0; - clkm_div_y = 0; - clkm_div_z = 0; - clkm_div_yn1 = 1; - } else if clock_settings.numerator > clock_settings.denominator / 2 { - clkm_div_x = clock_settings - .denominator - .overflowing_div( - clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0, - ) - .0 - .overflowing_sub(1) - .0; - clkm_div_y = clock_settings.denominator - % (clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0); - clkm_div_z = clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0; - clkm_div_yn1 = 1; - } else { - clkm_div_x = clock_settings.denominator / clock_settings.numerator - 1; - clkm_div_y = clock_settings.denominator % clock_settings.numerator; - clkm_div_z = clock_settings.numerator; - clkm_div_yn1 = 0; - } + fn set_tx_clock(&self, clock_settings: I2sClockDividers) { + let clkm_div = clock_settings.mclk_dividers(); self.regs().tx_clkm_div_conf().modify(|_, w| unsafe { - w.tx_clkm_div_x().bits(clkm_div_x as u16); - w.tx_clkm_div_y().bits(clkm_div_y as u16); - w.tx_clkm_div_yn1().bit(clkm_div_yn1 != 0); - w.tx_clkm_div_z().bits(clkm_div_z as u16) + w.tx_clkm_div_x().bits(clkm_div.x as u16); + w.tx_clkm_div_y().bits(clkm_div.y as u16); + w.tx_clkm_div_yn1().bit(clkm_div.yn1); + w.tx_clkm_div_z().bits(clkm_div.z as u16) }); self.regs().tx_clkm_conf().modify(|_, w| unsafe { w.clk_en().set_bit(); w.tx_clk_active().set_bit(); + // for now fixed at 160MHz w.tx_clk_sel() - .bits(crate::soc::constants::I2S_DEFAULT_CLK_SRC) // for now fixed at 160MHz - ; + .bits(crate::soc::constants::I2S_DEFAULT_CLK_SRC); w.tx_clkm_div_num().bits(clock_settings.mclk_divider as u8) }); @@ -1187,12 +1768,17 @@ mod private { w.tx_bck_div_num() .bits((clock_settings.bclk_divider - 1) as u8) }); + } + + #[cfg(any(esp32c3, esp32s3))] + fn set_rx_clock(&self, clock_settings: I2sClockDividers) { + let clkm_div = clock_settings.mclk_dividers(); self.regs().rx_clkm_div_conf().modify(|_, w| unsafe { - w.rx_clkm_div_x().bits(clkm_div_x as u16); - w.rx_clkm_div_y().bits(clkm_div_y as u16); - w.rx_clkm_div_yn1().bit(clkm_div_yn1 != 0); - w.rx_clkm_div_z().bits(clkm_div_z as u16) + w.rx_clkm_div_x().bits(clkm_div.x as u16); + w.rx_clkm_div_y().bits(clkm_div.y as u16); + w.rx_clkm_div_yn1().bit(clkm_div.yn1); + w.rx_clkm_div_z().bits(clkm_div.z as u16) }); self.regs().rx_clkm_conf().modify(|_, w| unsafe { @@ -1211,54 +1797,17 @@ mod private { } #[cfg(any(esp32c6, esp32h2))] - fn set_clock(&self, clock_settings: I2sClockDividers) { + fn set_tx_clock(&self, clock_settings: I2sClockDividers) { // I2S clocks are configured via PCR use crate::peripherals::PCR; - let clkm_div_x: u32; - let clkm_div_y: u32; - let clkm_div_z: u32; - let clkm_div_yn1: u32; - - if clock_settings.denominator == 0 || clock_settings.numerator == 0 { - clkm_div_x = 0; - clkm_div_y = 0; - clkm_div_z = 0; - clkm_div_yn1 = 1; - } else if clock_settings.numerator > clock_settings.denominator / 2 { - clkm_div_x = clock_settings - .denominator - .overflowing_div( - clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0, - ) - .0 - .overflowing_sub(1) - .0; - clkm_div_y = clock_settings.denominator - % (clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0); - clkm_div_z = clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0; - clkm_div_yn1 = 1; - } else { - clkm_div_x = clock_settings.denominator / clock_settings.numerator - 1; - clkm_div_y = clock_settings.denominator % clock_settings.numerator; - clkm_div_z = clock_settings.numerator; - clkm_div_yn1 = 0; - } + let clkm_div = clock_settings.mclk_dividers(); PCR::regs().i2s_tx_clkm_div_conf().modify(|_, w| unsafe { - w.i2s_tx_clkm_div_x().bits(clkm_div_x as u16); - w.i2s_tx_clkm_div_y().bits(clkm_div_y as u16); - w.i2s_tx_clkm_div_yn1().bit(clkm_div_yn1 != 0); - w.i2s_tx_clkm_div_z().bits(clkm_div_z as u16) + w.i2s_tx_clkm_div_x().bits(clkm_div.x as u16); + w.i2s_tx_clkm_div_y().bits(clkm_div.y as u16); + w.i2s_tx_clkm_div_yn1().bit(clkm_div.yn1); + w.i2s_tx_clkm_div_z().bits(clkm_div.z as u16) }); PCR::regs().i2s_tx_clkm_conf().modify(|_, w| unsafe { @@ -1280,12 +1829,20 @@ mod private { w.tx_bck_div_num() .bits((clock_settings.bclk_divider - 1) as u8) }); + } + + #[cfg(any(esp32c6, esp32h2))] + fn set_rx_clock(&self, clock_settings: I2sClockDividers) { + // I2S clocks are configured via PCR + use crate::peripherals::PCR; + + let clkm_div = clock_settings.mclk_dividers(); PCR::regs().i2s_rx_clkm_div_conf().modify(|_, w| unsafe { - w.i2s_rx_clkm_div_x().bits(clkm_div_x as u16); - w.i2s_rx_clkm_div_y().bits(clkm_div_y as u16); - w.i2s_rx_clkm_div_yn1().bit(clkm_div_yn1 != 0); - w.i2s_rx_clkm_div_z().bits(clkm_div_z as u16) + w.i2s_rx_clkm_div_x().bits(clkm_div.x as u16); + w.i2s_rx_clkm_div_y().bits(clkm_div.y as u16); + w.i2s_rx_clkm_div_yn1().bit(clkm_div.yn1); + w.i2s_rx_clkm_div_z().bits(clkm_div.z as u16) }); PCR::regs().i2s_rx_clkm_conf().modify(|_, w| unsafe { @@ -1309,72 +1866,100 @@ mod private { }); } - fn configure(&self, _standard: &Standard, data_format: &DataFormat) { - #[allow(clippy::useless_conversion)] + fn configure(&self, config: &Config) -> Result<(), ConfigError> { + config.validate()?; + + self.configure_tx(&config.tx_config)?; + self.configure_rx(&config.rx_config)?; + + Ok(()) + } + + fn configure_tx(&self, config: &UnitConfig) -> Result<(), ConfigError> { + use bitfield::Bit; + + config.validate()?; + + let ws_width = config.calculate_ws_width()?; + self.set_tx_clock(config.calculate_clock()); + self.regs().tx_conf1().modify(|_, w| unsafe { - w.tx_tdm_ws_width() - .bits((data_format.channel_bits() - 1).into()); - w.tx_bits_mod().bits(data_format.data_bits() - 1); - w.tx_tdm_chan_bits().bits(data_format.channel_bits() - 1); - w.tx_half_sample_bits().bits(data_format.channel_bits() - 1) + #[cfg(not(esp32h2))] + w.tx_msb_shift().bit(config.msb_shift); + #[allow(clippy::useless_conversion)] + w.tx_tdm_ws_width().bits((ws_width - 1).try_into().unwrap()); + w.tx_bits_mod().bits(config.data_format.data_bits() - 1); + w.tx_tdm_chan_bits() + .bits(config.data_format.channel_bits() - 1); + w.tx_half_sample_bits() + .bits((config.data_format.data_bits() * config.channels.count) / 2 - 1) }); - #[cfg(not(esp32h2))] - self.regs() - .tx_conf1() - .modify(|_, w| w.tx_msb_shift().set_bit()); - #[cfg(esp32h2)] - self.regs() - .tx_conf() - .modify(|_, w| w.tx_msb_shift().set_bit()); + self.regs().tx_conf().modify(|_, w| unsafe { w.tx_mono().clear_bit(); w.tx_mono_fst_vld().set_bit(); w.tx_stop_en().set_bit(); - w.tx_chan_equal().clear_bit(); + w.tx_chan_equal().bit(config.channels.fill.is_none()); w.tx_tdm_en().set_bit(); w.tx_pdm_en().clear_bit(); w.tx_pcm_bypass().set_bit(); - w.tx_big_endian().clear_bit(); - w.tx_bit_order().clear_bit(); + #[cfg(esp32h2)] + w.tx_msb_shift().bit(config.msb_shift); + w.tx_big_endian() + .bit(config.endianness == Endianness::BigEndian); + w.tx_bit_order().bit(config.bit_order == BitOrder::LsbFirst); + w.tx_ws_idle_pol() + .bit(config.ws_polarity == Polarity::ActiveHigh); w.tx_chan_mod().bits(0) }); self.regs().tx_tdm_ctrl().modify(|_, w| unsafe { - w.tx_tdm_tot_chan_num().bits(1); - w.tx_tdm_chan0_en().set_bit(); - w.tx_tdm_chan1_en().set_bit(); - w.tx_tdm_chan2_en().clear_bit(); - w.tx_tdm_chan3_en().clear_bit(); - w.tx_tdm_chan4_en().clear_bit(); - w.tx_tdm_chan5_en().clear_bit(); - w.tx_tdm_chan6_en().clear_bit(); - w.tx_tdm_chan7_en().clear_bit(); - w.tx_tdm_chan8_en().clear_bit(); - w.tx_tdm_chan9_en().clear_bit(); - w.tx_tdm_chan10_en().clear_bit(); - w.tx_tdm_chan11_en().clear_bit(); - w.tx_tdm_chan12_en().clear_bit(); - w.tx_tdm_chan13_en().clear_bit(); - w.tx_tdm_chan14_en().clear_bit(); - w.tx_tdm_chan15_en().clear_bit() + w.tx_tdm_tot_chan_num().bits(config.channels.count - 1); + w.tx_tdm_chan0_en().bit(config.channels.mask.bit(0)); + w.tx_tdm_chan1_en().bit(config.channels.mask.bit(1)); + w.tx_tdm_chan2_en().bit(config.channels.mask.bit(2)); + w.tx_tdm_chan3_en().bit(config.channels.mask.bit(3)); + w.tx_tdm_chan4_en().bit(config.channels.mask.bit(4)); + w.tx_tdm_chan5_en().bit(config.channels.mask.bit(5)); + w.tx_tdm_chan6_en().bit(config.channels.mask.bit(6)); + w.tx_tdm_chan7_en().bit(config.channels.mask.bit(7)); + w.tx_tdm_chan8_en().bit(config.channels.mask.bit(8)); + w.tx_tdm_chan9_en().bit(config.channels.mask.bit(9)); + w.tx_tdm_chan10_en().bit(config.channels.mask.bit(10)); + w.tx_tdm_chan11_en().bit(config.channels.mask.bit(11)); + w.tx_tdm_chan12_en().bit(config.channels.mask.bit(12)); + w.tx_tdm_chan13_en().bit(config.channels.mask.bit(13)); + w.tx_tdm_chan14_en().bit(config.channels.mask.bit(14)); + w.tx_tdm_chan15_en().bit(config.channels.mask.bit(15)); + w.tx_tdm_skip_msk_en().clear_bit() }); - #[allow(clippy::useless_conversion)] + self.regs() + .conf_sigle_data() + .modify(|_, w| unsafe { w.single_data().bits(config.channels.fill.unwrap_or(0)) }); + + Ok(()) + } + + fn configure_rx(&self, config: &UnitConfig) -> Result<(), ConfigError> { + use bitfield::Bit; + + config.validate()?; + + let ws_width = config.calculate_ws_width()?; + self.set_rx_clock(config.calculate_clock()); + self.regs().rx_conf1().modify(|_, w| unsafe { - w.rx_tdm_ws_width() - .bits((data_format.channel_bits() - 1).into()); - w.rx_bits_mod().bits(data_format.data_bits() - 1); - w.rx_tdm_chan_bits().bits(data_format.channel_bits() - 1); - w.rx_half_sample_bits().bits(data_format.channel_bits() - 1) + #[cfg(not(esp32h2))] + w.rx_msb_shift().bit(config.msb_shift); + #[allow(clippy::useless_conversion)] + w.rx_tdm_ws_width().bits((ws_width - 1).try_into().unwrap()); + w.rx_bits_mod().bits(config.data_format.data_bits() - 1); + w.rx_tdm_chan_bits() + .bits(config.data_format.channel_bits() - 1); + w.rx_half_sample_bits() + .bits((config.data_format.data_bits() * config.channels.count) / 2) }); - #[cfg(not(esp32h2))] - self.regs() - .rx_conf1() - .modify(|_, w| w.rx_msb_shift().set_bit()); - #[cfg(esp32h2)] - self.regs() - .rx_conf() - .modify(|_, w| w.rx_msb_shift().set_bit()); self.regs().rx_conf().modify(|_, w| unsafe { w.rx_mono().clear_bit(); @@ -1383,29 +1968,36 @@ mod private { w.rx_tdm_en().set_bit(); w.rx_pdm_en().clear_bit(); w.rx_pcm_bypass().set_bit(); - w.rx_big_endian().clear_bit(); - w.rx_bit_order().clear_bit() + #[cfg(esp32h2)] + w.rx_msb_shift().bit(config.msb_shift); + w.rx_big_endian() + .bit(config.endianness == Endianness::BigEndian); + w.rx_bit_order().bit(config.bit_order == BitOrder::LsbFirst); + w.rx_ws_idle_pol() + .bit(config.ws_polarity == Polarity::ActiveHigh) }); self.regs().rx_tdm_ctrl().modify(|_, w| unsafe { - w.rx_tdm_tot_chan_num().bits(1); - w.rx_tdm_pdm_chan0_en().set_bit(); - w.rx_tdm_pdm_chan1_en().set_bit(); - w.rx_tdm_pdm_chan2_en().clear_bit(); - w.rx_tdm_pdm_chan3_en().clear_bit(); - w.rx_tdm_pdm_chan4_en().clear_bit(); - w.rx_tdm_pdm_chan5_en().clear_bit(); - w.rx_tdm_pdm_chan6_en().clear_bit(); - w.rx_tdm_pdm_chan7_en().clear_bit(); - w.rx_tdm_chan8_en().clear_bit(); - w.rx_tdm_chan9_en().clear_bit(); - w.rx_tdm_chan10_en().clear_bit(); - w.rx_tdm_chan11_en().clear_bit(); - w.rx_tdm_chan12_en().clear_bit(); - w.rx_tdm_chan13_en().clear_bit(); - w.rx_tdm_chan14_en().clear_bit(); - w.rx_tdm_chan15_en().clear_bit() + w.rx_tdm_tot_chan_num().bits(config.channels.count - 1); + w.rx_tdm_pdm_chan0_en().bit(config.channels.mask.bit(0)); + w.rx_tdm_pdm_chan1_en().bit(config.channels.mask.bit(1)); + w.rx_tdm_pdm_chan2_en().bit(config.channels.mask.bit(2)); + w.rx_tdm_pdm_chan3_en().bit(config.channels.mask.bit(3)); + w.rx_tdm_pdm_chan4_en().bit(config.channels.mask.bit(4)); + w.rx_tdm_pdm_chan5_en().bit(config.channels.mask.bit(5)); + w.rx_tdm_pdm_chan6_en().bit(config.channels.mask.bit(6)); + w.rx_tdm_pdm_chan7_en().bit(config.channels.mask.bit(7)); + w.rx_tdm_chan8_en().bit(config.channels.mask.bit(8)); + w.rx_tdm_chan9_en().bit(config.channels.mask.bit(9)); + w.rx_tdm_chan10_en().bit(config.channels.mask.bit(10)); + w.rx_tdm_chan11_en().bit(config.channels.mask.bit(11)); + w.rx_tdm_chan12_en().bit(config.channels.mask.bit(12)); + w.rx_tdm_chan13_en().bit(config.channels.mask.bit(13)); + w.rx_tdm_chan14_en().bit(config.channels.mask.bit(14)); + w.rx_tdm_chan15_en().bit(config.channels.mask.bit(15)) }); + + Ok(()) } fn set_master(&self) { @@ -1735,66 +2327,108 @@ mod private { numerator: u32, } - pub fn calculate_clock(sample_rate: Rate, channels: u8, data_bits: u8) -> I2sClockDividers { - // this loosely corresponds to `i2s_std_calculate_clock` and - // `i2s_ll_tx_set_mclk` in esp-idf - // - // main difference is we are using fixed-point arithmetic here + #[cfg(any(esp32c3, esp32s3, esp32c6, esp32h2))] + pub struct I2sMclkDividers { + x: u32, + y: u32, + z: u32, + yn1: bool, + } - // If data_bits is a power of two, use 256 as the mclk_multiple - // If data_bits is 24, use 192 (24 * 8) as the mclk_multiple - let mclk_multiple = if data_bits == 24 { 192 } else { 256 }; - let sclk = crate::soc::constants::I2S_SCLK; // for now it's fixed 160MHz and 96MHz (just H2) + impl I2sClockDividers { + pub fn new(sample_rate: Rate, channels: u8, data_bits: u8) -> I2sClockDividers { + // this loosely corresponds to `i2s_std_calculate_clock` and + // `i2s_ll_tx_set_mclk` in esp-idf + // + // main difference is we are using fixed-point arithmetic here - let rate = sample_rate.as_hz(); + // If data_bits is a power of two, use 256 as the mclk_multiple + // If data_bits is 24, use 192 (24 * 8) as the mclk_multiple + let mclk_multiple = if data_bits == 24 { 192 } else { 256 }; + let sclk = crate::soc::constants::I2S_SCLK; // for now it's fixed 160MHz and 96MHz (just H2) - let bclk = rate * channels as u32 * data_bits as u32; - let mclk = rate * mclk_multiple; - let bclk_divider = mclk / bclk; - let mut mclk_divider = sclk / mclk; + let rate = sample_rate.as_hz(); - let mut ma: u32; - let mut mb: u32; - let mut denominator: u32 = 0; - let mut numerator: u32 = 0; + let bclk = rate * channels as u32 * data_bits as u32; + let mclk = rate * mclk_multiple; + let bclk_divider = mclk / bclk; + let mut mclk_divider = sclk / mclk; - let freq_diff = sclk.abs_diff(mclk * mclk_divider); + let mut ma: u32; + let mut mb: u32; + let mut denominator: u32 = 0; + let mut numerator: u32 = 0; - if freq_diff != 0 { - let decimal = freq_diff as u64 * 10000 / mclk as u64; + let freq_diff = sclk.abs_diff(mclk * mclk_divider); - // Carry bit if the decimal is greater than 1.0 - 1.0 / (63.0 * 2) = 125.0 / - // 126.0 - if decimal > 1250000 / 126 { - mclk_divider += 1; - } else { - let mut min: u32 = !0; + if freq_diff != 0 { + let decimal = freq_diff as u64 * 10000 / mclk as u64; - for a in 2..=I2S_LL_MCLK_DIVIDER_MAX { - let b = (a as u64) * (freq_diff as u64 * 10000u64 / mclk as u64) + 5000; - ma = ((freq_diff as u64 * 10000u64 * a as u64) / 10000) as u32; - mb = (mclk as u64 * (b / 10000)) as u32; + // Carry bit if the decimal is greater than 1.0 - 1.0 / (63.0 * 2) = 125.0 / + // 126.0 + if decimal > 1250000 / 126 { + mclk_divider += 1; + } else { + let mut min: u32 = !0; - if ma == mb { - denominator = a as u32; - numerator = (b / 10000) as u32; - break; - } + for a in 2..=I2S_LL_MCLK_DIVIDER_MAX { + let b = (a as u64) * (freq_diff as u64 * 10000u64 / mclk as u64) + 5000; + ma = ((freq_diff as u64 * 10000u64 * a as u64) / 10000) as u32; + mb = (mclk as u64 * (b / 10000)) as u32; - if mb.abs_diff(ma) < min { - denominator = a as u32; - numerator = b as u32; - min = mb.abs_diff(ma); + if ma == mb { + denominator = a as u32; + numerator = (b / 10000) as u32; + break; + } + + if mb.abs_diff(ma) < min { + denominator = a as u32; + numerator = b as u32; + min = mb.abs_diff(ma); + } } } } + + I2sClockDividers { + mclk_divider, + bclk_divider, + denominator, + numerator, + } } - I2sClockDividers { - mclk_divider, - bclk_divider, - denominator, - numerator, + #[cfg(any(esp32c3, esp32s3, esp32c6, esp32h2))] + fn mclk_dividers(&self) -> I2sMclkDividers { + let x; + let y; + let z; + let yn1; + + if self.denominator == 0 || self.numerator == 0 { + x = 0; + y = 0; + z = 0; + yn1 = true; + } else if self.numerator > self.denominator / 2 { + x = self + .denominator + .overflowing_div(self.denominator.overflowing_sub(self.numerator).0) + .0 + .overflowing_sub(1) + .0; + y = self.denominator % (self.denominator.overflowing_sub(self.numerator).0); + z = self.denominator.overflowing_sub(self.numerator).0; + yn1 = true; + } else { + x = self.denominator / self.numerator - 1; + y = self.denominator % self.numerator; + z = self.numerator; + yn1 = false; + } + + I2sMclkDividers { x, y, z, yn1 } } } } diff --git a/esp-hal/src/i2s/tdm_slot_msb.svg b/esp-hal/src/i2s/tdm_slot_msb.svg new file mode 100644 index 000000000..2bb64577b --- /dev/null +++ b/esp-hal/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/esp-hal/src/i2s/tdm_slot_pcm_long.svg b/esp-hal/src/i2s/tdm_slot_pcm_long.svg new file mode 100644 index 000000000..d00ad67db --- /dev/null +++ b/esp-hal/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/esp-hal/src/i2s/tdm_slot_pcm_short.svg b/esp-hal/src/i2s/tdm_slot_pcm_short.svg new file mode 100644 index 000000000..d10462f20 --- /dev/null +++ b/esp-hal/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/esp-hal/src/i2s/tdm_slot_philips.svg b/esp-hal/src/i2s/tdm_slot_philips.svg new file mode 100644 index 000000000..9eb063321 --- /dev/null +++ b/esp-hal/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/hil-test/tests/embassy_interrupt_spi_dma.rs b/hil-test/tests/embassy_interrupt_spi_dma.rs index b6fa2986d..875eda085 100644 --- a/hil-test/tests/embassy_interrupt_spi_dma.rs +++ b/hil-test/tests/embassy_interrupt_spi_dma.rs @@ -145,11 +145,13 @@ mod test { #[cfg(not(any(esp32, esp32s2, esp32s3)))] let other_peripheral = esp_hal::i2s::master::I2s::new( peripherals.I2S0, - esp_hal::i2s::master::Standard::Philips, - esp_hal::i2s::master::DataFormat::Data8Channel8, - Rate::from_khz(8), dma_channel2, - ); + esp_hal::i2s::master::Config::new_tdm_philips() + .with_sample_rate(Rate::from_khz(8)) + .with_data_format(esp_hal::i2s::master::DataFormat::Data16Channel16) + .with_channels(esp_hal::i2s::master::Channels::STEREO), + ) + .unwrap(); let sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); diff --git a/hil-test/tests/i2s.rs b/hil-test/tests/i2s.rs index 324b25fc3..f2b550b4b 100644 --- a/hil-test/tests/i2s.rs +++ b/hil-test/tests/i2s.rs @@ -15,7 +15,7 @@ use esp_hal::{ delay::Delay, dma_buffers, gpio::{AnyPin, NoPin, Pin}, - i2s::master::{DataFormat, I2s, I2sTx, Standard}, + i2s::master::{Channels, Config, DataFormat, I2s, I2sTx}, peripherals::I2S0, time::Rate, }; @@ -137,11 +137,13 @@ mod tests { let i2s = I2s::new( ctx.i2s, - Standard::Philips, - DataFormat::Data16Channel16, - Rate::from_hz(16000), ctx.dma_channel, + Config::new_tdm_philips() + .with_sample_rate(Rate::from_hz(16000)) + .with_data_format(DataFormat::Data16Channel16) + .with_channels(Channels::STEREO), ) + .unwrap() .into_async(); let (din, dout) = unsafe { ctx.dout.split() }; @@ -188,11 +190,13 @@ mod tests { let i2s = I2s::new( ctx.i2s, - Standard::Philips, - DataFormat::Data16Channel16, - Rate::from_hz(16000), ctx.dma_channel, - ); + Config::new_tdm_philips() + .with_sample_rate(Rate::from_hz(16000)) + .with_data_format(DataFormat::Data16Channel16) + .with_channels(Channels::STEREO), + ) + .unwrap(); let (din, dout) = unsafe { ctx.dout.split() }; @@ -295,11 +299,13 @@ mod tests { let i2s = I2s::new( ctx.i2s, - Standard::Philips, - DataFormat::Data16Channel16, - Rate::from_hz(16000), ctx.dma_channel, - ); + Config::new_tdm_philips() + .with_sample_rate(Rate::from_hz(16000)) + .with_data_format(DataFormat::Data16Channel16) + .with_channels(Channels::STEREO), + ) + .unwrap(); let mut i2s_tx = i2s .i2s_tx @@ -323,11 +329,13 @@ mod tests { let i2s = I2s::new( ctx.i2s, - Standard::Philips, - DataFormat::Data16Channel16, - Rate::from_hz(16000), ctx.dma_channel, - ); + Config::new_tdm_philips() + .with_sample_rate(Rate::from_hz(16000)) + .with_data_format(DataFormat::Data16Channel16) + .with_channels(Channels::STEREO), + ) + .unwrap(); let mut i2s_rx = i2s .i2s_rx diff --git a/qa-test/src/bin/embassy_i2s_read.rs b/qa-test/src/bin/embassy_i2s_read.rs index c70db44d2..37f7e48a1 100644 --- a/qa-test/src/bin/embassy_i2s_read.rs +++ b/qa-test/src/bin/embassy_i2s_read.rs @@ -20,7 +20,7 @@ use embassy_executor::Spawner; use esp_backtrace as _; use esp_hal::{ dma_buffers, - i2s::master::{DataFormat, I2s, Standard}, + i2s::master::{Channels, Config, DataFormat, I2s}, time::Rate, timer::timg::TimerGroup, }; @@ -48,11 +48,13 @@ async fn main(_spawner: Spawner) { let i2s = I2s::new( peripherals.I2S0, - Standard::Philips, - DataFormat::Data16Channel16, - Rate::from_hz(44100), dma_channel, + Config::new_tdm_philips() + .with_sample_rate(Rate::from_hz(44100)) + .with_data_format(DataFormat::Data16Channel16) + .with_channels(Channels::STEREO), ) + .unwrap() .into_async(); #[cfg(not(feature = "esp32"))] diff --git a/qa-test/src/bin/embassy_i2s_sound.rs b/qa-test/src/bin/embassy_i2s_sound.rs index 872aadff8..dfaaba28d 100644 --- a/qa-test/src/bin/embassy_i2s_sound.rs +++ b/qa-test/src/bin/embassy_i2s_sound.rs @@ -34,7 +34,7 @@ use embassy_executor::Spawner; use esp_backtrace as _; use esp_hal::{ dma_buffers, - i2s::master::{DataFormat, I2s, Standard}, + i2s::master::{Channels, Config, DataFormat, I2s}, time::Rate, timer::timg::TimerGroup, }; @@ -70,11 +70,13 @@ async fn main(_spawner: Spawner) { let i2s = I2s::new( peripherals.I2S0, - Standard::Philips, - DataFormat::Data16Channel16, - Rate::from_hz(44100), dma_channel, + Config::new_tdm_philips() + .with_sample_rate(Rate::from_hz(44100)) + .with_data_format(DataFormat::Data16Channel16) + .with_channels(Channels::STEREO), ) + .unwrap() .into_async(); let i2s_tx = i2s