From 25cc5241b1e7c4a35c427626cd8f2f8721948830 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Mon, 20 May 2024 16:15:41 +0300 Subject: [PATCH] Add i2s support for spi_v3. --- embassy-stm32/src/i2s.rs | 337 ++++++++++++++++++++++------ embassy-stm32/src/lib.rs | 2 +- examples/stm32f4/src/bin/i2s_dma.rs | 3 +- 3 files changed, 268 insertions(+), 74 deletions(-) diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs index 9c0bbbb87..f893dd235 100644 --- a/embassy-stm32/src/i2s.rs +++ b/embassy-stm32/src/i2s.rs @@ -1,7 +1,9 @@ //! Inter-IC Sound (I2S) + use embassy_hal_internal::into_ref; -use crate::gpio::{AFType, AnyPin, SealedPin}; +use crate::dma::ChannelAndRequest; +use crate::gpio::{AFType, AnyPin, SealedPin, Speed}; use crate::mode::Async; use crate::pac::spi::vals; use crate::spi::{Config as SpiConfig, *}; @@ -17,15 +19,6 @@ pub enum Mode { Slave, } -/// I2S function -#[derive(Copy, Clone)] -pub enum Function { - /// Transmit audio data - Transmit, - /// Receive audio data - Receive, -} - /// I2C standard #[derive(Copy, Clone)] pub enum Standard { @@ -42,7 +35,7 @@ pub enum Standard { } impl Standard { - #[cfg(any(spi_v1, spi_f1))] + #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn i2sstd(&self) -> vals::I2sstd { match self { Standard::Philips => vals::I2sstd::PHILIPS, @@ -53,7 +46,7 @@ impl Standard { } } - #[cfg(any(spi_v1, spi_f1))] + #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn pcmsync(&self) -> vals::Pcmsync { match self { Standard::PcmLongSync => vals::Pcmsync::LONG, @@ -76,7 +69,7 @@ pub enum Format { } impl Format { - #[cfg(any(spi_v1, spi_f1))] + #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn datlen(&self) -> vals::Datlen { match self { Format::Data16Channel16 => vals::Datlen::BITS16, @@ -86,7 +79,7 @@ impl Format { } } - #[cfg(any(spi_v1, spi_f1))] + #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn chlen(&self) -> vals::Chlen { match self { Format::Data16Channel16 => vals::Chlen::BITS16, @@ -107,7 +100,7 @@ pub enum ClockPolarity { } impl ClockPolarity { - #[cfg(any(spi_v1, spi_f1))] + #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn ckpol(&self) -> vals::Ckpol { match self { ClockPolarity::IdleHigh => vals::Ckpol::IDLEHIGH, @@ -127,15 +120,13 @@ impl ClockPolarity { pub struct Config { /// Mode pub mode: Mode, - /// Function (transmit, receive) - pub function: Function, /// Which I2S standard to use. pub standard: Standard, /// Data format. pub format: Format, /// Clock polarity. pub clock_polarity: ClockPolarity, - /// True to eanble master clock output from this instance. + /// True to enable master clock output from this instance. pub master_clock: bool, } @@ -143,7 +134,6 @@ impl Default for Config { fn default() -> Self { Self { mode: Mode::Master, - function: Function::Transmit, standard: Standard::Philips, format: Format::Data16Channel16, clock_polarity: ClockPolarity::IdleLow, @@ -155,50 +145,168 @@ impl Default for Config { /// I2S driver. pub struct I2S<'d> { _peri: Spi<'d, Async>, - sd: Option>, + txsd: Option>, + rxsd: Option>, ws: Option>, ck: Option>, mck: Option>, } +/// I2S function +#[derive(Copy, Clone)] +#[allow(dead_code)] +enum Function { + /// Transmit audio data + Transmit, + /// Receive audio data + Receive, + #[cfg(spi_v3)] + /// Transmit and Receive audio data + FullDuplex, +} + impl<'d> I2S<'d> { - /// Note: Full-Duplex modes are not supported at this time - pub fn new( + /// Create a transmitter driver + pub fn new_txonly( peri: impl Peripheral

+ 'd, sd: impl Peripheral

> + 'd, ws: impl Peripheral

> + 'd, ck: impl Peripheral

> + 'd, mck: impl Peripheral

> + 'd, + txdma: impl Peripheral

> + 'd, + freq: Hertz, + config: Config, + ) -> Self { + into_ref!(sd); + Self::new_inner( + peri, + new_pin!(sd, AFType::OutputPushPull, Speed::VeryHigh), + None, + ws, + ck, + mck, + new_dma!(txdma), + None, + freq, + config, + Function::Transmit, + ) + } + + /// Create a receiver driver + pub fn new_rxonly( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + mck: impl Peripheral

> + 'd, #[cfg(not(spi_v3))] txdma: impl Peripheral

> + 'd, rxdma: impl Peripheral

> + 'd, freq: Hertz, config: Config, ) -> Self { - into_ref!(sd, ws, ck, mck); - - 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 mut spi_cfg = SpiConfig::default(); - spi_cfg.frequency = freq; - let spi = Spi::new_internal( + into_ref!(sd); + Self::new_inner( peri, + None, + new_pin!(sd, AFType::OutputPushPull, Speed::VeryHigh), + ws, + ck, + mck, #[cfg(not(spi_v3))] new_dma!(txdma), #[cfg(spi_v3)] None, new_dma!(rxdma), - spi_cfg, - ); + freq, + config, + #[cfg(not(spi_v3))] + Function::Transmit, + #[cfg(spi_v3)] + Function::Receive, + ) + } + + #[cfg(spi_v3)] + /// Create a full duplex transmitter driver + pub fn new_full_duplex( + peri: impl Peripheral

+ 'd, + txsd: impl Peripheral

> + 'd, + rxsd: 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!(txsd, rxsd); + Self::new_inner( + peri, + new_pin!(txsd, AFType::OutputPushPull, Speed::VeryHigh), + new_pin!(rxsd, AFType::OutputPushPull, Speed::VeryHigh), + ws, + ck, + mck, + new_dma!(txdma), + new_dma!(rxdma), + freq, + config, + Function::FullDuplex, + ) + } + + /// Write audio data. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + self._peri.read(data).await + } + + /// Write audio data. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + self._peri.write(data).await + } + + /// Transfer audio data. + pub async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { + self._peri.transfer(read, write).await + } + + /// Transfer audio data in place. + pub async fn transfer_in_place(&mut self, data: &mut [W]) -> Result<(), Error> { + self._peri.transfer_in_place(data).await + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + txsd: Option>, + rxsd: Option>, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + mck: impl Peripheral

> + 'd, + txdma: Option>, + rxdma: Option>, + freq: Hertz, + config: Config, + function: Function, + ) -> Self { + into_ref!(ws, ck, mck); + + ws.set_as_af(ws.af_num(), AFType::OutputPushPull); + ws.set_speed(Speed::VeryHigh); + + ck.set_as_af(ck.af_num(), AFType::OutputPushPull); + ck.set_speed(Speed::VeryHigh); + + mck.set_as_af(mck.af_num(), AFType::OutputPushPull); + mck.set_speed(Speed::VeryHigh); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = freq; + + let spi = Spi::new_internal(peri, txdma, rxdma, spi_cfg); + + let regs = T::info().regs; // TODO move i2s to the new mux infra. //#[cfg(all(rcc_f4, not(stm32f410)))] @@ -208,26 +316,23 @@ impl<'d> I2S<'d> { let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format); - #[cfg(any(spi_v1, spi_f1))] + #[cfg(any(spi_v1, spi_v3, spi_f1))] { + #[cfg(spi_v3)] + { + regs.cr1().modify(|w| w.set_spe(false)); + + reset_incompatible_bitfields::(); + } + 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. - - spi.info.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); - }); + // 1. Select the I2SDIV[7:0] bits in the SPI_I2SPR/SPI_I2SCFGR register to define the serial clock baud + // rate to reach the proper audio sample frequency. The ODD bit in the + // SPI_I2SPR/SPI_I2SCFGR register also has to be defined. // 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 + // MCKOE bit in the SPI_I2SPR/SPI_I2SCFGR 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). @@ -243,50 +348,72 @@ impl<'d> I2S<'d> { // 5. The I2SE bit in SPI_I2SCFGR register must be set. - spi.info.regs.i2scfgr().modify(|w| { + let clk_reg = { + #[cfg(any(spi_v1, spi_f1))] + { + regs.i2spr() + } + #[cfg(spi_v3)] + { + regs.i2scfgr() + } + }; + + clk_reg.modify(|w| { + w.set_i2sdiv(div); + w.set_odd(match odd { + true => Odd::ODD, + false => Odd::EVEN, + }); + + w.set_mckoe(config.master_clock); + }); + + 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) { + w.set_i2scfg(match (config.mode, function) { (Mode::Master, Function::Transmit) => I2scfg::MASTERTX, (Mode::Master, Function::Receive) => I2scfg::MASTERRX, + #[cfg(spi_v3)] + (Mode::Master, Function::FullDuplex) => I2scfg::MASTERFULLDUPLEX, (Mode::Slave, Function::Transmit) => I2scfg::SLAVETX, (Mode::Slave, Function::Receive) => I2scfg::SLAVERX, + #[cfg(spi_v3)] + (Mode::Slave, Function::FullDuplex) => I2scfg::SLAVEFULLDUPLEX, }); - w.set_i2se(true) + #[cfg(any(spi_v1, spi_f1))] + w.set_i2se(true); }); + + #[cfg(spi_v3)] + regs.cr1().modify(|w| w.set_spe(true)); } Self { _peri: spi, - sd: Some(sd.map_into()), + txsd: txsd.map(|w| w.map_into()), + rxsd: rxsd.map(|w| w.map_into()), ws: Some(ws.map_into()), ck: Some(ck.map_into()), mck: Some(mck.map_into()), } } - - /// Write audio data. - pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { - self._peri.write(data).await - } - - /// Read audio data. - pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { - self._peri.read(data).await - } } impl<'d> Drop for I2S<'d> { fn drop(&mut self) { - self.sd.as_ref().map(|x| x.set_as_disconnected()); + self.txsd.as_ref().map(|x| x.set_as_disconnected()); + self.rxsd.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()); @@ -328,3 +455,71 @@ fn compute_baud_rate(i2s_clock: Hertz, request_freq: Hertz, mclk: bool, data_for ((division & 1) == 1, (division >> 1) as u8) } } + +#[cfg(spi_v3)] +// The STM32H7 reference manual specifies that any incompatible bitfields should be reset +// to their reset values while operating in I2S mode. +fn reset_incompatible_bitfields() { + let regs = T::info().regs; + regs.cr1().modify(|w| { + let iolock = w.iolock(); + let csusp = w.csusp(); + let spe = w.cstart(); + let cstart = w.cstart(); + w.0 = 0; + w.set_iolock(iolock); + w.set_csusp(csusp); + w.set_spe(spe); + w.set_cstart(cstart); + }); + + regs.cr2().write(|w| w.0 = 0); + + regs.cfg1().modify(|w| { + let txdmaen = w.txdmaen(); + let rxdmaen = w.rxdmaen(); + let fthlv = w.fthlv(); + w.0 = 0; + w.set_txdmaen(txdmaen); + w.set_rxdmaen(rxdmaen); + w.set_fthlv(fthlv); + }); + + regs.cfg2().modify(|w| { + let afcntr = w.afcntr(); + let lsbfirst = w.lsbfirst(); + let ioswp = w.ioswp(); + w.0 = 0; + w.set_afcntr(afcntr); + w.set_lsbfirst(lsbfirst); + w.set_ioswp(ioswp); + }); + + regs.ier().modify(|w| { + let tifreie = w.tifreie(); + let ovrie = w.ovrie(); + let udrie = w.udrie(); + let txpie = w.txpie(); + let rxpie = w.rxpie(); + + w.0 = 0; + + w.set_tifreie(tifreie); + w.set_ovrie(ovrie); + w.set_udrie(udrie); + w.set_txpie(txpie); + w.set_rxpie(rxpie); + }); + + regs.ifcr().write(|w| { + w.set_suspc(true); + w.set_tifrec(true); + w.set_ovrc(true); + w.set_udrc(true); + }); + + regs.crcpoly().write(|w| w.0 = 0x107); + regs.txcrc().write(|w| w.0 = 0); + regs.rxcrc().write(|w| w.0 = 0); + regs.udrdr().write(|w| w.0 = 0); +} diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 81ee60c1c..990bde98a 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -83,7 +83,7 @@ pub mod hrtim; pub mod hsem; #[cfg(i2c)] pub mod i2c; -#[cfg(all(spi_v1, rcc_f4))] +#[cfg(any(all(spi_v1, rcc_f4), spi_v3))] pub mod i2s; #[cfg(stm32wb)] pub mod ipcc; diff --git a/examples/stm32f4/src/bin/i2s_dma.rs b/examples/stm32f4/src/bin/i2s_dma.rs index 97a04b2aa..27b165f1b 100644 --- a/examples/stm32f4/src/bin/i2s_dma.rs +++ b/examples/stm32f4/src/bin/i2s_dma.rs @@ -15,14 +15,13 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let mut i2s = I2S::new( + let mut i2s = I2S::new_txonly( 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(), );