From 7b298beabba001b6337734cdee0d75d99798e112 Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Sat, 2 Jan 2021 16:14:08 +0100 Subject: [PATCH 1/3] sai: HAL implementation for the serial audio interface (only F429 for now). Some bits are still missing: - Support for DMA - Implementation of the new embedded-hal I2S traits - Documentation about how to set the intermediate SAI clock - Support for other MCUs --- src/lib.rs | 2 + src/sai.rs | 900 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 902 insertions(+) create mode 100644 src/sai.rs 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/sai.rs b/src/sai.rs new file mode 100644 index 00000000..4e914289 --- /dev/null +++ b/src/sai.rs @@ -0,0 +1,900 @@ +//! 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 +//! +//! The following code configures the both sub-blocks of SAI for full-duplex communication using +//! I2S-encoded audio. +//! +//! ``` +//! // Initialize clocks. +//! let rcc = ctx.device.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().unwrap() == 172.mhz().into()); +//! assert!(clocks.saib_clk().unwrap() == 172.mhz().into()); +//! +//! let gpioe = ctx.device.GPIOE.split(); +//! // SAIB is made synchronous to A. +//! let (saia, saib) = ctx.device.SAI.split_sync_b(); +//! let protocol = Protocol { +//! sync: Synchronization::I2S, +//! word_size: 16, +//! slot_size: 16, +//! num_slots: 2, +//! }; +//! let tx = saia.master_tx( +//! ( +//! gpioe.pe2.into_alternate_af6(), +//! gpioe.pe4.into_alternate_af6(), +//! gpioe.pe5.into_alternate_af6(), +//! gpioe.pe6.into_alternate_af6(), +//! ), +//! protocol, +//! 48000.hz(), +//! clocks, +//! ); +//! let rx = saib.slave_rx(gpioe.pe3.into_alternate_af6(), 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 = ctx.device.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().unwrap() == 172.mhz().into()); +//! +//! let gpioe = ctx.device.GPIOE.split(); +//! let (saia, _) = ctx.device.SAI.split(); +//! let protocol = Protocol { +//! sync: Synchronization::PCMShortFrame, +//! word_size: 16, +//! slot_size: 16, +//! // Stereo audio, two slots per frame. +//! num_slots: 2, +//! }; +//! let tx = saia.master_tx( +//! ( +//! gpioe.pe2.into_alternate_af6(), +//! gpioe.pe4.into_alternate_af6(), +//! gpioe.pe5.into_alternate_af6(), +//! gpioe.pe6.into_alternate_af6(), +//! ), +//! protocol, +//! 48000.hz(), +//! clocks, +//! ); +//! tx.start(); +//! loop { +//! tx.try_send(0xaaaa, 0xf0f0).ok(); +//! } +//! ``` +//! +//! # Clock Selection +//! +//! TODO +//! + +// TODO: Unify capitalization of template parameters. +// TODO: Synchronization of multiple SAIs. + +use core::marker::PhantomData; +use core::ops::Deref; + +use crate::gpio::gpiod::PD6; +use crate::gpio::gpioe::{PE2, PE3, PE4, PE5, PE6}; +use crate::gpio::gpiof::{PF6, PF7, PF8, PF9}; +use crate::gpio::{Alternate, AF6}; +use crate::rcc::Clocks; +use crate::stm32::RCC; +#[cfg(not(feature = "stm32f446"))] +use crate::stm32::{sai, SAI}; +#[cfg(feature = "stm32f446")] +use crate::stm32::{SAI1, SAI2}; +use crate::time::Hertz; + +/// SAI A sub-block. +pub struct SAIA { + _sai: PhantomData, +} + +/// SAI B sub-block. +pub struct SAIB { + _sai: PhantomData, +} + +#[cfg(not(feature = "stm32f446"))] +pub type SAI1A = SAIA; +#[cfg(not(feature = "stm32f446"))] +pub type SAI1B = SAIB; +#[cfg(feature = "stm32f446")] +pub type SAI1A = SAIA; +#[cfg(feature = "stm32f446")] +pub type SAI1B = SAIB; +#[cfg(feature = "stm32f446")] +pub type SAI2A = SAIA; +#[cfg(feature = "stm32f446")] +pub type SAI2B = SAIB; + +/// Trait for master clock pins. +pub trait PinMck {} +/// Trait for frame select pins. +pub trait PinFs {} +/// Trait for bit clock pins. +pub trait PinSck {} +/// Trait for data pins. +pub trait PinSd {} + +/// Pins required for an asynchronous SAI master channel. +pub trait MasterPins {} + +impl MasterPins for (MCK, FS, SCK, SD) +where + MCK: PinMck, + FS: PinFs, + SCK: PinSck, + SD: PinSd, +{ +} + +/// Pins required for an asynchronous SAI slave channel. +pub trait SlavePins {} + +impl SlavePins for (MCK, FS, SCK, SD) +where + MCK: PinMck, + FS: PinFs, + SCK: PinSck, + SD: PinSd, +{ +} + +/// A filler type for when the MCK pin is unnecessary +pub struct NoMck; + +macro_rules! pins { + ($($CH:ty: MCK: [$($MCK:ty),*] FS: [$($FS:ty),*] SCK: [$($SCK:ty),*] SD: [$($SD:ty),*])+) => { + $( + $( + impl PinMck<$CH> for $MCK {} + )* + $( + impl PinFs<$CH> for $FS {} + )* + $( + impl PinSck<$CH> for $SCK{} + )* + $( + impl PinSd<$CH> for $SD{} + )* + )+ + } +} + +#[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", +))] +pins! { + SAI1A: + MCK: [ + NoMck, + PE2> + ] + FS: [ + PE4> + ] + SCK: [ + PE5> + ] + SD: [ + PD6>, + PE6> + ] + SAI1B: + MCK: [ + NoMck, + PF7> + ] + FS: [ + PF9> + ] + SCK: [ + PF8> + ] + SD: [ + PE3>, + PF6> + ] +} + +/// 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: u8, + /// 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: u8, + /// 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, +} + +/// 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: Pins, +} + +/// 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: Pins, +} + +/// 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: SD, +} + +/// 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; + +impl Deref for SAIA { + type Target = sai::CH; + + fn deref(&self) -> &Self::Target { + unsafe { &(*SAI::ptr()).cha } + } +} + +impl Deref for SAIB { + type Target = sai::CH; + + fn deref(&self) -> &Self::Target { + unsafe { &(*SAI::ptr()).chb } + } +} + +pub trait Channel { + 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: 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. +} + +impl Channel for Ch +where + Ch: Deref, +{ + 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: Clocks) { + let mclk = sample_freq.0 * 256; + // TODO: Use saib_clock for SAIB. + let sai_clock = clocks.saia_clk().expect("no SAI clock available").0; + if (sai_clock + (mclk >> 1)) / mclk == 1 { + // TODO: Typo in stm32f4 + self.cr1.modify(|_, w| unsafe { w.mcjdiv().bits(0) }); + } else { + let best_divider = (sai_clock + mclk) / (mclk << 1); + assert!(best_divider < 16); + self.cr1 + .modify(|_, w| unsafe { w.mcjdiv().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); + if protocol.slot_size < protocol.word_size { + panic!("slot size smaller than word size"); + } + self.cr1.modify(|_, w| { + match protocol.word_size { + 8 => w.ds().bit8(), + 10 => w.ds().bit10(), + 16 => w.ds().bit16(), + 20 => w.ds().bit20(), + 24 => w.ds().bit24(), + 32 => w.ds().bit32(), + _ => panic!("invalid word size"), + }; + if (pcm && tx) || (!pcm && !tx) { + w.ckstr().rising_edge(); + } else { + w.ckstr().falling_edge(); + } + w + }); + 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((protocol.slot_size * protocol.num_slots) - 1); + } + } else { + w.fspol().falling_edge(); + w.fsdef().set_bit(); + unsafe { + w.fsall().bits(protocol.slot_size - 1); + w.frl().bits((protocol.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); + } + if protocol.slot_size == protocol.word_size { + w.slotsz().data_size(); + } else if protocol.slot_size == 16 { + w.slotsz().bit16(); + } else if protocol.slot_size == 32 { + w.slotsz().bit32(); + } else { + panic!("invalid slot size"); + } + match protocol.sync { + Synchronization::LSBJustified => { + w.fboff().bits(protocol.slot_size - protocol.word_size) + } + _ => w.fboff().bits(0), + }; + w + }); + } + + fn start(&self) { + self.clrfr.modify(|_, 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). + self.sr.read().flvl().is_full() || self.sr.read().flvl().is_quarter4() + } + fn fifo_empty(&self) -> bool { + // We usually readat least two words (stereo data). + self.sr.read().flvl().is_empty() || self.sr.read().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, + config: Config, + 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 { + /// Splits the SAI instance into two asynchronous sub-blocks. + fn split( + self, + ) -> ( + SubBlock, Asynchronous, NoDir>, + SubBlock, Asynchronous, NoDir>, + ) + 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, NoDir>, + SubBlock, Asynchronous, NoDir>, + ) + 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, NoDir>, + SubBlock, Synchronous, NoDir>, + ) + where + Self: Sized; + + /*/// Un-splits the two sub-blocks and resets the SAI. + fn uninit(a: SubBlock, b: SubBlock) -> Self + where + Self: Sized;*/ + + /// Enables and resets the SAI instance. + fn reset(&mut self); +} + +impl SAIExt for SAI { + fn split( + mut self, + ) -> ( + SubBlock, + SubBlock, + ) + where + Self: Sized, + { + self.reset(); + ( + SubBlock { + channel: SAIA { _sai: PhantomData }, + config: Asynchronous, + direction: NoDir, + }, + SubBlock { + channel: SAIB { _sai: PhantomData }, + config: Asynchronous, + direction: NoDir, + }, + ) + } + + fn split_sync_a( + mut self, + ) -> ( + SubBlock, + SubBlock, + ) + where + Self: Sized, + { + self.reset(); + ( + SubBlock { + channel: SAIA { _sai: PhantomData }, + config: Synchronous, + direction: NoDir, + }, + SubBlock { + channel: SAIB { _sai: PhantomData }, + config: Asynchronous, + direction: NoDir, + }, + ) + } + + fn split_sync_b( + mut self, + ) -> ( + SubBlock, + SubBlock, + ) + where + Self: Sized, + { + self.reset(); + ( + SubBlock { + channel: SAIA { _sai: PhantomData }, + config: Asynchronous, + direction: NoDir, + }, + SubBlock { + channel: SAIB { _sai: PhantomData }, + config: Synchronous, + direction: NoDir, + }, + ) + } + + /*fn uninit(a: SubBlock, b: SubBlock) -> Self { + // TODO + }*/ + + fn reset(&mut self) { + let rcc = unsafe { &*RCC::ptr() }; + rcc.apb2enr.modify(|_, w| w.sai1en().set_bit()); + rcc.apb2rstr.modify(|_, w| w.sai1rst().set_bit()); + rcc.apb2rstr.modify(|_, w| w.sai1rst().clear_bit()); + } +} + +impl SubBlock +where + Ch: Channel, +{ + /// Configures the channel as a master and a receiver. + pub fn master_rx( + self, + pins: Pins, + protocol: Protocol, + sample_freq: F, + clocks: Clocks, + ) -> SubBlock, Receive> + where + Pins: MasterPins, + F: Into, + { + 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 { pins }, + direction: Receive, + } + } + + /// Configures the channel as a master and a receiver. + pub fn master_tx( + self, + pins: Pins, + protocol: Protocol, + sample_freq: F, + clocks: Clocks, + ) -> SubBlock, Transmit> + where + Pins: MasterPins, + F: Into, + { + 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 { pins }, + direction: Transmit, + } + } + + /// Configures the channel as a slave and a receiver. + pub fn slave_rx( + self, + pins: Pins, + protocol: Protocol, + ) -> SubBlock, Receive> + where + Pins: SlavePins, + { + self.channel.set_slave_rx(); + self.channel.set_protocol(protocol, false); + + SubBlock { + channel: self.channel, + config: AsyncSlave { pins }, + direction: Receive, + } + } + + /// Configures the channel as a master and a receiver. + pub fn slave_tx( + self, + pins: Pins, + protocol: Protocol, + ) -> SubBlock, Transmit> + where + Pins: SlavePins, + { + self.channel.set_slave_tx(); + self.channel.set_protocol(protocol, true); + + SubBlock { + channel: self.channel, + config: AsyncSlave { pins }, + direction: Transmit, + } + } +} + +impl SubBlock +where + Ch: Channel, +{ + /// Configures the channel as a slave and a receiver. + pub fn slave_rx(self, sd: SD, protocol: Protocol) -> SubBlock, Receive> + where + SD: PinSd, + { + self.channel.set_slave_rx(); + self.channel.set_protocol(protocol, false); + + SubBlock { + channel: self.channel, + config: SyncSlave { sd }, + direction: Receive, + } + } + + /// Configures the channel as a master and a receiver. + pub fn slave_tx(self, sd: SD, protocol: Protocol) -> SubBlock, Transmit> + where + SD: PinSd, + { + self.channel.set_slave_tx(); + self.channel.set_protocol(protocol, true); + + SubBlock { + channel: self.channel, + config: SyncSlave { 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. +} From 0836f84905657998dbbafc3d122fdf5176ece8f7 Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Sun, 10 Jan 2021 16:52:26 +0100 Subject: [PATCH 2/3] sai: Fix example. --- src/sai.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sai.rs b/src/sai.rs index 4e914289..8567f3c2 100644 --- a/src/sai.rs +++ b/src/sai.rs @@ -78,7 +78,7 @@ //! // Stereo audio, two slots per frame. //! num_slots: 2, //! }; -//! let tx = saia.master_tx( +//! let mut tx = saia.master_tx( //! ( //! gpioe.pe2.into_alternate_af6(), //! gpioe.pe4.into_alternate_af6(), From ca5a6bd5008c7001b858e8ffabb47484f85465e3 Mon Sep 17 00:00:00 2001 From: Andrey Zgarbul Date: Sat, 4 Mar 2023 13:01:43 +0300 Subject: [PATCH 3/3] rebase --- CHANGELOG.md | 2 + Cargo.toml | 4 + examples/sai-duplex.rs | 89 ++++++ src/dma/traits.rs | 7 +- src/dma/traits/f4.rs | 33 +- src/gpio/alt/f4.rs | 26 +- src/prelude.rs | 2 + src/rcc/f4/enable.rs | 7 +- src/sai.rs | 683 +++++++++++++++++++---------------------- 9 files changed, 437 insertions(+), 416 deletions(-) create mode 100644 examples/sai-duplex.rs 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/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 index 8567f3c2..f63c14af 100644 --- a/src/sai.rs +++ b/src/sai.rs @@ -9,91 +9,7 @@ //! //! # Usage Examples //! -//! The following code configures the both sub-blocks of SAI for full-duplex communication using -//! I2S-encoded audio. -//! -//! ``` -//! // Initialize clocks. -//! let rcc = ctx.device.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().unwrap() == 172.mhz().into()); -//! assert!(clocks.saib_clk().unwrap() == 172.mhz().into()); -//! -//! let gpioe = ctx.device.GPIOE.split(); -//! // SAIB is made synchronous to A. -//! let (saia, saib) = ctx.device.SAI.split_sync_b(); -//! let protocol = Protocol { -//! sync: Synchronization::I2S, -//! word_size: 16, -//! slot_size: 16, -//! num_slots: 2, -//! }; -//! let tx = saia.master_tx( -//! ( -//! gpioe.pe2.into_alternate_af6(), -//! gpioe.pe4.into_alternate_af6(), -//! gpioe.pe5.into_alternate_af6(), -//! gpioe.pe6.into_alternate_af6(), -//! ), -//! protocol, -//! 48000.hz(), -//! clocks, -//! ); -//! let rx = saib.slave_rx(gpioe.pe3.into_alternate_af6(), 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 = ctx.device.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().unwrap() == 172.mhz().into()); -//! -//! let gpioe = ctx.device.GPIOE.split(); -//! let (saia, _) = ctx.device.SAI.split(); -//! let protocol = Protocol { -//! sync: Synchronization::PCMShortFrame, -//! word_size: 16, -//! slot_size: 16, -//! // Stereo audio, two slots per frame. -//! num_slots: 2, -//! }; -//! let mut tx = saia.master_tx( -//! ( -//! gpioe.pe2.into_alternate_af6(), -//! gpioe.pe4.into_alternate_af6(), -//! gpioe.pe5.into_alternate_af6(), -//! gpioe.pe6.into_alternate_af6(), -//! ), -//! protocol, -//! 48000.hz(), -//! clocks, -//! ); -//! tx.start(); -//! loop { -//! tx.try_send(0xaaaa, 0xf0f0).ok(); -//! } -//! ``` +//! See examples/sai-duplex.rs //! //! # Clock Selection //! @@ -103,136 +19,43 @@ // TODO: Unify capitalization of template parameters. // TODO: Synchronization of multiple SAIs. -use core::marker::PhantomData; use core::ops::Deref; -use crate::gpio::gpiod::PD6; -use crate::gpio::gpioe::{PE2, PE3, PE4, PE5, PE6}; -use crate::gpio::gpiof::{PF6, PF7, PF8, PF9}; -use crate::gpio::{Alternate, AF6}; -use crate::rcc::Clocks; -use crate::stm32::RCC; -#[cfg(not(feature = "stm32f446"))] -use crate::stm32::{sai, SAI}; -#[cfg(feature = "stm32f446")] -use crate::stm32::{SAI1, SAI2}; +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; -/// SAI A sub-block. -pub struct SAIA { - _sai: PhantomData, -} - -/// SAI B sub-block. -pub struct SAIB { - _sai: PhantomData, -} - -#[cfg(not(feature = "stm32f446"))] -pub type SAI1A = SAIA; -#[cfg(not(feature = "stm32f446"))] -pub type SAI1B = SAIB; -#[cfg(feature = "stm32f446")] -pub type SAI1A = SAIA; -#[cfg(feature = "stm32f446")] -pub type SAI1B = SAIB; -#[cfg(feature = "stm32f446")] -pub type SAI2A = SAIA; -#[cfg(feature = "stm32f446")] -pub type SAI2B = SAIB; - -/// Trait for master clock pins. -pub trait PinMck {} -/// Trait for frame select pins. -pub trait PinFs {} -/// Trait for bit clock pins. -pub trait PinSck {} -/// Trait for data pins. -pub trait PinSd {} - -/// Pins required for an asynchronous SAI master channel. -pub trait MasterPins {} - -impl MasterPins for (MCK, FS, SCK, SD) -where - MCK: PinMck, - FS: PinFs, - SCK: PinSck, - SD: PinSd, -{ -} +pub use sai::ch::{cr1::DS as WordSize, slotr::SLOTSZ as SlotSize}; -/// Pins required for an asynchronous SAI slave channel. -pub trait SlavePins {} - -impl SlavePins for (MCK, FS, SCK, SD) -where - MCK: PinMck, - FS: PinFs, - SCK: PinSck, - SD: PinSd, -{ -} - -/// A filler type for when the MCK pin is unnecessary -pub struct NoMck; - -macro_rules! pins { - ($($CH:ty: MCK: [$($MCK:ty),*] FS: [$($FS:ty),*] SCK: [$($SCK:ty),*] SD: [$($SD:ty),*])+) => { - $( - $( - impl PinMck<$CH> for $MCK {} - )* - $( - impl PinFs<$CH> for $FS {} - )* - $( - impl PinSck<$CH> for $SCK{} - )* - $( - impl PinSd<$CH> for $SD{} - )* - )+ +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, } } -#[cfg(any( - feature = "stm32f427", - feature = "stm32f429", - feature = "stm32f437", - feature = "stm32f439", -))] -pins! { - SAI1A: - MCK: [ - NoMck, - PE2> - ] - FS: [ - PE4> - ] - SCK: [ - PE5> - ] - SD: [ - PD6>, - PE6> - ] - SAI1B: - MCK: [ - NoMck, - PF7> - ] - FS: [ - PF9> - ] - SCK: [ - PF8> - ] - SD: [ - PE3>, - PF6> - ] +fn slot_size(sz: SlotSize, ws: u8) -> u8 { + match sz { + SlotSize::DataSize => ws, + SlotSize::Bit16 => 16, + SlotSize::Bit32 => 32, + } } /// Serial audio protocol. @@ -243,7 +66,7 @@ pub struct Protocol { /// Number of bits filled with audio data. /// /// The only values allowed are 8, 10, 16, 20, 24, and 32. - pub word_size: u8, + 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 @@ -252,7 +75,7 @@ pub struct Protocol { /// 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: u8, + 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). @@ -292,23 +115,72 @@ pub enum Synchronization { 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 { +pub struct AsyncMaster { // TODO: Remove attribute when destroy function is implemented. #[allow(unused)] - pins: Pins, + 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 { +pub struct AsyncSlave { // TODO: Remove attribute when destroy function is implemented. #[allow(unused)] - pins: Pins, + 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. @@ -317,10 +189,16 @@ pub struct AsyncSlave { pub struct Synchronous; /// Synchronous SAI sub-block which as been configured as a slave. -pub struct SyncSlave { +pub struct SyncSlave { // TODO: Remove attribute when destroy function is implemented. #[allow(unused)] - sd: SD, + 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. @@ -332,31 +210,68 @@ pub struct Receive; /// SAI sub-block which has been configured as a transmitter. pub struct Transmit; -impl Deref for SAIA { - type Target = sai::CH; +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; +} - fn deref(&self) -> &Self::Target { - unsafe { &(*SAI::ptr()).cha } +impl SAICH { + fn new(sai: SAI) -> Self { + Self { sai } + } + fn new_steal() -> Self { + Self { + sai: unsafe { SAI::steal() }, + } } } -impl Deref for SAIB { +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 { - unsafe { &(*SAI::ptr()).chb } + self.ch() } } -pub trait Channel { +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: Clocks); + fn set_clock_gen(&self, sample_freq: Hertz, clocks: &rcc::Clocks); fn set_protocol(&self, protocol: Protocol, tx: bool); fn start(&self); @@ -374,43 +289,69 @@ pub trait Channel { // 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, + Ch: Deref + ChannelClocks, { fn set_master_tx(&self) { - self.cr1.modify(|_, w| w.mode().master_tx()); + self.cr1().modify(|_, w| w.mode().master_tx()); } fn set_master_rx(&self) { - self.cr1.modify(|_, w| w.mode().master_rx()); + self.cr1().modify(|_, w| w.mode().master_rx()); } fn set_slave_tx(&self) { - self.cr1.modify(|_, w| w.mode().slave_tx()); + self.cr1().modify(|_, w| w.mode().slave_tx()); } fn set_slave_rx(&self) { - self.cr1.modify(|_, w| w.mode().slave_rx()); + self.cr1().modify(|_, w| w.mode().slave_rx()); } fn is_slave(&self) -> bool { - let mode = self.cr1.read().mode(); + let mode = self.cr1().read().mode(); mode.is_slave_tx() || mode.is_slave_rx() } - fn set_clock_gen(&self, sample_freq: Hertz, clocks: Clocks) { - let mclk = sample_freq.0 * 256; - // TODO: Use saib_clock for SAIB. - let sai_clock = clocks.saia_clk().expect("no SAI clock available").0; + 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 { - // TODO: Typo in stm32f4 - self.cr1.modify(|_, w| unsafe { w.mcjdiv().bits(0) }); + 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.mcjdiv().bits(best_divider as u8) }); + self.cr1() + .modify(|_, w| unsafe { w.mckdiv().bits(best_divider as u8) }); } } @@ -428,27 +369,18 @@ where panic!("only stereo I2S supported"); } assert!(protocol.num_slots > 0); - if protocol.slot_size < protocol.word_size { - panic!("slot size smaller than word size"); - } - self.cr1.modify(|_, w| { - match protocol.word_size { - 8 => w.ds().bit8(), - 10 => w.ds().bit10(), - 16 => w.ds().bit16(), - 20 => w.ds().bit20(), - 24 => w.ds().bit24(), - 32 => w.ds().bit32(), - _ => panic!("invalid word size"), - }; + 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(); + w.ckstr().rising_edge() } else { - w.ckstr().falling_edge(); + w.ckstr().falling_edge() } - w }); - self.frcr.modify(|_, w| { + self.frcr().modify(|_, w| { match protocol.sync { Synchronization::PCMShortFrame => w.fsoff().before_first(), _ => w.fsoff().on_first(), @@ -463,19 +395,19 @@ where Synchronization::PCMLongFrame => w.fsall().bits(12), _ => unreachable!(), }; - w.frl().bits((protocol.slot_size * protocol.num_slots) - 1); + w.frl().bits((slot_size * protocol.num_slots) - 1); } } else { w.fspol().falling_edge(); w.fsdef().set_bit(); unsafe { - w.fsall().bits(protocol.slot_size - 1); - w.frl().bits((protocol.slot_size << 1) - 1); + w.fsall().bits(slot_size - 1); + w.frl().bits((slot_size << 1) - 1); } } w }); - self.slotr.modify(|_, w| unsafe { + self.slotr().modify(|_, w| unsafe { if pcm { w.sloten().bits((1 << protocol.num_slots as u32) - 1); w.nbslot().bits(protocol.num_slots - 1); @@ -483,19 +415,9 @@ where w.sloten().bits(0x3); w.nbslot().bits(1); } - if protocol.slot_size == protocol.word_size { - w.slotsz().data_size(); - } else if protocol.slot_size == 16 { - w.slotsz().bit16(); - } else if protocol.slot_size == 32 { - w.slotsz().bit32(); - } else { - panic!("invalid slot size"); - } + w.slotsz().variant(protocol.slot_size); match protocol.sync { - Synchronization::LSBJustified => { - w.fboff().bits(protocol.slot_size - protocol.word_size) - } + Synchronization::LSBJustified => w.fboff().bits(slot_size - word_size), _ => w.fboff().bits(0), }; w @@ -503,7 +425,7 @@ where } fn start(&self) { - self.clrfr.modify(|_, w| { + self.clrfr().write(|w| { w.clfsdet().set_bit(); w.cafsdet().set_bit(); w.ccnrdy().set_bit(); @@ -512,36 +434,40 @@ where w.covrudr().set_bit(); w }); - self.cr2.modify(|_, w| w.fflush().flush()); - self.cr1.modify(|_, w| w.saien().enabled()); + 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() {} + 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). - self.sr.read().flvl().is_full() || self.sr.read().flvl().is_quarter4() + 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). - self.sr.read().flvl().is_empty() || self.sr.read().flvl().is_quarter1() + // 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) }); + self.dr().write(|w| unsafe { w.data().bits(word) }); } fn read(&self) -> u32 { - self.dr.read().data().bits() + self.dr().read().data().bits() } } /// Wrapper for a single channel of the SAI and its configuration. -pub struct SubBlock { +pub struct SubBlock { channel: Channel, + #[allow(unused)] config: Config, + #[allow(unused)] direction: Direction, } @@ -549,13 +475,16 @@ pub struct SubBlock { /// /// 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 { +pub trait SAIExt +where + Self: Instance, +{ /// Splits the SAI instance into two asynchronous sub-blocks. fn split( self, ) -> ( - SubBlock, Asynchronous, NoDir>, - SubBlock, Asynchronous, NoDir>, + SubBlock, Asynchronous>, + SubBlock, Asynchronous>, ) where Self: Sized; @@ -565,8 +494,8 @@ pub trait SAIExt { fn split_sync_a( self, ) -> ( - SubBlock, Synchronous, NoDir>, - SubBlock, Asynchronous, NoDir>, + SubBlock, Synchronous>, + SubBlock, Asynchronous>, ) where Self: Sized; @@ -576,8 +505,8 @@ pub trait SAIExt { fn split_sync_b( self, ) -> ( - SubBlock, Asynchronous, NoDir>, - SubBlock, Synchronous, NoDir>, + SubBlock, Asynchronous>, + SubBlock, Synchronous>, ) where Self: Sized; @@ -586,30 +515,32 @@ pub trait SAIExt { fn uninit(a: SubBlock, b: SubBlock) -> Self where Self: Sized;*/ - - /// Enables and resets the SAI instance. - fn reset(&mut self); } -impl SAIExt for SAI { +impl SAIExt for SAI +where + SAI: Instance, +{ fn split( - mut self, + self, ) -> ( - SubBlock, - SubBlock, + SubBlock, Asynchronous>, + SubBlock, Asynchronous>, ) where Self: Sized, { - self.reset(); + let rcc = unsafe { &*RCC::ptr() }; + SAI::enable(rcc); + SAI::reset(rcc); ( SubBlock { - channel: SAIA { _sai: PhantomData }, + channel: SAIA::new(self), config: Asynchronous, direction: NoDir, }, SubBlock { - channel: SAIB { _sai: PhantomData }, + channel: SAIB::new_steal(), config: Asynchronous, direction: NoDir, }, @@ -617,23 +548,25 @@ impl SAIExt for SAI { } fn split_sync_a( - mut self, + self, ) -> ( - SubBlock, - SubBlock, + SubBlock, Synchronous>, + SubBlock, Asynchronous>, ) where Self: Sized, { - self.reset(); + let rcc = unsafe { &*RCC::ptr() }; + SAI::enable(rcc); + SAI::reset(rcc); ( SubBlock { - channel: SAIA { _sai: PhantomData }, + channel: SAIA::new(self), config: Synchronous, direction: NoDir, }, SubBlock { - channel: SAIB { _sai: PhantomData }, + channel: SAIB::new_steal(), config: Asynchronous, direction: NoDir, }, @@ -641,23 +574,25 @@ impl SAIExt for SAI { } fn split_sync_b( - mut self, + self, ) -> ( - SubBlock, - SubBlock, + SubBlock, Asynchronous>, + SubBlock, Synchronous>, ) where Self: Sized, { - self.reset(); + let rcc = unsafe { &*RCC::ptr() }; + SAI::enable(rcc); + SAI::reset(rcc); ( SubBlock { - channel: SAIA { _sai: PhantomData }, + channel: SAIA::new(self), config: Asynchronous, direction: NoDir, }, SubBlock { - channel: SAIB { _sai: PhantomData }, + channel: SAIB::new_steal(), config: Synchronous, direction: NoDir, }, @@ -667,134 +602,135 @@ impl SAIExt for SAI { /*fn uninit(a: SubBlock, b: SubBlock) -> Self { // TODO }*/ - - fn reset(&mut self) { - let rcc = unsafe { &*RCC::ptr() }; - rcc.apb2enr.modify(|_, w| w.sai1en().set_bit()); - rcc.apb2rstr.modify(|_, w| w.sai1rst().set_bit()); - rcc.apb2rstr.modify(|_, w| w.sai1rst().clear_bit()); - } } -impl SubBlock +impl SubBlock where - Ch: Channel, + Ch: Channel + SaiChannel, { /// Configures the channel as a master and a receiver. - pub fn master_rx( + pub fn master_rx( self, - pins: Pins, + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), protocol: Protocol, - sample_freq: F, - clocks: Clocks, - ) -> SubBlock, Receive> - where - Pins: MasterPins, - F: Into, - { + 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 { pins }, + config: AsyncMaster::new(pins), direction: Receive, } } - /// Configures the channel as a master and a receiver. - pub fn master_tx( + /// Configures the channel as a master and a transmitter. + pub fn master_tx( self, - pins: Pins, + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), protocol: Protocol, - sample_freq: F, - clocks: Clocks, - ) -> SubBlock, Transmit> - where - Pins: MasterPins, - F: Into, - { + 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 { pins }, + config: AsyncMaster::new(pins), direction: Transmit, } } /// Configures the channel as a slave and a receiver. - pub fn slave_rx( + pub fn slave_rx( self, - pins: Pins, + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), protocol: Protocol, - ) -> SubBlock, Receive> - where - Pins: SlavePins, - { + ) -> SubBlock, Receive> { self.channel.set_slave_rx(); self.channel.set_protocol(protocol, false); SubBlock { channel: self.channel, - config: AsyncSlave { pins }, + config: AsyncSlave::new(pins), direction: Receive, } } - /// Configures the channel as a master and a receiver. - pub fn slave_tx( + /// Configures the channel as a master and a transmitter. + pub fn slave_tx( self, - pins: Pins, + pins: ( + impl Into, + impl Into, + impl Into, + impl Into, + ), protocol: Protocol, - ) -> SubBlock, Transmit> - where - Pins: SlavePins, - { + ) -> SubBlock, Transmit> { self.channel.set_slave_tx(); self.channel.set_protocol(protocol, true); SubBlock { channel: self.channel, - config: AsyncSlave { pins }, + config: AsyncSlave::new(pins), direction: Transmit, } } } -impl SubBlock +impl SubBlock where - Ch: Channel, + Ch: Channel + SaiChannel, { /// Configures the channel as a slave and a receiver. - pub fn slave_rx(self, sd: SD, protocol: Protocol) -> SubBlock, Receive> - where - SD: PinSd, - { + 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 { sd }, + config: SyncSlave::new(sd), direction: Receive, } } - /// Configures the channel as a master and a receiver. - pub fn slave_tx(self, sd: SD, protocol: Protocol) -> SubBlock, Transmit> - where - SD: PinSd, - { + /// 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 { sd }, + config: SyncSlave::new(sd), direction: Transmit, } } @@ -898,3 +834,12 @@ where // 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; +}