diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f3eebac..157d2a36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Enable `sdio` for stm32f446 - port LTDC implementation and example from stm32f7xx-hal [#731] - IrDA mode for USARTs + - initial `SAI` support [#248] - initial `embedded-io` support [#725] ### Changed @@ -22,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Allow different lengths of buffers in hal_1 SpiBus impl [#566] - `steal` UART peripheral on `Rx::new` +[#248]: https://github.com/stm32-rs/stm32f4xx-hal/pull/248 [#566]: https://github.com/stm32-rs/stm32f4xx-hal/pull/566 [#706]: https://github.com/stm32-rs/stm32f4xx-hal/pull/706 [#731]: https://github.com/stm32-rs/stm32f4xx-hal/pull/731 diff --git a/Cargo.toml b/Cargo.toml index 79d7ce22..4d591e6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -693,6 +693,10 @@ required-features = ["stm32f411", "rtic1"] # stm32f411 name = "rtic-usb-cdc-echo" required-features = ["stm32f411", "rtic1", "otg-fs", "usb_fs"] # stm32f411 +[[example]] +name = "sai-duplex" +required-features = ["stm32f429"] + [[example]] name = "sd" required-features = ["gpiod", "sdio", "sdio-host"] # stm32f405 diff --git a/examples/sai-duplex.rs b/examples/sai-duplex.rs new file mode 100644 index 00000000..f8143dd1 --- /dev/null +++ b/examples/sai-duplex.rs @@ -0,0 +1,89 @@ +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +use stm32f4xx_hal as hal; + +use crate::hal::{ + pac, + prelude::*, + sai::{Duplex, Protocol, SlotSize, Synchronization, WordSize}, +}; +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + let p = pac::Peripherals::take().unwrap(); + + // The following code configures the both sub-blocks of SAI for full-duplex communication using + // I2S-encoded audio. + + // Initialize clocks. + let rcc = p.RCC.constrain(); + let clocks = rcc + .cfgr + .use_hse(8.MHz()) + .saia_clk(172.MHz()) + .saib_clk(172.MHz()) + .freeze(); + // Test that the SAI clock is suitable for 48000KHz audio. + assert!(clocks.saia_clk() == Some(172.MHz())); + assert!(clocks.saib_clk() == Some(172.MHz())); + + let gpioe = p.GPIOE.split(); + // SAIB is made synchronous to A. + let (saia, saib) = p.SAI.split_sync_b(); + let protocol = Protocol { + sync: Synchronization::I2S, + word_size: WordSize::Bit16, + slot_size: SlotSize::DataSize, + num_slots: 2, + }; + let tx = saia.master_tx( + (gpioe.pe2, gpioe.pe4, gpioe.pe5, gpioe.pe6), + protocol, + 48.kHz(), + &clocks, + ); + let rx = saib.slave_rx(gpioe.pe3, protocol); + + let mut duplex = Duplex::new(rx, tx); + duplex.start(); + loop { + duplex.try_send(0xaaaa, 0xf0f0).ok(); + let _input = duplex.try_read(); + } + + /* + // The following code configures the A sub-block of SAI as a master transmitter for PCM-encoded audio. + + // Initialize clocks. + let rcc = p.RCC.constrain(); + let clocks = rcc.cfgr.use_hse(8.MHz()).saia_clk(172.MHz()).freeze(); + // Test that the SAI clock is suitable for 48000KHz audio. + assert!(clocks.saia_clk() == Some(172.MHz())); + + let gpioe = p.GPIOE.split(); + let (saia, _) = p.SAI.split(); + let protocol = Protocol { + sync: Synchronization::PCMShortFrame, + word_size: WordSize::Bit16, + slot_size: SlotSize::DataSize, + // Stereo audio, two slots per frame. + num_slots: 2, + }; + let mut tx = saia.master_tx( + (gpioe.pe2, gpioe.pe4, gpioe.pe5, gpioe.pe6), + protocol, + 48.kHz(), + &clocks, + ); + tx.start(); + loop { + tx.try_send(0xaaaa, 0xf0f0).ok(); + } + */ +} diff --git a/src/dma/traits.rs b/src/dma/traits.rs index 96e9ef71..947d9348 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -355,12 +355,7 @@ pub struct FLT { impl crate::Sealed for FLT {} #[cfg(feature = "sai")] -pub struct SAICH { - _per: PhantomData, -} - -#[cfg(feature = "sai")] -impl crate::Sealed for SAICH {} +pub use crate::sai::SAICH; dma_map!( (Stream0:0, MemoryToMemory, [MemoryToMemory | MemoryToMemory | MemoryToMemory]), diff --git a/src/dma/traits/f4.rs b/src/dma/traits/f4.rs index 837d8443..9a0d639a 100644 --- a/src/dma/traits/f4.rs +++ b/src/dma/traits/f4.rs @@ -475,38 +475,19 @@ mod sai1 { use pac::SAI1; dma_map!( - (Stream1:0, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI1_A - (Stream3:0, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI1_A - (Stream4:1, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI1_B - (Stream5:0, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI1_B:DMA_CHANNEL_0 + (Stream1:0, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI1_A + (Stream3:0, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI1_A + (Stream4:1, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI1_B + (Stream5:0, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI1_B:DMA_CHANNEL_0 ); - - unsafe impl PeriAddress for SAICH { - #[inline(always)] - fn address(&self) -> u32 { - unsafe { (*SAI1::ptr()).ch(C as usize).dr().as_ptr() as u32 } - } - - type MemSize = u32; - } } #[cfg(feature = "sai2")] dma_map!( - (Stream4:3, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI2_A - (Stream6:3, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI2_B - (Stream7:0, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI2_B:DMA_CHANNEL_0 + (Stream4:3, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI2_A + (Stream6:3, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI2_B + (Stream7:0, SAICH, [MemoryToPeripheral | PeripheralToMemory]), //SAI2_B:DMA_CHANNEL_0 ); -#[cfg(feature = "sai2")] -unsafe impl PeriAddress for SAICH { - #[inline(always)] - fn address(&self) -> u32 { - unsafe { (*pac::SAI2::ptr()).ch(C as usize).dr().as_ptr() as u32 } - } - - type MemSize = u32; -} - #[cfg(feature = "spi6")] dma_map!( (Stream5:1, pac::SPI6, [MemoryToPeripheral]), //SPI6_TX diff --git a/src/gpio/alt/f4.rs b/src/gpio/alt/f4.rs index 230e1e58..601ae02a 100644 --- a/src/gpio/alt/f4.rs +++ b/src/gpio/alt/f4.rs @@ -2502,7 +2502,7 @@ pub mod sai1 { PF9<7>, ], - for [ + for no:NoPin, [ #[cfg(feature = "gpio-f413")] PA15<10>, @@ -2516,7 +2516,7 @@ pub mod sai1 { PG7<6>, ], - for [ + for no:NoPin, [ #[cfg(feature = "gpio-f446")] PC0<6>, @@ -2611,19 +2611,18 @@ pub mod sai1 { use crate::pac::SAI; #[cfg(any(feature = "stm32f427", feature = "stm32f437", feature = "gpio-f446"))] use crate::pac::SAI1 as SAI; - pub struct ChannelA; - pub struct ChannelB; + pub use crate::sai::{SAI1A, SAI1B}; impl SaiChannels for SAI { - type A = ChannelA; - type B = ChannelB; + type A = SAI1A; + type B = SAI1B; } - impl SaiChannel for ChannelA { + impl SaiChannel for SAI1A { type Fs = FsA; type Mclk = MclkA; type Sck = SckA; type Sd = SdA; } - impl SaiChannel for ChannelB { + impl SaiChannel for SAI1B { type Fs = FsB; type Mclk = MclkB; type Sck = SckB; @@ -2686,19 +2685,18 @@ pub mod sai2 { } use crate::pac::SAI2 as SAI; - pub struct ChannelA; - pub struct ChannelB; + pub use crate::sai::{SAI2A, SAI2B}; impl SaiChannels for SAI { - type A = ChannelA; - type B = ChannelB; + type A = SAI2A; + type B = SAI2B; } - impl SaiChannel for ChannelA { + impl SaiChannel for SAI2A { type Fs = FsA; type Mclk = MclkA; type Sck = SckA; type Sd = SdA; } - impl SaiChannel for ChannelB { + impl SaiChannel for SAI2B { type Fs = FsB; type Mclk = MclkB; type Sck = SckB; diff --git a/src/lib.rs b/src/lib.rs index 92ac13a9..691dee34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,8 @@ pub mod qei; pub mod qspi; pub mod rcc; pub mod rtc; +#[cfg(feature = "sai")] +pub mod sai; #[cfg(all(feature = "sdio-host", feature = "sdio"))] pub mod sdio; pub mod serial; diff --git a/src/prelude.rs b/src/prelude.rs index bc5e65ca..1e80849d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -68,6 +68,8 @@ pub use crate::qei::QeiExt as _stm32f4xx_hal_QeiExt; pub use crate::rcc::RccExt as _stm32f4xx_hal_rcc_RccExt; #[cfg(feature = "rng")] pub use crate::rng::RngExt as _stm32f4xx_hal_rng_RngExt; +#[cfg(feature = "sai")] +pub use crate::sai::SAIExt as _; pub use crate::serial::dma::SerialHandleIT as _stm32f4xx_hal_serial_dma_SerialHandleIT; pub use crate::serial::dma::SerialReadDMA as _stm32f4xx_hal_serial_dma_SerialReadDMA; pub use crate::serial::dma::SerialWriteDMA as _stm32f4xx_hal_serial_dma_SerialWriteDMA; diff --git a/src/rcc/f4/enable.rs b/src/rcc/f4/enable.rs index b8a6e3a7..146ae362 100644 --- a/src/rcc/f4/enable.rs +++ b/src/rcc/f4/enable.rs @@ -276,7 +276,12 @@ bus_lpenable!(ADC3 => 10); #[cfg(feature = "adc3")] bus_reset!(ADC3 => 8); -#[cfg(feature = "stm32f413")] +#[cfg(any( + feature = "gpio-f413", + feature = "gpio-f469", + feature = "stm32f429", + feature = "stm32f439" +))] bus! { SAI => (APB2, 22), } diff --git a/src/sai.rs b/src/sai.rs new file mode 100644 index 00000000..f63c14af --- /dev/null +++ b/src/sai.rs @@ -0,0 +1,845 @@ +//! Serial Audio Interface. +//! +//! Each SAI instance consists of two sub-blocks (SAIA and SAIB), each of which can be configured +//! completely independently. Optionally, one block can be configured to be synchronous to the +//! other, in which no clock and word select pins are required. +//! +//! This implementation supports PCM audio with short and long frame synchronization signals as +//! well as I2S and its left-justified and right-justified variations. +//! +//! # Usage Examples +//! +//! See examples/sai-duplex.rs +//! +//! # Clock Selection +//! +//! TODO +//! + +// TODO: Unify capitalization of template parameters. +// TODO: Synchronization of multiple SAIs. + +use core::ops::Deref; + +use crate::gpio::alt::SaiChannel; +use crate::pac::RCC; +#[cfg(feature = "sai2")] +use crate::pac::SAI2; +#[cfg(any( + feature = "gpio-f413", + feature = "gpio-f469", + feature = "stm32f429", + feature = "stm32f439" +))] +use crate::pac::{sai, SAI as SAI1}; +#[cfg(any(feature = "stm32f427", feature = "stm32f437", feature = "stm32f446"))] +use crate::pac::{sai1 as sai, SAI1}; +use crate::rcc; +use crate::time::Hertz; + +pub use sai::ch::{cr1::DS as WordSize, slotr::SLOTSZ as SlotSize}; + +fn word_size(ws: WordSize) -> u8 { + match ws { + WordSize::Bit8 => 8, + WordSize::Bit10 => 10, + WordSize::Bit16 => 16, + WordSize::Bit20 => 20, + WordSize::Bit24 => 24, + WordSize::Bit32 => 32, + } +} + +fn slot_size(sz: SlotSize, ws: u8) -> u8 { + match sz { + SlotSize::DataSize => ws, + SlotSize::Bit16 => 16, + SlotSize::Bit32 => 32, + } +} + +/// Serial audio protocol. +#[derive(Clone, Copy)] +pub struct Protocol { + /// Synchronization scheme. + pub sync: Synchronization, + /// Number of bits filled with audio data. + /// + /// The only values allowed are 8, 10, 16, 20, 24, and 32. + pub word_size: WordSize, + /// Number of bits transmitted per word. + /// + /// If a master clock is generated, the slot size should be a power of two if an integer ratio + /// between the master clock and the bit clock is required. + /// + /// If the value does not equal the word size, the only other values allowed are 16 and 32. In + /// any case, the value has to be equal or larger than the word size. If the slot size does not + /// match the word size, the data is padded according to the synchronization scheme. + pub slot_size: SlotSize, + /// Number of slots (i.e., audio channels) per frame. + /// + /// For everything but PCM audio, the value needs to be 2 (stereo). + pub num_slots: u8, +} + +/// Synchronization mode. +#[derive(Clone, Copy)] +pub enum Synchronization { + /// I2S standard synchronization. + /// + /// The frame-select signal is low during data for the left channel and high for the right + /// channel. The frame-select signal is activated one SCK cycle before the first bit for the + /// corresponding channel is available. + /// + /// Data is followed by zeros if the configured word size does not match the frame size. + I2S, + /// MSB-justified synchronization. + /// + /// Like I2S, but the frame-select signal is activated when the first bit for the corresponding + /// channel is available. + MSBJustified, + /// LSB-justified synchronization. + /// + /// Like I2S, but the frame-select signal is activated when the first bit for the corresponding + /// channel is available, and the leading bits are set to zero if the word size does not match + /// the frame size. + LSBJustified, + /// PCM data with short-frame synchronization. + /// + /// The frame-select signal is asserted for one bit before the start of the data. + PCMShortFrame, + /// PCM data with long-frame synchronization. + /// + /// The frame-select signal is asserted at the same time as the start of the data and is held + /// high for 13 bits. + PCMLongFrame, +} + +/// SAI sub-block. +pub struct SAICH { + sai: SAI, +} + +impl SAICH { + fn ch(&self) -> &sai::CH { + self.sai.ch(usize::from(C)) + } +} + +/// SAI A sub-block. +pub type SAIA = SAICH; + +/// SAI B sub-block. +pub type SAIB = SAICH; + +impl crate::Sealed for SAIA {} + +/// Asynchronous SAI sub-block which has not yet been configured. +/// +/// Asynchronous means that the sub-block has its own set of clock pins. +pub struct Asynchronous; + +/// Asynchronous SAI sub-block which as been configured as a master. +pub struct AsyncMaster { + // TODO: Remove attribute when destroy function is implemented. + #[allow(unused)] + pins: (Ch::Mclk, Ch::Fs, Ch::Sck, Ch::Sd), +} + +impl AsyncMaster { + fn new( + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), + ) -> Self { + Self { + pins: (pins.0.into(), pins.1.into(), pins.2.into(), pins.3.into()), + } + } +} + +/// Asynchronous SAI sub-block which as been configured as a slave. +pub struct AsyncSlave { + // TODO: Remove attribute when destroy function is implemented. + #[allow(unused)] + pins: (Ch::Mclk, Ch::Fs, Ch::Sck, Ch::Sd), +} + +impl AsyncSlave { + fn new( + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), + ) -> Self { + Self { + pins: (pins.0.into(), pins.1.into(), pins.2.into(), pins.3.into()), + } + } +} + +/// Synchronous SAI sub-block. +/// +/// Synchronous sub-blocks are always configured as slaves. +pub struct Synchronous; + +/// Synchronous SAI sub-block which as been configured as a slave. +pub struct SyncSlave { + // TODO: Remove attribute when destroy function is implemented. + #[allow(unused)] + sd: Ch::Sd, +} + +impl SyncSlave { + fn new(sd: impl Into) -> Self { + Self { sd: sd.into() } + } +} + +/// SAI sub-block which has neither been configured as a receiver nor as a transmitter. +pub struct NoDir; + +/// SAI sub-block which has been configured as a receiver. +pub struct Receive; + +/// SAI sub-block which has been configured as a transmitter. +pub struct Transmit; + +pub trait Instance: + crate::Sealed + Deref + rcc::Enable + rcc::Reset + rcc::BusClock +{ + #[doc(hidden)] + fn ptr() -> *const sai::RegisterBlock; + #[doc(hidden)] + unsafe fn steal() -> Self; +} + +impl SAICH { + fn new(sai: SAI) -> Self { + Self { sai } + } + fn new_steal() -> Self { + Self { + sai: unsafe { SAI::steal() }, + } + } +} + +macro_rules! sai_impl { + ($SAI:ty, $sai:ident, $SAIA:ident, $SAIB:ident) => { + pub type $SAIA = SAIA<$SAI>; + pub type $SAIB = SAIB<$SAI>; + impl Instance for $SAI { + fn ptr() -> *const sai::RegisterBlock { + <$SAI>::ptr() + } + unsafe fn steal() -> Self { + <$SAI>::steal() + } + } + }; +} + +sai_impl!(SAI1, sai1, SAI1A, SAI1B); +#[cfg(feature = "sai2")] +sai_impl!(SAI2, sai2, SAI2A, SAI2B); + +impl Deref for SAICH +where + SAI: Instance, +{ + type Target = sai::CH; + + fn deref(&self) -> &Self::Target { + self.ch() + } +} + +pub trait ChannelClocks { + fn get_clk_frequency(clocks: &rcc::Clocks) -> Option; +} + +pub trait Channel: ChannelClocks { + fn set_master_tx(&self); + fn set_master_rx(&self); + fn set_slave_tx(&self); + fn set_slave_rx(&self); + + fn is_slave(&self) -> bool; + fn set_clock_gen(&self, sample_freq: Hertz, clocks: &rcc::Clocks); + fn set_protocol(&self, protocol: Protocol, tx: bool); + + fn start(&self); + fn stop(&self); + + fn fifo_full(&self) -> bool; + fn fifo_empty(&self) -> bool; + + fn write(&self, word: u32); + fn read(&self) -> u32; + + // TODO: DMA information? + // TODO: Interrupt bits? + // TODO: SAIA is on Channel 0, Stream 1 and 3. + // TODO: SAIB is on Channel 0, Stream 5 and Channel 1, Stream 4. +} + +#[cfg(any(feature = "gpio-f413", feature = "gpio-f427", feature = "gpio-f469"))] +impl ChannelClocks for SAICH { + fn get_clk_frequency(clocks: &rcc::Clocks) -> Option { + if C { + clocks.saib_clk() + } else { + clocks.saia_clk() + } + } +} + +#[cfg(feature = "gpio-f446")] +impl ChannelClocks for SAICH { + fn get_clk_frequency(clocks: &rcc::Clocks) -> Option { + clocks.sai1_clk() + } +} + +#[cfg(feature = "sai2")] +#[cfg(feature = "gpio-f446")] +impl ChannelClocks for SAICH { + fn get_clk_frequency(clocks: &rcc::Clocks) -> Option { + clocks.sai2_clk() + } +} + +impl Channel for Ch +where + Ch: Deref + ChannelClocks, +{ + fn set_master_tx(&self) { + self.cr1().modify(|_, w| w.mode().master_tx()); + } + + fn set_master_rx(&self) { + self.cr1().modify(|_, w| w.mode().master_rx()); + } + + fn set_slave_tx(&self) { + self.cr1().modify(|_, w| w.mode().slave_tx()); + } + + fn set_slave_rx(&self) { + self.cr1().modify(|_, w| w.mode().slave_rx()); + } + + fn is_slave(&self) -> bool { + let mode = self.cr1().read().mode(); + mode.is_slave_tx() || mode.is_slave_rx() + } + + fn set_clock_gen(&self, sample_freq: Hertz, clocks: &rcc::Clocks) { + let mclk = sample_freq.raw() * 256; + let sai_clock = Self::get_clk_frequency(clocks) + .expect("no SAI clock available") + .raw(); + if (sai_clock + (mclk >> 1)) / mclk == 1 { + self.cr1().modify(|_, w| unsafe { w.mckdiv().bits(0) }); + } else { + let best_divider = (sai_clock + mclk) / (mclk << 1); + assert!(best_divider < 16); + self.cr1() + .modify(|_, w| unsafe { w.mckdiv().bits(best_divider as u8) }); + } + } + + fn set_protocol(&self, protocol: Protocol, tx: bool) { + // According to the reference manual (I2S section), PCM has an inverted bit clock by + // default. Other sources sometimes disagree. + let pcm = match protocol.sync { + Synchronization::I2S => false, + Synchronization::MSBJustified => false, + Synchronization::LSBJustified => false, + Synchronization::PCMLongFrame => true, + Synchronization::PCMShortFrame => true, + }; + if !pcm && protocol.num_slots != 2 { + panic!("only stereo I2S supported"); + } + assert!(protocol.num_slots > 0); + let word_size = word_size(protocol.word_size); + let slot_size = slot_size(protocol.slot_size, word_size); + assert!(slot_size >= word_size, "slot size smaller than word size"); + self.cr1().modify(|_, w| { + w.ds().variant(protocol.word_size); + if (pcm && tx) || (!pcm && !tx) { + w.ckstr().rising_edge() + } else { + w.ckstr().falling_edge() + } + }); + self.frcr().modify(|_, w| { + match protocol.sync { + Synchronization::PCMShortFrame => w.fsoff().before_first(), + _ => w.fsoff().on_first(), + }; + if pcm { + w.fspol().rising_edge(); + w.fsdef().clear_bit(); + unsafe { + // The long frame sync signal is fixed and has a length of 13 bits. + match protocol.sync { + Synchronization::PCMShortFrame => w.fsall().bits(0), + Synchronization::PCMLongFrame => w.fsall().bits(12), + _ => unreachable!(), + }; + w.frl().bits((slot_size * protocol.num_slots) - 1); + } + } else { + w.fspol().falling_edge(); + w.fsdef().set_bit(); + unsafe { + w.fsall().bits(slot_size - 1); + w.frl().bits((slot_size << 1) - 1); + } + } + w + }); + self.slotr().modify(|_, w| unsafe { + if pcm { + w.sloten().bits((1 << protocol.num_slots as u32) - 1); + w.nbslot().bits(protocol.num_slots - 1); + } else { + w.sloten().bits(0x3); + w.nbslot().bits(1); + } + w.slotsz().variant(protocol.slot_size); + match protocol.sync { + Synchronization::LSBJustified => w.fboff().bits(slot_size - word_size), + _ => w.fboff().bits(0), + }; + w + }); + } + + fn start(&self) { + self.clrfr().write(|w| { + w.clfsdet().set_bit(); + w.cafsdet().set_bit(); + w.ccnrdy().set_bit(); + w.cwckcfg().set_bit(); + w.cmutedet().set_bit(); + w.covrudr().set_bit(); + w + }); + self.cr2().modify(|_, w| w.fflush().flush()); + self.cr1().modify(|_, w| w.saien().enabled()); + } + + fn stop(&self) { + self.cr1().modify(|_, w| w.saien().disabled()); + while self.cr1().read().saien().bit_is_set() {} + } + + fn fifo_full(&self) -> bool { + // We usually write at least two words (stereo data). + let sr = self.sr().read(); + sr.flvl().is_full() || sr.flvl().is_quarter4() + } + fn fifo_empty(&self) -> bool { + // We usually readat least two words (stereo data).(stereo data). + let sr = self.sr().read(); + sr.flvl().is_empty() || sr.flvl().is_quarter1() + } + + fn write(&self, word: u32) { + self.dr().write(|w| unsafe { w.data().bits(word) }); + } + fn read(&self) -> u32 { + self.dr().read().data().bits() + } +} + +/// Wrapper for a single channel of the SAI and its configuration. +pub struct SubBlock { + channel: Channel, + #[allow(unused)] + config: Config, + #[allow(unused)] + direction: Direction, +} + +/// Functions to configure the two sub-blocks of an SAI instance. +/// +/// For the two sub-blocks of a single SAI instance, only specific combinations of modes are valid. +/// This trait has one method for each such combination. +pub trait SAIExt +where + Self: Instance, +{ + /// Splits the SAI instance into two asynchronous sub-blocks. + fn split( + self, + ) -> ( + SubBlock, Asynchronous>, + SubBlock, Asynchronous>, + ) + where + Self: Sized; + + /// Splits the SAI instance so that the A block uses the synchronization signals of the B + /// block. + fn split_sync_a( + self, + ) -> ( + SubBlock, Synchronous>, + SubBlock, Asynchronous>, + ) + where + Self: Sized; + + /// Splits the SAI instance so that the B block uses the synchronization signals of the A + /// block. + fn split_sync_b( + self, + ) -> ( + SubBlock, Asynchronous>, + SubBlock, Synchronous>, + ) + where + Self: Sized; + + /*/// Un-splits the two sub-blocks and resets the SAI. + fn uninit(a: SubBlock, b: SubBlock) -> Self + where + Self: Sized;*/ +} + +impl SAIExt for SAI +where + SAI: Instance, +{ + fn split( + self, + ) -> ( + SubBlock, Asynchronous>, + SubBlock, Asynchronous>, + ) + where + Self: Sized, + { + let rcc = unsafe { &*RCC::ptr() }; + SAI::enable(rcc); + SAI::reset(rcc); + ( + SubBlock { + channel: SAIA::new(self), + config: Asynchronous, + direction: NoDir, + }, + SubBlock { + channel: SAIB::new_steal(), + config: Asynchronous, + direction: NoDir, + }, + ) + } + + fn split_sync_a( + self, + ) -> ( + SubBlock, Synchronous>, + SubBlock, Asynchronous>, + ) + where + Self: Sized, + { + let rcc = unsafe { &*RCC::ptr() }; + SAI::enable(rcc); + SAI::reset(rcc); + ( + SubBlock { + channel: SAIA::new(self), + config: Synchronous, + direction: NoDir, + }, + SubBlock { + channel: SAIB::new_steal(), + config: Asynchronous, + direction: NoDir, + }, + ) + } + + fn split_sync_b( + self, + ) -> ( + SubBlock, Asynchronous>, + SubBlock, Synchronous>, + ) + where + Self: Sized, + { + let rcc = unsafe { &*RCC::ptr() }; + SAI::enable(rcc); + SAI::reset(rcc); + ( + SubBlock { + channel: SAIA::new(self), + config: Asynchronous, + direction: NoDir, + }, + SubBlock { + channel: SAIB::new_steal(), + config: Synchronous, + direction: NoDir, + }, + ) + } + + /*fn uninit(a: SubBlock, b: SubBlock) -> Self { + // TODO + }*/ +} + +impl SubBlock +where + Ch: Channel + SaiChannel, +{ + /// Configures the channel as a master and a receiver. + pub fn master_rx( + self, + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), + protocol: Protocol, + sample_freq: impl Into, + clocks: &rcc::Clocks, + ) -> SubBlock, Receive> { + self.channel.set_clock_gen(sample_freq.into(), clocks); + self.channel.set_master_rx(); + self.channel.set_protocol(protocol, false); + + SubBlock { + channel: self.channel, + config: AsyncMaster::new(pins), + direction: Receive, + } + } + + /// Configures the channel as a master and a transmitter. + pub fn master_tx( + self, + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), + protocol: Protocol, + sample_freq: impl Into, + clocks: &rcc::Clocks, + ) -> SubBlock, Transmit> { + self.channel.set_clock_gen(sample_freq.into(), clocks); + self.channel.set_master_tx(); + self.channel.set_protocol(protocol, true); + + SubBlock { + channel: self.channel, + config: AsyncMaster::new(pins), + direction: Transmit, + } + } + + /// Configures the channel as a slave and a receiver. + pub fn slave_rx( + self, + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), + protocol: Protocol, + ) -> SubBlock, Receive> { + self.channel.set_slave_rx(); + self.channel.set_protocol(protocol, false); + + SubBlock { + channel: self.channel, + config: AsyncSlave::new(pins), + direction: Receive, + } + } + + /// Configures the channel as a master and a transmitter. + pub fn slave_tx( + self, + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), + protocol: Protocol, + ) -> SubBlock, Transmit> { + self.channel.set_slave_tx(); + self.channel.set_protocol(protocol, true); + + SubBlock { + channel: self.channel, + config: AsyncSlave::new(pins), + direction: Transmit, + } + } +} + +impl SubBlock +where + Ch: Channel + SaiChannel, +{ + /// Configures the channel as a slave and a receiver. + pub fn slave_rx( + self, + sd: impl Into, + protocol: Protocol, + ) -> SubBlock, Receive> { + self.channel.set_slave_rx(); + self.channel.set_protocol(protocol, false); + + SubBlock { + channel: self.channel, + config: SyncSlave::new(sd), + direction: Receive, + } + } + + /// Configures the channel as a slave and a transmitter. + pub fn slave_tx( + self, + sd: impl Into, + protocol: Protocol, + ) -> SubBlock, Transmit> { + self.channel.set_slave_tx(); + self.channel.set_protocol(protocol, true); + + SubBlock { + channel: self.channel, + config: SyncSlave::new(sd), + direction: Transmit, + } + } +} + +impl SubBlock +where + Ch: Channel, +{ + pub fn start(&mut self) { + self.channel.start(); + } + + pub fn stop(&mut self) { + self.channel.stop(); + } + + // TODO: Mono functions, DMA. + + pub fn try_read(&mut self) -> nb::Result<(u32, u32), ()> { + if !self.channel.fifo_empty() { + // Note that fifo_empty() actually checks for at least two words of data. + Ok((self.channel.read(), self.channel.read())) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +impl SubBlock +where + Ch: Channel, +{ + pub fn start(&mut self) { + self.channel.start(); + } + + pub fn stop(&mut self) { + self.channel.stop(); + } + + // TODO: Mono functions, DMA. + + pub fn try_send(&mut self, left: u32, right: u32) -> nb::Result<(), ()> { + if !self.channel.fifo_full() { + // Note that fifo_full) actually checks for at least two words of space. + self.channel.write(left); + self.channel.write(right); + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +/// Wrapper around `Receive` and `Transmit` blocks to provide full-duplex I2S transfers. +pub struct Duplex { + rx: SubBlock, + tx: SubBlock, +} + +impl Duplex +where + Ch1: Channel, + Ch2: Channel, +{ + /// Wraps the specified receiver/transmitter objects. + pub fn new(rx: SubBlock, tx: SubBlock) -> Self { + Self { rx, tx } + } + + pub fn start(&mut self) { + // When the two channels are synchronized (internally or externally), we need to start the + // slave first. + if self.rx.channel.is_slave() { + self.rx.start(); + } + if self.tx.channel.is_slave() { + self.tx.start(); + } + if !self.rx.channel.is_slave() { + self.rx.start(); + } + if !self.tx.channel.is_slave() { + self.tx.start(); + } + } + + pub fn stop(&mut self) { + self.rx.stop(); + self.tx.stop(); + } + + pub fn try_read(&mut self) -> nb::Result<(u32, u32), ()> { + self.rx.try_read() + } + + pub fn try_send(&mut self, left: u32, right: u32) -> nb::Result<(), ()> { + self.tx.try_send(left, right) + } + + // TODO: Implement embedded-hal I2S traits for Duplex. +} + +unsafe impl crate::dma::traits::PeriAddress for SAICH { + #[inline(always)] + fn address(&self) -> u32 { + self.ch().dr().as_ptr() as u32 + } + + type MemSize = u32; +}