stm32f4xx_hal/
i2s.rs

1//! I2S (inter-IC Sound) communication using SPI peripherals
2//!
3//! This module is only available if the `i2s` feature is enabled.
4//!
5//! Note: while F413 and F423 have full duplex i2s capability, this mode is not yet availalble for
6//! these chips because their `I2S2EXT` and `I2S3EXT` peripherals are missing from their package
7//! access crate.
8
9use crate::gpio::{self, PinSpeed, Speed};
10use crate::pac;
11#[allow(unused)]
12use crate::rcc::{self, Clocks, Rcc, Reset};
13use fugit::HertzU32 as Hertz;
14
15#[cfg(feature = "i2s")]
16pub extern crate stm32_i2s_v12x;
17
18// I2S pins are mostly the same as the corresponding SPI pins:
19// MOSI -> SD
20// NSS -> WS (the current SPI code doesn't define NSS pins)
21// SCK -> CK
22// The master clock output is separate.
23
24/// Trait for SPI peripheral with i2s capability.
25pub trait Instance:
26    I2sFreq + rcc::Enable + rcc::Reset + gpio::alt::I2sCommon + gpio::alt::I2sMaster
27{
28}
29
30/// Trait for SPI peripheral that have an extension for full duplex i2s capability.
31pub trait DualInstance: Instance + gpio::alt::I2sExtPin {
32    /// The I2SEXT peripheral that extend the SPI peripheral
33    type I2sExtPeripheral;
34}
35
36/// Trait to get I2s frequency at SPI peripheral input.
37pub trait I2sFreq {
38    fn try_i2s_freq(clocks: &Clocks) -> Option<Hertz>;
39    fn i2s_freq(clocks: &Clocks) -> Hertz {
40        Self::try_i2s_freq(clocks).expect("I2S clock input for SPI not enabled")
41    }
42}
43
44impl<T> I2sFreq for T
45where
46    T: rcc::RccBus,
47    T::Bus: I2sFreq,
48{
49    fn try_i2s_freq(clocks: &Clocks) -> Option<Hertz> {
50        T::Bus::try_i2s_freq(clocks)
51    }
52}
53
54impl I2sFreq for rcc::APB1 {
55    fn try_i2s_freq(clocks: &Clocks) -> Option<Hertz> {
56        #[cfg(not(feature = "rcc_i2s_apb"))]
57        {
58            clocks.i2s_clk()
59        }
60        #[cfg(feature = "rcc_i2s_apb")]
61        {
62            clocks.i2s_apb1_clk()
63        }
64    }
65}
66
67impl I2sFreq for rcc::APB2 {
68    fn try_i2s_freq(clocks: &Clocks) -> Option<Hertz> {
69        #[cfg(not(feature = "rcc_i2s_apb"))]
70        {
71            clocks.i2s_clk()
72        }
73        #[cfg(feature = "rcc_i2s_apb")]
74        {
75            clocks.i2s_apb2_clk()
76        }
77    }
78}
79
80/// Trait to build an [`I2s`] object from SPI peripheral, pins and clocks
81pub trait I2sExt: Sized + Instance {
82    #[allow(non_upper_case_globals)]
83    const NoMck: Option<Self::Mck> = None;
84    fn i2s(
85        self,
86        pins: (
87            impl Into<Self::Ws>,
88            impl Into<Self::Ck>,
89            Option<impl Into<Self::Mck>>,
90            impl Into<Self::Sd>,
91        ),
92        rcc: &mut Rcc,
93    ) -> I2s<Self>;
94}
95
96impl<SPI: Instance> I2sExt for SPI {
97    fn i2s(
98        self,
99        pins: (
100            impl Into<Self::Ws>,
101            impl Into<Self::Ck>,
102            Option<impl Into<Self::Mck>>,
103            impl Into<Self::Sd>,
104        ),
105        rcc: &mut Rcc,
106    ) -> I2s<Self> {
107        I2s::new(self, pins, rcc)
108    }
109}
110
111/// Trait to build an [`DualI2s`] object from SPI peripheral, a I2SEXT peripheral, pins and clocks
112pub trait DualI2sExt: Sized + DualInstance {
113    fn dual_i2s(
114        self,
115        i2s_ext: Self::I2sExtPeripheral,
116        pins: (
117            impl Into<Self::Ws>,
118            impl Into<Self::Ck>,
119            Option<impl Into<Self::Mck>>,
120            impl Into<Self::Sd>,
121            impl Into<Self::ExtSd>,
122        ),
123        rcc: &mut Rcc,
124    ) -> DualI2s<Self>;
125}
126
127impl<SPI: DualInstance> DualI2sExt for SPI {
128    fn dual_i2s(
129        self,
130        i2s_ext: Self::I2sExtPeripheral,
131        pins: (
132            impl Into<Self::Ws>,
133            impl Into<Self::Ck>,
134            Option<impl Into<Self::Mck>>,
135            impl Into<Self::Sd>,
136            impl Into<Self::ExtSd>,
137        ),
138        rcc: &mut Rcc,
139    ) -> DualI2s<Self> {
140        DualI2s::new(self, i2s_ext, pins, rcc)
141    }
142}
143
144/// An I2s wrapper around an SPI object and pins
145pub struct I2s<I: Instance> {
146    spi: I,
147    pins: (I::Ws, I::Ck, Option<I::Mck>, I::Sd),
148    /// Frequency of clock input to this peripheral from the I2S PLL or related source
149    input_clock: Hertz,
150}
151
152// Note: for API documenting reason, it's better to keep `(WS, CK, MCLK, SD)` for ctor and dtor
153// than replacing by `PINS`
154impl<SPI: Instance> I2s<SPI> {
155    /// Creates an I2s object around an SPI peripheral and pins
156    ///
157    /// This function enables and resets the SPI peripheral, but does not configure it.
158    ///
159    /// The returned I2s object implements `stm32_i2s_v12x::I2sPeripheral`, so it can be used to
160    /// configure the peripheral and communicate.
161    ///
162    /// # Panics
163    ///
164    /// This function panics if the I2S clock input (from the I2S PLL or similar)
165    /// is not configured.
166    pub fn new(
167        spi: SPI,
168        pins: (
169            impl Into<SPI::Ws>,
170            impl Into<SPI::Ck>,
171            Option<impl Into<SPI::Mck>>,
172            impl Into<SPI::Sd>,
173        ),
174        rcc: &mut Rcc,
175    ) -> Self {
176        let input_clock = SPI::i2s_freq(&rcc.clocks);
177        // Enable clock, enable reset, clear, reset
178        SPI::enable(rcc);
179        SPI::reset(rcc);
180
181        let pins = (
182            pins.0.into(),
183            // Workaround for corrupted last bit of data issue, see stm32f411 errata
184            pins.1.into().speed(Speed::VeryHigh),
185            pins.2.map(Into::into),
186            pins.3.into(),
187        );
188
189        I2s {
190            spi,
191            pins,
192            input_clock,
193        }
194    }
195
196    #[allow(clippy::type_complexity)]
197    pub fn release(self) -> (SPI, (SPI::Ws, SPI::Ck, Option<SPI::Mck>, SPI::Sd)) {
198        (self.spi, self.pins)
199    }
200}
201
202impl<SPI: Instance> I2s<SPI> {
203    pub fn ws_pin(&self) -> &SPI::Ws {
204        &self.pins.0
205    }
206    pub fn ws_pin_mut(&mut self) -> &mut SPI::Ws {
207        &mut self.pins.0
208    }
209}
210
211impl<I: Instance> I2s<I> {
212    /// Returns the frequency of the clock signal that the SPI peripheral is receiving from the
213    /// I2S PLL or similar source
214    pub fn input_clock(&self) -> Hertz {
215        self.input_clock
216    }
217}
218
219/// Implements stm32_i2s_v12x::I2sPeripheral for I2s<$SPI> and creates an I2s::$spix function
220/// to create and enable the peripheral
221///
222/// $SPI: The fully-capitalized name of the SPI peripheral from pac module (example: SPI1)
223/// $I2s: The CamelCase I2S alias name for hal I2s wrapper (example: I2s1).
224/// $i2s: module containing the Ws pin definition. (example: i2s1).
225macro_rules! i2s {
226    ($SPI:ty, $I2s:ident, $i2s:ident) => {
227        pub type $I2s = I2s<$SPI>;
228
229        impl Instance for $SPI {}
230
231        #[cfg(feature = "i2s")]
232        impl stm32_i2s_v12x::WsPin for gpio::alt::$i2s::Ws {
233            fn is_high(&self) -> bool {
234                use crate::gpio::ReadPin;
235                <Self as ReadPin>::is_high(self)
236            }
237            fn is_low(&self) -> bool {
238                use crate::gpio::ReadPin;
239                <Self as ReadPin>::is_low(self)
240            }
241        }
242
243        #[cfg(feature = "i2s")]
244        unsafe impl stm32_i2s_v12x::I2sPeripheral for I2s<$SPI>
245        where
246            $SPI: rcc::Reset,
247        {
248            type WsPin = gpio::alt::$i2s::Ws;
249            const REGISTERS: *const () = <$SPI>::ptr() as *const _;
250            fn i2s_freq(&self) -> u32 {
251                self.input_clock.raw()
252            }
253            fn ws_pin(&self) -> &Self::WsPin {
254                self.ws_pin()
255            }
256            fn ws_pin_mut(&mut self) -> &mut Self::WsPin {
257                self.ws_pin_mut()
258            }
259            fn rcc_reset(&mut self) {
260                unsafe {
261                    <$SPI>::reset_unchecked();
262                }
263            }
264        }
265    };
266}
267
268#[cfg(any(
269    feature = "gpio-f410",
270    feature = "gpio-f411",
271    feature = "gpio-f412",
272    feature = "gpio-f413",
273    feature = "gpio-f446"
274))]
275i2s!(pac::SPI1, I2s1, i2s1);
276
277i2s!(pac::SPI2, I2s2, i2s2);
278
279#[cfg(feature = "spi3")]
280i2s!(pac::SPI3, I2s3, i2s3);
281
282#[cfg(feature = "spi4")]
283i2s!(pac::SPI4, I2s4, i2s4);
284
285#[cfg(feature = "spi5")]
286i2s!(pac::SPI5, I2s5, i2s5);
287
288/// A wrapper around a SPI and a I2SEXT object and pins for full duplex I2S operation
289#[allow(clippy::type_complexity)]
290pub struct DualI2s<I: DualInstance> {
291    spi: I,
292    i2s_ext: I::I2sExtPeripheral,
293    pins: (I::Ws, I::Ck, Option<I::Mck>, I::Sd, I::ExtSd),
294    /// Frequency of clock input to this peripheral from the I2S PLL or related source
295    input_clock: Hertz,
296}
297
298impl<SPI: DualInstance> DualI2s<SPI> {
299    /// Creates an DualI2s object around a SPI peripheral, it's I2SEXT extension, and pins
300    ///
301    /// This function enables and resets the SPI and I2SEXT peripheral, but does not configure it.
302    ///
303    /// The returned DualI2s object implements `stm32_i2s_v12x::DualI2sPeripheral`, so it can be used to
304    /// configure the peripheral and communicate.
305    ///
306    /// # Panics
307    ///
308    /// This function panics if the I2S clock input (from the I2S PLL or similar)
309    /// is not configured.
310    pub fn new(
311        spi: SPI,
312        i2s_ext: SPI::I2sExtPeripheral,
313        pins: (
314            impl Into<SPI::Ws>,
315            impl Into<SPI::Ck>,
316            Option<impl Into<SPI::Mck>>,
317            impl Into<SPI::Sd>,
318            impl Into<SPI::ExtSd>,
319        ),
320        rcc: &mut Rcc,
321    ) -> Self {
322        let input_clock = SPI::i2s_freq(&rcc.clocks);
323        // Enable clock, enable reset, clear, reset
324        // Note: this also affect the I2SEXT peripheral
325        SPI::enable(rcc);
326        SPI::reset(rcc);
327
328        let pins = (
329            pins.0.into(),
330            // Workaround for corrupted last bit of data issue, see stm32f411 errata
331            pins.1.into().speed(Speed::VeryHigh),
332            pins.2.map(Into::into),
333            pins.3.into(),
334            pins.4.into(),
335        );
336
337        Self {
338            spi,
339            i2s_ext,
340            pins,
341            input_clock,
342        }
343    }
344
345    #[allow(clippy::type_complexity)]
346    pub fn release(
347        self,
348    ) -> (
349        SPI,
350        SPI::I2sExtPeripheral,
351        (SPI::Ws, SPI::Ck, Option<SPI::Mck>, SPI::Sd, SPI::ExtSd),
352    ) {
353        (self.spi, self.i2s_ext, self.pins)
354    }
355}
356
357impl<SPI: DualInstance> DualI2s<SPI> {
358    pub fn ws_pin(&self) -> &SPI::Ws {
359        &self.pins.0
360    }
361    pub fn ws_pin_mut(&mut self) -> &mut SPI::Ws {
362        &mut self.pins.0
363    }
364}
365
366impl<I: DualInstance> DualI2s<I> {
367    /// Returns the frequency of the clock signal that the SPI peripheral is receiving from the
368    /// I2S PLL or similar source
369    pub fn input_clock(&self) -> Hertz {
370        self.input_clock
371    }
372}
373
374/// Implements stm32_i2s_v12x::DualI2sPeripheral for DualI2s<$SPI>
375///
376/// $SPI: The fully-capitalized name of the SPI peripheral from pac module (example: SPI1)
377/// $I2SEXT: The fully-capitalized name of the I2SEXT peripheral from pac module (example: I2S3EXT)
378/// $DualI2s: The CamelCase I2S alias name for hal I2s wrapper (example: DualI2s1).
379/// $i2s: module containing the Ws pin definition. (example: i2s1).
380/// $clock: The name of the Clocks function that returns the frequency of the I2S clock input
381/// to this SPI peripheral (i2s_cl, i2s_apb1_clk, or i2s2_apb_clk)
382#[cfg(any(
383    feature = "gpio-f401",
384    feature = "gpio-f411",
385    feature = "gpio-f412",
386    feature = "gpio-f417",
387    feature = "gpio-f427",
388    feature = "gpio-f469",
389))]
390macro_rules! dual_i2s {
391    ($SPI:ty,$I2SEXT:ty, $DualI2s:ident, $i2s:ident, $clock:ident) => {
392        pub type $DualI2s = DualI2s<$SPI>;
393
394        impl DualInstance for $SPI {
395            type I2sExtPeripheral = $I2SEXT;
396        }
397
398        #[cfg(feature = "i2s")]
399        unsafe impl stm32_i2s_v12x::DualI2sPeripheral for DualI2s<$SPI>
400        where
401            $SPI: rcc::Reset,
402        {
403            type WsPin = gpio::alt::$i2s::Ws;
404            const MAIN_REGISTERS: *const () = <$SPI>::ptr() as *const _;
405            const EXT_REGISTERS: *const () = <$I2SEXT>::ptr() as *const _;
406            fn i2s_freq(&self) -> u32 {
407                self.input_clock.raw()
408            }
409            fn ws_pin(&self) -> &Self::WsPin {
410                self.ws_pin()
411            }
412            fn ws_pin_mut(&mut self) -> &mut Self::WsPin {
413                self.ws_pin_mut()
414            }
415            fn rcc_reset(&mut self) {
416                unsafe {
417                    <$SPI>::reset_unchecked();
418                }
419            }
420        }
421    };
422}
423
424// Actually define objects for dual i2s
425// Each one has to be split into two declarations because the F412, F413, F423, and F446
426// have two different I2S clocks while other models have only one.
427// All STM32F4 models except STM32F410 and STM32F446 have dual i2s support on SPI2 and SPI3
428#[cfg(any(
429    feature = "gpio-f401",
430    feature = "gpio-f411",
431    feature = "gpio-f417",
432    feature = "gpio-f427",
433    feature = "gpio-f469",
434))]
435dual_i2s!(pac::SPI2, pac::I2S2EXT, DualI2s2, i2s2, i2s_clk);
436
437// add "gpio-f413" feature here when missing I2SEXT in pac wil be fixed.
438#[cfg(feature = "gpio-f412")]
439dual_i2s!(pac::SPI2, pac::I2S2EXT, DualI2s2, i2s2, i2s_apb1_clk);
440
441#[cfg(any(
442    feature = "gpio-f401",
443    feature = "gpio-f411",
444    feature = "gpio-f417",
445    feature = "gpio-f427",
446    feature = "gpio-f469",
447))]
448dual_i2s!(pac::SPI3, pac::I2S3EXT, DualI2s3, i2s3, i2s_clk);
449
450// add "gpio-f413" feature here when missing I2SEXT in pac wil be fixed.
451#[cfg(feature = "gpio-f412")]
452dual_i2s!(pac::SPI3, pac::I2S3EXT, DualI2s3, i2s3, i2s_apb1_clk);
453
454// DMA support: reuse existing mappings for SPI
455#[cfg(feature = "i2s")]
456mod dma {
457    use super::*;
458    use crate::dma::traits::{DMASet, PeriAddress};
459    use crate::pac::spi1::RegisterBlock;
460    use core::marker::PhantomData;
461    use core::ops::Deref;
462    use stm32_i2s_v12x::driver::{I2sCore, I2sDriver};
463    use stm32_i2s_v12x::transfer::{Ext, Main};
464    use stm32_i2s_v12x::DualI2sPeripheral;
465
466    /// I2S DMA reads from and writes to the data register
467    unsafe impl<SPI: Instance, MS, TR, STD> PeriAddress for I2sDriver<I2s<SPI>, MS, TR, STD>
468    where
469        I2s<SPI>: stm32_i2s_v12x::I2sPeripheral,
470        SPI: Deref<Target = crate::pac::spi1::RegisterBlock>,
471    {
472        /// SPI_DR is only 16 bits. Multiple transfers are needed for a 24-bit or 32-bit sample,
473        /// as explained in the reference manual.
474        type MemSize = u16;
475
476        fn address(&self) -> u32 {
477            self.data_register_address()
478        }
479    }
480
481    /// DMA is available for I2S based on the underlying implementations for SPI
482    unsafe impl<SPI: Instance, MS, TR, STD, STREAM, const CHANNEL: u8, DIR>
483        DMASet<STREAM, CHANNEL, DIR> for I2sDriver<I2s<SPI>, MS, TR, STD>
484    where
485        SPI: DMASet<STREAM, CHANNEL, DIR>,
486    {
487    }
488
489    pub trait DualI2sDmaTargetExt<I, PART, MS, DIR, STD> {
490        fn dma_target(&self) -> DualI2sDmaTarget<I, PART, MS, DIR, STD>;
491    }
492    impl<I, PART, MS, DIR, STD> DualI2sDmaTargetExt<I, PART, MS, DIR, STD>
493        for I2sCore<I, PART, MS, DIR, STD>
494    {
495        fn dma_target(&self) -> DualI2sDmaTarget<I, PART, MS, DIR, STD> {
496            DualI2sDmaTarget {
497                _dual_i2s_peripheral: PhantomData,
498                _part: PhantomData,
499                _ms: PhantomData,
500                _dir: PhantomData,
501                _std: PhantomData,
502            }
503        }
504    }
505
506    ///  - `I`: The [DualI2sPeripheral] controlled by the I2sCore.
507    ///  - `PART`: `Main` or `Ext`. The part of [DualI2sPeripheral] controlled by I2sCore.
508    ///  - `MS`: `Master` or `Slave`. The role of the I2sCore. Only a `Main` I2sCore can be Master.
509    ///  - `DIR` : `Transmit` or `Receive`. Communication direction.
510    ///  - `STD`: I2S standard, eg `Philips`
511    pub struct DualI2sDmaTarget<I, PART, MS, DIR, STD> {
512        _dual_i2s_peripheral: PhantomData<I>,
513        _part: PhantomData<PART>,
514        _ms: PhantomData<MS>,
515        _dir: PhantomData<DIR>,
516        _std: PhantomData<STD>,
517    }
518
519    macro_rules! dual_dma {
520        ($ext: ty, $reg: ident) => {
521            /// I2S DMA reads from and writes to the data register
522            unsafe impl<SPIext: DualInstance, MS, TR, STD> PeriAddress
523                for DualI2sDmaTarget<DualI2s<SPIext>, $ext, MS, TR, STD>
524            where
525                DualI2s<SPIext>: DualI2sPeripheral,
526            {
527                /// SPI_DR is only 16 bits. Multiple transfers are needed for a 24-bit or 32-bit sample,
528                /// as explained in the reference manual.
529                type MemSize = u16;
530
531                fn address(&self) -> u32 {
532                    let reg = unsafe { &*(DualI2s::$reg as *const RegisterBlock) };
533                    reg.dr().as_ptr() as u32
534                }
535            }
536        };
537    }
538
539    dual_dma!(Main, MAIN_REGISTERS);
540    dual_dma!(Ext, EXT_REGISTERS);
541
542    /// DMA is available for I2S based on the underlying implementations for SPI
543    unsafe impl<SPIext: DualInstance, PART, MS, TR, STD, STREAM, const CHANNEL: u8, DIR>
544        DMASet<STREAM, CHANNEL, DIR> for DualI2sDmaTarget<DualI2s<SPIext>, PART, MS, TR, STD>
545    where
546        SPIext: DMASet<STREAM, CHANNEL, DIR>,
547    {
548    }
549}
550
551#[cfg(feature = "i2s")]
552pub use dma::{DualI2sDmaTarget, DualI2sDmaTargetExt};