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