#![cfg_attr(docsrs, procmacros::doc_replace(
"dma_channel" => {
cfg(i2s_dma_engine = "I2S_DMA") => "DMA_I2S0",
_ => "DMA_CH0",
},
"mclk" => {
cfg(not(esp32)) => "let i2s = i2s.with_mclk(peripherals.GPIO0);",
_ => ""
},
"tdm_slot_philips" => include_str!("../tdm_slot_philips.svg"),
"tdm_slot_msb" => include_str!("../tdm_slot_msb.svg"),
"tdm_slot_pcm_short" => include_str!("../tdm_slot_pcm_short.svg"),
"tdm_slot_pcm_long" => include_str!("../tdm_slot_pcm_long.svg"),
))]
#![cfg_attr(
any(i2s_supports_pdm_tx, i2s_supports_pdm_rx),
doc = r"## PDM mode
PDM (pulse-density modulation) is supported on I2S instances where the hardware
provides PDM (see per-instance metadata). Use [`I2s::new_pdm`] with [`PdmConfig`].
Hardware PCM-to-PDM / PDM-to-PCM conversion is only available on instances that
support it (typically I2S0); other instances require [`PdmDataFormat::Raw`].
PDM uses a single clock pin (`with_clk` on [`I2s::i2s_tx`] / [`I2s::i2s_rx`])
instead of separate BCLK and WS lines. Only simplex operation (TX *or* RX) is
supported.
```rust, no_run
# {before_snippet}
use esp_hal::i2s::master::{I2s, PdmSlotMode, PdmTxConfig, PdmConfig};
use esp_hal::time::Rate;
let pdm = PdmConfig::tx_only(PdmTxConfig::new_codec_default(
Rate::from_hz(16_000),
PdmSlotMode::Mono,
));
let i2s = I2s::new_pdm(peripherals.I2S0, peripherals.__dma_channel__, pdm)?;
# {after_snippet}
```
"
)]
use core::mem::ManuallyDrop;
use enumset::{EnumSet, EnumSetType, enum_set};
use private::*;
mod low_level;
#[doc(hidden)]
pub use low_level::Info;
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
pub use super::pdm::{
PdmConfig,
PdmDataFormat,
PdmError,
PdmInstance,
PdmRxConfig,
PdmSlotMode,
PdmTxConfig,
};
use crate::{
Async,
Blocking,
DriverMode,
clock::dividers::FractionalDivider,
dma::{
Channel,
ChannelRx,
ChannelTx,
DmaChannel,
DmaEligiblePeripheral,
DmaError,
DmaRxBuffer,
DmaRxInterrupt,
DmaTxBuffer,
DmaTxInterrupt,
asynch::{DmaRxFuture, DmaTxFuture},
},
gpio::{
InputConfig,
InputSignal,
OutputConfig,
OutputSignal,
interconnect::{PeripheralInput, PeripheralOutput},
},
i2s::{AnyI2s, any::Inner as AnyI2sInner},
interrupt::{InterruptConfigurable, InterruptHandler},
pac::i2s0::RegisterBlock,
system::PeripheralGuard,
time::Rate,
};
#[derive(Debug, EnumSetType)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2sInterrupt {
RxHung,
TxHung,
#[cfg(not(i2s_version = "1"))]
RxDone,
#[cfg(not(i2s_version = "1"))]
TxDone,
}
with_i2s_dma_engine! {
($engine:tt, $any_channel:ident) => {
#[instability::unstable]
#[diagnostic::on_unimplemented(
message = "The DMA channel cannot be used with this I2S peripheral",
label = "This DMA channel",
note = "Use a channel that matches the I2S instance."
)]
pub trait I2sMasterDmaChannel<'d, S>: crate::private::Sealed + Into<crate::dma::$any_channel<'d>> {}
crate::macros::impl_dma_channel_trait! {
$engine,
any_peri = AnyI2s<'d>,
peris = for_each_i2s,
($peri:path, $ch:path) => {
impl<'d> I2sMasterDmaChannel<'d, $peri> for $ch {}
}
}
type I2sMasterErased<'d> = crate::dma::$any_channel<'d>;
};
}
impl<'d> I2s<'d, crate::Blocking> {
pub fn new<I: Instance + 'd>(
i2s: I,
channel: impl I2sMasterDmaChannel<'d, I>,
config: TdmConfig,
) -> Result<Self, ConfigError> {
Self::new_internal(i2s, channel.into(), Config::Tdm(config))
}
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
pub fn new_pdm<I: Instance + PdmInstance + 'd>(
i2s: I,
channel: impl I2sMasterDmaChannel<'d, I>,
config: PdmConfig,
) -> Result<Self, ConfigError> {
Self::new_internal(i2s, channel.into(), Config::Pdm(config))
}
}
pub(crate) const I2S_LL_MCLK_DIVIDER_BIT_WIDTH: usize = property!("i2s.mclk_divider_bit_width");
pub(crate) const I2S_LL_MCLK_DIVIDER_MAX: usize = (1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1;
#[instability::unstable]
pub struct I2sTxDmaTransfer<'d, Dm, Buf>
where
Dm: DriverMode,
Buf: DmaTxBuffer,
{
i2s_tx: ManuallyDrop<I2sTx<'d, Dm>>,
buffer_view: ManuallyDrop<Buf::View>,
completed: bool,
}
impl<'d, Dm, Buf> I2sTxDmaTransfer<'d, Dm, Buf>
where
Dm: DriverMode,
Buf: DmaTxBuffer,
{
pub fn is_done(&self) -> bool {
self.completed || self.i2s_tx.i2s.info().is_tx_done()
}
pub fn wait(self) -> (Result<(), DmaError>, I2sTx<'d, Dm>, Buf::Final) {
while !self.is_done() {}
let (i2s_tx, buf) = self.stop();
if i2s_tx.tx_channel.has_error() {
(Err(DmaError::DescriptorError), i2s_tx, buf)
} else {
(Ok(()), i2s_tx, buf)
}
}
pub fn stop(mut self) -> (I2sTx<'d, Dm>, Buf::Final) {
self.i2s_tx.tx_channel.stop_transfer();
self.i2s_tx.i2s.info().tx_stop();
let (i2s_tx, buf) = self.release();
(i2s_tx, Buf::from_view(buf))
}
fn release(mut self) -> (I2sTx<'d, Dm>, Buf::View) {
let (i2s_tx, buffer_view) = unsafe {
(
ManuallyDrop::take(&mut self.i2s_tx),
ManuallyDrop::take(&mut self.buffer_view),
)
};
core::mem::forget(self);
(i2s_tx, buffer_view)
}
}
impl<'d, Buf> I2sTxDmaTransfer<'d, Async, Buf>
where
Buf: DmaTxBuffer,
{
pub async fn wait_async(mut self) -> (Result<(), DmaError>, I2sTx<'d, Async>, Buf::Final) {
if !self.completed {
if let Err(err) = DmaTxFuture::new(&mut self.i2s_tx.tx_channel).await {
self.i2s_tx.tx_channel.stop_transfer();
self.i2s_tx.i2s.info().tx_stop();
let (i2s_tx, buf) = self.release();
return (Err(err), i2s_tx, Buf::from_view(buf));
}
while !self.is_done() {}
self.completed = true;
}
let (i2s_tx, buf) = self.stop();
if i2s_tx.tx_channel.has_error() {
(Err(DmaError::DescriptorError), i2s_tx, buf)
} else {
(Ok(()), i2s_tx, buf)
}
}
pub async fn wait_for_done_async(&mut self) -> Result<(), DmaError> {
if !self.completed {
DmaTxFuture::new(&mut self.i2s_tx.tx_channel).await?;
while !self.is_done() {}
self.completed = true;
}
Ok(())
}
pub async fn wait_for_available_async(&mut self) -> Result<(), DmaError> {
DmaTxFuture::new_with_config(
&mut self.i2s_tx.tx_channel,
enum_set!(DmaTxInterrupt::Eof),
enum_set!(DmaTxInterrupt::DescriptorError | DmaTxInterrupt::TotalEof),
)
.await
}
}
impl<Dm: DriverMode, BUF: DmaTxBuffer> core::ops::Deref for I2sTxDmaTransfer<'_, Dm, BUF> {
type Target = BUF::View;
fn deref(&self) -> &Self::Target {
&self.buffer_view
}
}
impl<Dm: DriverMode, BUF: DmaTxBuffer> core::ops::DerefMut for I2sTxDmaTransfer<'_, Dm, BUF> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer_view
}
}
impl<Dm: DriverMode, BUF: DmaTxBuffer> Drop for I2sTxDmaTransfer<'_, Dm, BUF> {
fn drop(&mut self) {
self.i2s_tx.tx_channel.stop_transfer();
self.i2s_tx.i2s.info().tx_stop();
let view = unsafe {
ManuallyDrop::drop(&mut self.i2s_tx);
ManuallyDrop::take(&mut self.buffer_view)
};
let _ = BUF::from_view(view);
}
}
#[instability::unstable]
pub struct I2sRxDmaTransfer<'d, Dm, Buf>
where
Dm: DriverMode,
Buf: DmaRxBuffer,
{
i2s_rx: ManuallyDrop<I2sRx<'d, Dm>>,
buffer_view: ManuallyDrop<Buf::View>,
completed: bool,
}
impl<'d, Dm, Buf> I2sRxDmaTransfer<'d, Dm, Buf>
where
Dm: DriverMode,
Buf: DmaRxBuffer,
{
pub fn is_done(&self) -> bool {
self.completed || self.i2s_rx.i2s.info().is_rx_done()
}
pub fn wait(self) -> (Result<(), DmaError>, I2sRx<'d, Dm>, Buf::Final) {
while !self.is_done() {}
let (i2s_rx, buf) = self.stop();
if i2s_rx.rx_channel.has_error() {
(Err(DmaError::DescriptorError), i2s_rx, buf)
} else {
(Ok(()), i2s_rx, buf)
}
}
pub fn stop(mut self) -> (I2sRx<'d, Dm>, Buf::Final) {
self.i2s_rx.i2s.info().rx_stop();
self.i2s_rx.rx_channel.stop_transfer();
let (i2s_rx, buf) = self.release();
(i2s_rx, Buf::from_view(buf))
}
fn release(mut self) -> (I2sRx<'d, Dm>, Buf::View) {
let (i2s_rx, buffer_view) = unsafe {
(
ManuallyDrop::take(&mut self.i2s_rx),
ManuallyDrop::take(&mut self.buffer_view),
)
};
core::mem::forget(self);
(i2s_rx, buffer_view)
}
}
impl<'d, Buf> I2sRxDmaTransfer<'d, Async, Buf>
where
Buf: DmaRxBuffer,
{
pub async fn wait_async(mut self) -> (Result<(), DmaError>, I2sRx<'d, Async>, Buf::Final) {
if !self.completed {
if let Err(err) = DmaRxFuture::new_with_config(
&mut self.i2s_rx.rx_channel,
enum_set!(DmaRxInterrupt::DescriptorEmpty),
enum_set!(DmaRxInterrupt::ErrorEof | DmaRxInterrupt::DescriptorError),
)
.await
{
self.completed = true;
self.i2s_rx.i2s.info().rx_stop();
self.i2s_rx.rx_channel.stop_transfer();
let (i2s_rx, buf) = self.release();
return (Err(err), i2s_rx, Buf::from_view(buf));
}
self.completed = true;
}
let (i2s_rx, buf) = self.stop();
if i2s_rx.rx_channel.has_error() {
(Err(DmaError::DescriptorError), i2s_rx, buf)
} else {
(Ok(()), i2s_rx, buf)
}
}
pub async fn wait_for_done_async(&mut self) -> Result<(), DmaError> {
if !self.completed {
DmaRxFuture::new_with_config(
&mut self.i2s_rx.rx_channel,
enum_set!(DmaRxInterrupt::DescriptorEmpty),
enum_set!(DmaRxInterrupt::ErrorEof | DmaRxInterrupt::DescriptorError),
)
.await?;
self.completed = true;
}
Ok(())
}
pub async fn wait_for_available_async(&mut self) -> Result<(), DmaError> {
DmaRxFuture::new_with_config(
&mut self.i2s_rx.rx_channel,
enum_set!(DmaRxInterrupt::SuccessfulEof | DmaRxInterrupt::Done),
enum_set!(
DmaRxInterrupt::ErrorEof
| DmaRxInterrupt::DescriptorError
| DmaRxInterrupt::DescriptorEmpty
),
)
.await
}
}
impl<Dm: DriverMode, BUF: DmaRxBuffer> core::ops::Deref for I2sRxDmaTransfer<'_, Dm, BUF> {
type Target = BUF::View;
fn deref(&self) -> &Self::Target {
&self.buffer_view
}
}
impl<Dm: DriverMode, BUF: DmaRxBuffer> core::ops::DerefMut for I2sRxDmaTransfer<'_, Dm, BUF> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer_view
}
}
impl<Dm: DriverMode, BUF: DmaRxBuffer> Drop for I2sRxDmaTransfer<'_, Dm, BUF> {
fn drop(&mut self) {
self.i2s_rx.rx_channel.stop_transfer();
self.i2s_rx.i2s.info().rx_stop();
let view = unsafe {
ManuallyDrop::drop(&mut self.i2s_rx);
ManuallyDrop::take(&mut self.buffer_view)
};
let _ = BUF::from_view(view);
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
pub enum Error {
Unknown,
DmaError(DmaError),
IllegalArgument,
}
impl From<DmaError> for Error {
fn from(value: DmaError) -> Self {
Error::DmaError(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg(not(i2s_version = "1"))]
pub enum DataFormat {
Data32Channel32,
Data32Channel24,
Data32Channel16,
Data32Channel8,
Data16Channel16,
Data16Channel8,
Data8Channel8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg(esp32s2)]
pub enum DataFormat {
Data32Channel32,
Data24Channel24,
Data16Channel16,
Data8Channel8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg(esp32)]
pub enum DataFormat {
Data32Channel32,
Data16Channel16,
}
#[cfg(not(i2s_version = "1"))]
impl DataFormat {
pub fn data_bits(&self) -> u8 {
match self {
DataFormat::Data32Channel32 => 32,
DataFormat::Data32Channel24 => 32,
DataFormat::Data32Channel16 => 32,
DataFormat::Data32Channel8 => 32,
DataFormat::Data16Channel16 => 16,
DataFormat::Data16Channel8 => 16,
DataFormat::Data8Channel8 => 8,
}
}
pub fn channel_bits(&self) -> u8 {
match self {
DataFormat::Data32Channel32 => 32,
DataFormat::Data32Channel24 => 24,
DataFormat::Data32Channel16 => 16,
DataFormat::Data32Channel8 => 8,
DataFormat::Data16Channel16 => 16,
DataFormat::Data16Channel8 => 8,
DataFormat::Data8Channel8 => 8,
}
}
}
#[cfg(esp32s2)]
impl DataFormat {
pub fn data_bits(&self) -> u8 {
match self {
DataFormat::Data32Channel32 => 32,
DataFormat::Data24Channel24 => 24,
DataFormat::Data16Channel16 => 16,
DataFormat::Data8Channel8 => 8,
}
}
pub fn channel_bits(&self) -> u8 {
match self {
DataFormat::Data32Channel32 => 32,
DataFormat::Data24Channel24 => 24,
DataFormat::Data16Channel16 => 16,
DataFormat::Data8Channel8 => 8,
}
}
}
#[cfg(esp32)]
impl DataFormat {
pub fn data_bits(&self) -> u8 {
match self {
DataFormat::Data32Channel32 => 32,
DataFormat::Data16Channel16 => 16,
}
}
pub fn channel_bits(&self) -> u8 {
match self {
DataFormat::Data32Channel32 => 32,
DataFormat::Data16Channel16 => 16,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BitOrder {
#[default]
MsbFirst,
LsbFirst,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Endianness {
#[default]
LittleEndian,
BigEndian,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum WsWidth {
#[default]
HalfFrame,
#[cfg(not(i2s_version = "1"))]
OneChannel,
Bit,
#[cfg(not(i2s_version = "1"))]
Bits(u16),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Polarity {
#[default]
ActiveHigh,
ActiveLow,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Channels {
count: u8,
mask: u16,
fill: Option<u32>,
}
impl Channels {
pub const STEREO: Channels = Channels::new_impl(2, 0b11, None);
pub const MONO: Channels = Channels::new_impl(2, 0b01, None);
pub const LEFT: Channels = Channels::new_impl(2, 0b01, Some(0));
pub const RIGHT: Channels = Channels::new_impl(2, 0b10, Some(0));
#[procmacros::doc_replace]
#[cfg(not(i2s_version = "1"))]
pub const fn new(count: u8, mask: u16, fill: Option<u32>) -> Self {
Self::new_impl(count, mask, fill)
}
const fn new_impl(count: u8, mut mask: u16, fill: Option<u32>) -> Self {
mask &= (1 << count) - 1;
Self { count, mask, fill }
}
#[cfg(not(i2s_version = "1"))]
fn active_count(&self) -> u8 {
self.mask.count_ones() as u8
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(not(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx)), derive(Eq, Hash))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub(crate) enum Config {
Tdm(TdmConfig),
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
Pdm(PdmConfig),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct TdmConfig {
rx_config: TdmUnitConfig,
tx_config: TdmUnitConfig,
signal_loopback: bool,
#[cfg(i2s_version = "1")]
sample_rate: Rate,
#[cfg(i2s_version = "1")]
data_format: DataFormat,
}
impl Config {
fn validate(&self, _info: &Info) -> Result<(), ConfigError> {
match self {
Self::Tdm(c) => c.validate(),
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
Self::Pdm(c) => c.validate(_info).map_err(ConfigError::Pdm),
}
}
#[cfg(i2s_version = "1")]
fn calculate_clock(&self) -> I2sClockDividers {
match self {
Self::Tdm(c) => c.calculate_clock(),
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
Self::Pdm(_) => unreachable!(),
}
}
}
impl TdmConfig {
pub fn new_tdm_philips() -> Self {
Self {
rx_config: TdmUnitConfig::new_tdm_philips(),
tx_config: TdmUnitConfig::new_tdm_philips(),
..Default::default()
}
}
pub fn new_tdm_msb() -> Self {
Self {
rx_config: TdmUnitConfig::new_tdm_msb(),
tx_config: TdmUnitConfig::new_tdm_msb(),
..Default::default()
}
}
pub fn new_tdm_pcm_short() -> Self {
Self {
rx_config: TdmUnitConfig::new_tdm_pcm_short(),
tx_config: TdmUnitConfig::new_tdm_pcm_short(),
..Default::default()
}
}
#[cfg(not(i2s_version = "1"))]
pub fn new_tdm_pcm_long() -> Self {
Self {
rx_config: TdmUnitConfig::new_tdm_pcm_long(),
tx_config: TdmUnitConfig::new_tdm_pcm_long(),
..Default::default()
}
}
fn validate(&self) -> Result<(), ConfigError> {
self.rx_config.validate()?;
self.tx_config.validate()?;
Ok(())
}
#[cfg(i2s_version = "1")]
fn calculate_clock(&self) -> I2sClockDividers {
I2sClockDividers::new(self.sample_rate, 2, self.data_format.data_bits())
}
#[must_use]
#[cfg(not(i2s_version = "1"))]
pub fn with_sample_rate(self, sample_rate: Rate) -> Self {
Self {
rx_config: self.rx_config.with_sample_rate(sample_rate),
tx_config: self.tx_config.with_sample_rate(sample_rate),
..self
}
}
#[must_use]
pub fn with_channels(self, channels: Channels) -> Self {
Self {
rx_config: self.rx_config.with_channels(channels),
tx_config: self.tx_config.with_channels(channels),
..self
}
}
#[must_use]
#[cfg(not(i2s_version = "1"))]
pub fn with_data_format(self, data_format: DataFormat) -> Self {
Self {
rx_config: self.rx_config.with_data_format(data_format),
tx_config: self.tx_config.with_data_format(data_format),
..self
}
}
#[must_use]
pub fn with_ws_width(self, ws_width: WsWidth) -> Self {
Self {
rx_config: self.rx_config.with_ws_width(ws_width),
tx_config: self.tx_config.with_ws_width(ws_width),
..self
}
}
#[must_use]
pub fn with_ws_polarity(self, ws_polarity: Polarity) -> Self {
Self {
rx_config: self.rx_config.with_ws_polarity(ws_polarity),
tx_config: self.tx_config.with_ws_polarity(ws_polarity),
..self
}
}
#[must_use]
pub fn with_msb_shift(self, msb_shift: bool) -> Self {
Self {
rx_config: self.rx_config.with_msb_shift(msb_shift),
tx_config: self.tx_config.with_msb_shift(msb_shift),
..self
}
}
#[cfg(not(esp32))]
#[must_use]
pub fn with_endianness(self, endianness: Endianness) -> Self {
Self {
rx_config: self.rx_config.with_endianness(endianness),
tx_config: self.tx_config.with_endianness(endianness),
..self
}
}
#[cfg(not(i2s_version = "1"))]
#[must_use]
pub fn with_bit_order(self, bit_order: BitOrder) -> Self {
Self {
rx_config: self.rx_config.with_bit_order(bit_order),
tx_config: self.tx_config.with_bit_order(bit_order),
..self
}
}
}
#[allow(clippy::derivable_impls)]
impl Default for TdmConfig {
fn default() -> Self {
Self {
rx_config: TdmUnitConfig::new_tdm_philips(),
tx_config: TdmUnitConfig::new_tdm_philips(),
signal_loopback: false,
#[cfg(i2s_version = "1")]
sample_rate: Rate::from_hz(44100),
#[cfg(i2s_version = "1")]
data_format: DataFormat::Data16Channel16,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct TdmUnitConfig {
#[cfg(not(i2s_version = "1"))]
sample_rate: Rate,
channels: Channels,
#[cfg(not(i2s_version = "1"))]
data_format: DataFormat,
ws_width: WsWidth,
ws_polarity: Polarity,
msb_shift: bool,
#[cfg(not(esp32))]
endianness: Endianness,
#[cfg(not(i2s_version = "1"))]
bit_order: BitOrder,
}
pub type UnitConfig = TdmUnitConfig;
impl TdmUnitConfig {
pub fn new_tdm_philips() -> Self {
Self {
#[cfg(not(i2s_version = "1"))]
sample_rate: Rate::from_hz(44100),
channels: Channels::STEREO,
#[cfg(not(i2s_version = "1"))]
data_format: DataFormat::Data16Channel16,
ws_width: WsWidth::HalfFrame,
ws_polarity: Polarity::ActiveLow,
msb_shift: true,
#[cfg(not(esp32))]
endianness: Endianness::LittleEndian,
#[cfg(not(i2s_version = "1"))]
bit_order: BitOrder::MsbFirst,
}
}
pub fn new_tdm_msb() -> Self {
Self::new_tdm_philips().with_msb_shift(false)
}
pub fn new_tdm_pcm_short() -> Self {
Self::new_tdm_philips()
.with_ws_width(WsWidth::Bit)
.with_ws_polarity(Polarity::ActiveHigh)
}
#[cfg(not(i2s_version = "1"))]
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(i2s_version = "1"))]
if self.channels.active_count() == 0 || self.channels.count > 16 {
return Err(ConfigError::ChannelsOutOfRange);
}
Ok(())
}
#[cfg(not(i2s_version = "1"))]
fn calculate_ws_width(&self) -> Result<u16, ConfigError> {
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,
};
const MAX_WS_WIDTH: u16 = property!("i2s.max_ws_width");
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(i2s_version = "1"))]
fn calculate_clock(&self) -> I2sClockDividers {
I2sClockDividers::new(
self.sample_rate,
self.channels.count,
self.data_format.data_bits(),
)
}
}
impl Default for TdmUnitConfig {
fn default() -> Self {
Self::new_tdm_philips()
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConfigError {
#[cfg(not(i2s_version = "1"))]
ChannelsOutOfRange,
#[cfg(not(i2s_version = "1"))]
WsWidthOutOfRange,
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
Pdm(PdmError),
}
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
impl From<PdmError> for ConfigError {
fn from(err: PdmError) -> Self {
Self::Pdm(err)
}
}
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(i2s_version = "1"))]
ConfigError::ChannelsOutOfRange => {
write!(
f,
"Provided channels configuration has no active channels or has over 16 total channels"
)
}
#[cfg(not(i2s_version = "1"))]
ConfigError::WsWidthOutOfRange => {
write!(
f,
"The requested WS signal width is out of supported range (1..=128)"
)
}
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
ConfigError::Pdm(err) => write!(f, "{err}"),
}
}
}
#[non_exhaustive]
pub struct I2s<'d, Dm>
where
Dm: DriverMode,
{
pub i2s_rx: RxCreator<'d, Dm>,
pub i2s_tx: TxCreator<'d, Dm>,
}
impl<Dm> I2s<'_, Dm>
where
Dm: DriverMode,
{
#[cfg_attr(
not(multi_core),
doc = "Registers an interrupt handler for the peripheral."
)]
#[cfg_attr(
multi_core,
doc = "Registers an interrupt handler for the peripheral on the current core."
)]
#[doc = ""]
#[instability::unstable]
pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
self.i2s_tx.i2s.set_interrupt_handler(handler);
}
#[instability::unstable]
pub fn listen(&mut self, interrupts: impl Into<EnumSet<I2sInterrupt>>) {
self.i2s_tx
.i2s
.info()
.enable_listen(interrupts.into(), true);
}
#[instability::unstable]
pub fn unlisten(&mut self, interrupts: impl Into<EnumSet<I2sInterrupt>>) {
self.i2s_tx
.i2s
.info()
.enable_listen(interrupts.into(), false);
}
#[instability::unstable]
pub fn interrupts(&mut self) -> EnumSet<I2sInterrupt> {
self.i2s_tx.i2s.info().interrupts()
}
#[instability::unstable]
pub fn clear_interrupts(&mut self, interrupts: impl Into<EnumSet<I2sInterrupt>>) {
self.i2s_tx.i2s.info().clear_interrupts(interrupts.into());
}
}
impl<Dm> crate::private::Sealed for I2s<'_, Dm> where Dm: DriverMode {}
impl<Dm> InterruptConfigurable for I2s<'_, Dm>
where
Dm: DriverMode,
{
fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
I2s::set_interrupt_handler(self, handler);
}
}
impl<'d> I2s<'d, Blocking> {
#[cfg(i2s_driver_supported)]
fn new_internal(
i2s: impl Instance + 'd,
channel: I2sMasterErased<'d>,
config: Config,
) -> Result<Self, ConfigError> {
let channel = Channel::new(channel);
let i2s = i2s.degrade();
channel.runtime_ensure_compatible(i2s.dma_peripheral());
let peripheral = i2s.info().peripheral;
let rx_guard = PeripheralGuard::new(peripheral);
let tx_guard = PeripheralGuard::new(peripheral);
i2s.info().set_master();
i2s.info().configure(&config)?;
match &config {
Config::Tdm(_) => {
i2s.info().update_tx();
i2s.info().update_rx();
}
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
Config::Pdm(c) => {
if c.tx.is_some() {
i2s.info().update_tx();
}
if c.rx.is_some() {
i2s.info().update_rx();
}
}
}
Ok(Self {
i2s_rx: RxCreator {
i2s: unsafe { i2s.clone_unchecked() },
rx_channel: channel.rx,
guard: rx_guard,
#[cfg(i2s_version = "1")]
data_format: match config {
Config::Tdm(c) => c.data_format,
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
Config::Pdm(_) => DataFormat::Data16Channel16,
},
},
i2s_tx: TxCreator {
i2s,
tx_channel: channel.tx,
guard: tx_guard,
#[cfg(i2s_version = "1")]
data_format: match config {
Config::Tdm(c) => c.data_format,
#[cfg(any(i2s_supports_pdm_tx, i2s_supports_pdm_rx))]
Config::Pdm(_) => DataFormat::Data16Channel16,
},
},
})
}
pub fn into_async(self) -> I2s<'d, Async> {
I2s {
i2s_rx: RxCreator {
i2s: self.i2s_rx.i2s,
rx_channel: self.i2s_rx.rx_channel.into_async(),
guard: self.i2s_rx.guard,
#[cfg(i2s_version = "1")]
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(i2s_version = "1")]
data_format: self.i2s_tx.data_format,
},
}
}
}
#[cfg(esp32)]
mod esp32 {
use super::*;
use crate::gpio::OutputSignal;
pub trait ClkPin<'d>: PeripheralOutput<'d> {
#[doc(hidden)]
fn signal(&self) -> OutputSignal;
}
impl<'d> ClkPin<'d> for crate::peripherals::GPIO0<'d> {
fn signal(&self) -> OutputSignal {
OutputSignal::CLK_OUT1
}
}
impl<'d> ClkPin<'d> for crate::peripherals::GPIO1<'d> {
fn signal(&self) -> OutputSignal {
OutputSignal::CLK_OUT3
}
}
impl<'d> ClkPin<'d> for crate::peripherals::GPIO3<'d> {
fn signal(&self) -> OutputSignal {
OutputSignal::CLK_OUT2
}
}
}
#[cfg(esp32)]
pub use esp32::ClkPin;
impl<'d, Dm> I2s<'d, Dm>
where
Dm: DriverMode,
{
#[cfg(not(esp32))]
pub fn with_mclk(self, mclk: impl PeripheralOutput<'d>) -> Self {
let mclk = mclk.into();
mclk.apply_output_config(&OutputConfig::default());
mclk.set_output_enable(true);
self.i2s_tx.i2s.info().mclk.connect_to(&mclk);
self
}
#[cfg(esp32)]
pub fn with_mclk(self, mclk: impl ClkPin<'d>) -> Self {
use crate::{gpio::OutputSignal, peripherals::IO_MUX};
let clk_signal = mclk.signal();
let mclk = mclk.into();
mclk.apply_output_config(&OutputConfig::default());
mclk.set_output_enable(true);
let selector = match self.i2s_rx.i2s.0 {
super::any::Inner::I2s0(_) => 0x0,
super::any::Inner::I2s1(_) => 0xF,
};
IO_MUX::regs().pin_ctrl().modify(|_, w| unsafe {
match clk_signal {
OutputSignal::CLK_OUT1 => w.clk1().bits(selector),
OutputSignal::CLK_OUT2 => w.clk2().bits(selector),
OutputSignal::CLK_OUT3 => w.clk3().bits(selector),
_ => unreachable!(),
}
});
clk_signal.connect_to(&mclk);
self
}
}
pub struct I2sTx<'d, Dm>
where
Dm: DriverMode,
{
i2s: AnyI2s<'d>,
tx_channel: ChannelTx<Dm, <I2sMasterErased<'d> as DmaChannel>::Tx>,
_guard: PeripheralGuard,
#[cfg(i2s_version = "1")]
data_format: DataFormat,
}
impl<Dm> core::fmt::Debug for I2sTx<'_, Dm>
where
Dm: DriverMode,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("I2sTx").finish()
}
}
impl<'d, Dm> I2sTx<'d, Dm>
where
Dm: DriverMode,
{
#[allow(clippy::type_complexity)]
#[instability::unstable]
pub fn write<TX: DmaTxBuffer>(
mut self,
mut buffer: TX,
) -> Result<I2sTxDmaTransfer<'d, Dm, TX>, (Error, Self, TX)> {
self.i2s.info().reset_tx();
let res = unsafe {
self.tx_channel
.prepare_transfer(self.i2s.dma_peripheral(), &mut buffer)
.and_then(|_| self.tx_channel.start_transfer())
};
if let Err(err) = res {
return Err((Error::DmaError(err), self, buffer));
}
self.i2s.info().tx_start();
Ok(I2sTxDmaTransfer {
i2s_tx: ManuallyDrop::new(self),
buffer_view: ManuallyDrop::new(buffer.into_view()),
completed: false,
})
}
pub fn apply_config(&mut self, tx_config: &UnitConfig) -> Result<(), ConfigError> {
tx_config.validate()?;
self.i2s.info().configure_tx(
tx_config,
#[cfg(i2s_version = "1")]
self.data_format,
)
}
}
pub struct I2sRx<'d, Dm>
where
Dm: DriverMode,
{
i2s: AnyI2s<'d>,
rx_channel: ChannelRx<Dm, <I2sMasterErased<'d> as DmaChannel>::Rx>,
_guard: PeripheralGuard,
#[cfg(i2s_version = "1")]
data_format: DataFormat,
}
impl<Dm> core::fmt::Debug for I2sRx<'_, Dm>
where
Dm: DriverMode,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("I2sRx").finish()
}
}
impl<'d, Dm> I2sRx<'d, Dm>
where
Dm: DriverMode,
{
pub fn read<BUF>(
mut self,
mut buffer: BUF,
) -> Result<I2sRxDmaTransfer<'d, Dm, BUF>, (Error, Self, BUF)>
where
BUF: DmaRxBuffer,
{
self.i2s.info().reset_rx();
let res = unsafe {
self.rx_channel
.prepare_transfer(self.i2s.dma_peripheral(), &mut buffer)
.and_then(|_| self.rx_channel.start_transfer())
};
if let Err(err) = res {
return Err((Error::DmaError(err), self, buffer));
}
self.i2s.info().rx_start(usize::MAX);
Ok(I2sRxDmaTransfer {
i2s_rx: ManuallyDrop::new(self),
buffer_view: ManuallyDrop::new(buffer.into_view()),
completed: false,
})
}
pub fn apply_config(&mut self, rx_config: &UnitConfig) -> Result<(), ConfigError> {
rx_config.validate()?;
self.i2s.info().configure_rx(
rx_config,
#[cfg(i2s_version = "1")]
self.data_format,
)
}
}
pub trait Instance: crate::private::Sealed + super::any::Degrade {
#[doc(hidden)]
fn info(&self) -> &'static Info;
}
for_each_i2s! {
(
$instance:ident, $sys:ident, $mclk:ident,
$bclk:ident, $ws:ident, $bclk_rx:ident, $ws_rx:ident,
[$($dout:ident),+], [$($din:ident),+], $pdm_tx:literal, $pdm_rx:literal,
$pcm2pdm:literal, $pdm2pcm:literal
) => {
impl Instance for crate::peripherals::$instance<'_> {
fn info(&self) -> &'static Info {
static INFO: Info = Info {
register_block: crate::peripherals::$instance::PTR.cast::<RegisterBlock>(),
peripheral: crate::system::Peripheral::$sys,
#[cfg(not(esp32))]
mclk: OutputSignal::$mclk,
bclk: OutputSignal::$bclk,
ws: OutputSignal::$ws,
bclk_rx: OutputSignal::$bclk_rx,
ws_rx: OutputSignal::$ws_rx,
dout_lines: &[$(OutputSignal::$dout),+],
din_lines: &[$(InputSignal::$din),+],
pdm_tx: $pdm_tx,
pdm_rx: $pdm_rx,
pcm2pdm: $pcm2pdm,
pdm2pcm: $pdm2pcm,
};
&INFO
}
}
};
}
impl Instance for AnyI2s<'_> {
fn info(&self) -> &'static Info {
match &self.0 {
#[cfg(soc_has_i2s0)]
AnyI2sInner::I2s0(i2s) => i2s.info(),
#[cfg(soc_has_i2s1)]
AnyI2sInner::I2s1(i2s) => i2s.info(),
#[cfg(soc_has_i2s2)]
AnyI2sInner::I2s2(i2s) => i2s.info(),
}
}
}
impl AnyI2s<'_> {
delegate::delegate! {
to match &self.0 {
#[cfg(soc_has_i2s0)]
AnyI2sInner::I2s0(i2s) => i2s,
#[cfg(soc_has_i2s1)]
AnyI2sInner::I2s1(i2s) => i2s,
#[cfg(soc_has_i2s2)]
AnyI2sInner::I2s2(i2s) => i2s,
} {
fn bind_peri_interrupt(&self, handler: InterruptHandler);
fn disable_peri_interrupt_on_all_cores(&self);
}
}
pub(super) fn set_interrupt_handler(&self, handler: InterruptHandler) {
self.disable_peri_interrupt_on_all_cores();
self.bind_peri_interrupt(handler);
}
}
pub(crate) mod private {
use super::*;
pub struct TxCreator<'d, Dm>
where
Dm: DriverMode,
{
pub i2s: AnyI2s<'d>,
pub tx_channel: ChannelTx<Dm, <I2sMasterErased<'d> as DmaChannel>::Tx>,
pub(crate) guard: PeripheralGuard,
#[cfg(i2s_version = "1")]
pub(crate) data_format: DataFormat,
}
impl<'d, Dm> TxCreator<'d, Dm>
where
Dm: DriverMode,
{
pub fn build(self) -> I2sTx<'d, Dm> {
let peripheral = self.i2s.info().peripheral;
I2sTx {
i2s: self.i2s,
tx_channel: self.tx_channel,
_guard: PeripheralGuard::new(peripheral),
#[cfg(i2s_version = "1")]
data_format: self.data_format,
}
}
pub fn with_bclk(self, bclk: impl PeripheralOutput<'d>) -> Self {
let bclk = bclk.into();
bclk.apply_output_config(&OutputConfig::default());
bclk.set_output_enable(true);
self.i2s.info().bclk.connect_to(&bclk);
self
}
pub fn with_ws(self, ws: impl PeripheralOutput<'d>) -> Self {
let ws = ws.into();
ws.apply_output_config(&OutputConfig::default());
ws.set_output_enable(true);
self.i2s.info().ws.connect_to(&ws);
self
}
pub fn with_dout(self, dout: impl PeripheralOutput<'d>) -> Self {
let dout = dout.into();
dout.apply_output_config(&OutputConfig::default());
dout.set_output_enable(true);
unwrap!(self.i2s.info().dout(0)).connect_to(&dout);
self
}
pub fn with_clk(self, clk: impl PeripheralOutput<'d>) -> Self {
self.with_ws(clk)
}
#[cfg(all(i2s_supports_pdm_tx, not(i2s_version = "1")))]
pub fn with_dout2(self, dout: impl PeripheralOutput<'d>) -> Result<Self, ConfigError> {
let dout = dout.into();
dout.apply_output_config(&OutputConfig::default());
dout.set_output_enable(true);
let signal = self.i2s.info().dout(1).ok_or(PdmError::InvalidLine)?;
signal.connect_to(&dout);
Ok(self)
}
}
pub struct RxCreator<'d, Dm>
where
Dm: DriverMode,
{
pub i2s: AnyI2s<'d>,
pub rx_channel: ChannelRx<Dm, <I2sMasterErased<'d> as DmaChannel>::Rx>,
pub(crate) guard: PeripheralGuard,
#[cfg(i2s_version = "1")]
pub(crate) data_format: DataFormat,
}
impl<'d, Dm> RxCreator<'d, Dm>
where
Dm: DriverMode,
{
pub fn build(self) -> I2sRx<'d, Dm> {
let peripheral = self.i2s.info().peripheral;
I2sRx {
i2s: self.i2s,
rx_channel: self.rx_channel,
_guard: PeripheralGuard::new(peripheral),
#[cfg(i2s_version = "1")]
data_format: self.data_format,
}
}
pub fn with_bclk(self, bclk: impl PeripheralOutput<'d>) -> Self {
let bclk = bclk.into();
bclk.apply_output_config(&OutputConfig::default());
bclk.set_output_enable(true);
self.i2s.info().bclk_rx.connect_to(&bclk);
self
}
pub fn with_ws(self, ws: impl PeripheralOutput<'d>) -> Self {
let ws = ws.into();
ws.apply_output_config(&OutputConfig::default());
ws.set_output_enable(true);
self.i2s.info().ws_rx.connect_to(&ws);
self
}
pub fn with_din(self, din: impl PeripheralInput<'d>) -> Self {
let din = din.into();
din.apply_input_config(&InputConfig::default());
din.set_input_enable(true);
unwrap!(self.i2s.info().din(0)).connect_to(&din);
self
}
pub fn with_clk(self, clk: impl PeripheralOutput<'d>) -> Self {
self.with_ws(clk)
}
#[cfg(i2s_supports_pdm_rx)]
pub fn with_din_line(
self,
line: u8,
din: impl PeripheralInput<'d>,
) -> Result<Self, ConfigError> {
let din = din.into();
din.apply_input_config(&InputConfig::default());
din.set_input_enable(true);
let signal = self.i2s.info().din(line).ok_or(PdmError::InvalidLine)?;
signal.connect_to(&din);
Ok(self)
}
}
pub struct I2sClockDividers {
pub(crate) mclk_divider: u32,
pub(crate) bclk_divider: u32,
pub(crate) denominator: u32,
pub(crate) numerator: u32,
}
impl I2sClockDividers {
pub fn new(sample_rate: Rate, channels: u8, data_bits: u8) -> I2sClockDividers {
let mclk_multiple = if data_bits == 24 { 192 } else { 256 };
let sclk = crate::soc::i2s_sclk_frequency();
let rate = sample_rate.as_hz();
let bclk = rate * channels as u32 * data_bits as u32;
let mclk = rate * mclk_multiple;
I2sClockDividers::from_frequencies(sclk, mclk, mclk / bclk)
}
pub(crate) fn from_frequencies(sclk: u32, mclk: u32, bclk_divider: u32) -> Self {
let divider = FractionalDivider::new(sclk, mclk, I2S_LL_MCLK_DIVIDER_MAX as u32);
I2sClockDividers {
mclk_divider: divider.integer,
bclk_divider,
denominator: divider.denominator,
numerator: divider.numerator,
}
}
}
}