diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index a00c6c416..dcee535b5 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -420,6 +420,10 @@ fn main() { (("spi", "SCK"), quote!(crate::spi::SckPin)), (("spi", "MOSI"), quote!(crate::spi::MosiPin)), (("spi", "MISO"), quote!(crate::spi::MisoPin)), + (("spi", "NSS"), quote!(crate::spi::CsPin)), + (("spi", "I2S_MCK"), quote!(crate::spi::MckPin)), + (("spi", "I2S_CK"), quote!(crate::spi::CkPin)), + (("spi", "I2S_WS"), quote!(crate::spi::WsPin)), (("i2c", "SDA"), quote!(crate::i2c::SdaPin)), (("i2c", "SCL"), quote!(crate::i2c::SclPin)), (("rcc", "MCO_1"), quote!(crate::rcc::McoPin)), diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs new file mode 100644 index 000000000..2bb199f68 --- /dev/null +++ b/embassy-stm32/src/i2s.rs @@ -0,0 +1,310 @@ +use embassy_hal_common::into_ref; + +use crate::gpio::sealed::{AFType, Pin as _}; +use crate::gpio::AnyPin; +use crate::pac::spi::vals; +use crate::rcc::get_freqs; +use crate::spi::{Config as SpiConfig, *}; +use crate::time::Hertz; +use crate::{Peripheral, PeripheralRef}; + +#[derive(Copy, Clone)] +pub enum Mode { + Master, + Slave, +} + +#[derive(Copy, Clone)] +pub enum Function { + Transmit, + Receive, +} + +#[derive(Copy, Clone)] +pub enum Standard { + Philips, + MsbFirst, + LsbFirst, + PcmLongSync, + PcmShortSync, +} + +impl Standard { + #[cfg(any(spi_v1, spi_f1))] + pub const fn i2sstd(&self) -> vals::I2sstd { + match self { + Standard::Philips => vals::I2sstd::PHILIPS, + Standard::MsbFirst => vals::I2sstd::MSB, + Standard::LsbFirst => vals::I2sstd::LSB, + Standard::PcmLongSync => vals::I2sstd::PCM, + Standard::PcmShortSync => vals::I2sstd::PCM, + } + } + + #[cfg(any(spi_v1, spi_f1))] + pub const fn pcmsync(&self) -> vals::Pcmsync { + match self { + Standard::PcmLongSync => vals::Pcmsync::LONG, + _ => vals::Pcmsync::SHORT, + } + } +} + +#[derive(Copy, Clone)] +pub enum Format { + /// 16 bit data length on 16 bit wide channel + Data16Channel16, + /// 16 bit data length on 32 bit wide channel + Data16Channel32, + /// 24 bit data length on 32 bit wide channel + Data24Channel32, + /// 32 bit data length on 32 bit wide channel + Data32Channel32, +} + +impl Format { + #[cfg(any(spi_v1, spi_f1))] + pub const fn datlen(&self) -> vals::Datlen { + match self { + Format::Data16Channel16 => vals::Datlen::SIXTEENBIT, + Format::Data16Channel32 => vals::Datlen::SIXTEENBIT, + Format::Data24Channel32 => vals::Datlen::TWENTYFOURBIT, + Format::Data32Channel32 => vals::Datlen::THIRTYTWOBIT, + } + } + + #[cfg(any(spi_v1, spi_f1))] + pub const fn chlen(&self) -> vals::Chlen { + match self { + Format::Data16Channel16 => vals::Chlen::SIXTEENBIT, + Format::Data16Channel32 => vals::Chlen::THIRTYTWOBIT, + Format::Data24Channel32 => vals::Chlen::THIRTYTWOBIT, + Format::Data32Channel32 => vals::Chlen::THIRTYTWOBIT, + } + } +} + +#[derive(Copy, Clone)] +pub enum ClockPolarity { + IdleLow, + IdleHigh, +} + +impl ClockPolarity { + #[cfg(any(spi_v1, spi_f1))] + pub const fn ckpol(&self) -> vals::Ckpol { + match self { + ClockPolarity::IdleHigh => vals::Ckpol::IDLEHIGH, + ClockPolarity::IdleLow => vals::Ckpol::IDLELOW, + } + } +} + +/// [`I2S`] configuration. +/// +/// - `MS`: `Master` or `Slave` +/// - `TR`: `Transmit` or `Receive` +/// - `STD`: I2S standard, eg `Philips` +/// - `FMT`: Frame Format marker, eg `Data16Channel16` +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + pub mode: Mode, + pub function: Function, + pub standard: Standard, + pub format: Format, + pub clock_polarity: ClockPolarity, + pub master_clock: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: Mode::Master, + function: Function::Transmit, + standard: Standard::Philips, + format: Format::Data16Channel16, + clock_polarity: ClockPolarity::IdleLow, + master_clock: true, + } + } +} + +pub struct I2S<'d, T: Instance, Tx, Rx> { + _peri: Spi<'d, T, Tx, Rx>, + sd: Option>, + ws: Option>, + ck: Option>, + mck: Option>, +} + +impl<'d, T: Instance, Tx, Rx> I2S<'d, T, Tx, Rx> { + /// Note: Full-Duplex modes are not supported at this time + pub fn new( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + mck: impl Peripheral

> + 'd, + txdma: impl Peripheral

+ 'd, + rxdma: impl Peripheral

+ 'd, + freq: Hertz, + config: Config, + ) -> Self { + into_ref!(sd, ws, ck, mck); + + unsafe { + sd.set_as_af(sd.af_num(), AFType::OutputPushPull); + sd.set_speed(crate::gpio::Speed::VeryHigh); + + ws.set_as_af(ws.af_num(), AFType::OutputPushPull); + ws.set_speed(crate::gpio::Speed::VeryHigh); + + ck.set_as_af(ck.af_num(), AFType::OutputPushPull); + ck.set_speed(crate::gpio::Speed::VeryHigh); + + mck.set_as_af(mck.af_num(), AFType::OutputPushPull); + mck.set_speed(crate::gpio::Speed::VeryHigh); + } + + let spi = Spi::new_internal(peri, txdma, rxdma, freq, SpiConfig::default()); + + #[cfg(all(rcc_f4, not(stm32f410)))] + let pclk = unsafe { get_freqs() }.plli2s.unwrap(); + + #[cfg(stm32f410)] + let pclk = T::frequency(); + + let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format); + + #[cfg(any(spi_v1, spi_f1))] + unsafe { + use stm32_metapac::spi::vals::{I2scfg, Odd}; + + // 1. Select the I2SDIV[7:0] bits in the SPI_I2SPR register to define the serial clock baud + // rate to reach the proper audio sample frequency. The ODD bit in the SPI_I2SPR + // register also has to be defined. + + T::REGS.i2spr().modify(|w| { + w.set_i2sdiv(div); + w.set_odd(match odd { + true => Odd::ODD, + false => Odd::EVEN, + }); + + w.set_mckoe(config.master_clock); + }); + + // 2. Select the CKPOL bit to define the steady level for the communication clock. Set the + // MCKOE bit in the SPI_I2SPR register if the master clock MCK needs to be provided to + // the external DAC/ADC audio component (the I2SDIV and ODD values should be + // computed depending on the state of the MCK output, for more details refer to + // Section 28.4.4: Clock generator). + + // 3. Set the I2SMOD bit in SPI_I2SCFGR to activate the I2S functionalities and choose the + // I2S standard through the I2SSTD[1:0] and PCMSYNC bits, the data length through the + // DATLEN[1:0] bits and the number of bits per channel by configuring the CHLEN bit. + // Select also the I2S master mode and direction (Transmitter or Receiver) through the + // I2SCFG[1:0] bits in the SPI_I2SCFGR register. + + // 4. If needed, select all the potential interruption sources and the DMA capabilities by + // writing the SPI_CR2 register. + + // 5. The I2SE bit in SPI_I2SCFGR register must be set. + + T::REGS.i2scfgr().modify(|w| { + w.set_ckpol(config.clock_polarity.ckpol()); + + w.set_i2smod(true); + w.set_i2sstd(config.standard.i2sstd()); + w.set_pcmsync(config.standard.pcmsync()); + + w.set_datlen(config.format.datlen()); + w.set_chlen(config.format.chlen()); + + w.set_i2scfg(match (config.mode, config.function) { + (Mode::Master, Function::Transmit) => I2scfg::MASTERTX, + (Mode::Master, Function::Receive) => I2scfg::MASTERRX, + (Mode::Slave, Function::Transmit) => I2scfg::SLAVETX, + (Mode::Slave, Function::Receive) => I2scfg::SLAVERX, + }); + + w.set_i2se(true) + }); + } + #[cfg(spi_v2)] + unsafe {} + #[cfg(any(spi_v3, spi_v4))] + unsafe {} + + Self { + _peri: spi, + sd: Some(sd.map_into()), + ws: Some(ws.map_into()), + ck: Some(ck.map_into()), + mck: Some(mck.map_into()), + } + } + + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> + where + Tx: TxDma, + { + self._peri.write(data).await + } + + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> + where + Tx: TxDma, + Rx: RxDma, + { + self._peri.read(data).await + } +} + +impl<'d, T: Instance, Tx, Rx> Drop for I2S<'d, T, Tx, Rx> { + fn drop(&mut self) { + unsafe { + self.sd.as_ref().map(|x| x.set_as_disconnected()); + self.ws.as_ref().map(|x| x.set_as_disconnected()); + self.ck.as_ref().map(|x| x.set_as_disconnected()); + self.mck.as_ref().map(|x| x.set_as_disconnected()); + } + } +} + +// Note, calculation details: +// Fs = i2s_clock / [256 * ((2 * div) + odd)] when master clock is enabled +// Fs = i2s_clock / [(channel_length * 2) * ((2 * div) + odd)]` when master clock is disabled +// channel_length is 16 or 32 +// +// can be rewritten as +// Fs = i2s_clock / (coef * division) +// where coef is a constant equal to 256, 64 or 32 depending channel length and master clock +// and where division = (2 * div) + odd +// +// Equation can be rewritten as +// division = i2s_clock/ (coef * Fs) +// +// note: division = (2 * div) + odd = (div << 1) + odd +// in other word, from bits point of view, division[8:1] = div[7:0] and division[0] = odd +fn compute_baud_rate(i2s_clock: Hertz, request_freq: Hertz, mclk: bool, data_format: Format) -> (bool, u8) { + let coef = if mclk { + 256 + } else if let Format::Data16Channel16 = data_format { + 32 + } else { + 64 + }; + + let (n, d) = (i2s_clock.0, coef * request_freq.0); + let division = (n + (d >> 1)) / d; + + if division < 4 { + (false, 2) + } else if division > 511 { + (true, 255) + } else { + ((division & 1) == 1, (division >> 1) as u8) + } +} diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 11820b7a0..7c83a6984 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -44,6 +44,8 @@ pub mod i2c; #[cfg(crc)] pub mod crc; pub mod flash; +#[cfg(all(spi_v1, rcc_f4))] +pub mod i2s; #[cfg(stm32wb)] pub mod ipcc; pub mod pwm; diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index aefa42435..22ab423b6 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -207,6 +207,17 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config) } + #[allow(dead_code)] + pub(crate) fn new_internal( + peri: impl Peripheral

+ 'd, + txdma: impl Peripheral

+ 'd, + rxdma: impl Peripheral

+ 'd, + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config) + } + fn new_inner( peri: impl Peripheral

+ 'd, sck: Option>, @@ -1039,6 +1050,10 @@ pub trait Instance: Peripheral

+ sealed::Instance + RccPeripheral {} pin_trait!(SckPin, Instance); pin_trait!(MosiPin, Instance); pin_trait!(MisoPin, Instance); +pin_trait!(CsPin, Instance); +pin_trait!(MckPin, Instance); +pin_trait!(CkPin, Instance); +pin_trait!(WsPin, Instance); dma_trait!(RxDma, Instance); dma_trait!(TxDma, Instance); diff --git a/examples/stm32f4/src/bin/i2s_dma.rs b/examples/stm32f4/src/bin/i2s_dma.rs new file mode 100644 index 000000000..e8d7b5f77 --- /dev/null +++ b/examples/stm32f4/src/bin/i2s_dma.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::fmt::Write; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2s::{Config, I2S}; +use embassy_stm32::time::Hertz; +use heapless::String; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut i2s = I2S::new( + p.SPI2, + p.PC3, // sd + p.PB12, // ws + p.PB10, // ck + p.PC6, // mck + p.DMA1_CH4, + p.DMA1_CH3, + Hertz(1_000_000), + Config::default(), + ); + + for n in 0u32.. { + let mut write: String<128> = String::new(); + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + i2s.write(&mut write.as_bytes()).await.ok(); + } +}