Skip to main content

ariel_os_stm32/spi/main/
mod.rs

1//! Provides support for the SPI communication bus in main mode.
2
3#![expect(unsafe_code)]
4
5use portable_atomic::{AtomicBool, Ordering};
6
7use ariel_os_embassy_common::{
8    impl_async_spibus_for_driver_enum,
9    spi::{BitOrder, Mode, main::Kilohertz},
10};
11use embassy_embedded_hal::adapter::{BlockingAsync, YieldingAsync};
12use embassy_stm32::{
13    gpio,
14    mode::Blocking,
15    peripherals,
16    spi::{MisoPin, MosiPin, SckPin, Spi as InnerSpi},
17    time::Hertz,
18};
19
20// TODO: we could consider making this `pub`
21// NOTE(hal): values from the datasheets.
22// When peripherals support different frequencies, the smallest one is used.
23#[cfg(context = "stm32c031c6")]
24const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(24);
25#[cfg(any(context = "stm32f303cb", context = "stm32f303re"))]
26const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(18);
27#[cfg(context = "stm32f401re")]
28const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(21);
29#[cfg(context = "stm32f411re")]
30const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(25);
31#[cfg(context = "stm32g431rb")]
32const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(75);
33#[cfg(any(context = "stm32h755zi", context = "stm32h753zi"))]
34const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(150);
35#[cfg(context = "stm32l475vg")]
36const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(40);
37#[cfg(any(context = "stm32u073kc", context = "stm32u083mc"))]
38const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(32);
39// TODO: verify, datasheet says "Baud rate prescaler up to kernel frequency/2 or bypass from RCC in
40// master mode", core freq is 160MHz
41#[cfg(context = "stm32u585ai")]
42const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(80);
43#[cfg(context = "stm32wb55rg")]
44const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(32);
45
46/// SPI bus configuration.
47#[derive(Clone)]
48#[non_exhaustive]
49pub struct Config {
50    /// The frequency at which the bus should operate.
51    pub frequency: Frequency,
52    /// The SPI mode to use.
53    pub mode: Mode,
54    #[doc(hidden)]
55    pub bit_order: BitOrder,
56}
57
58impl Default for Config {
59    fn default() -> Self {
60        Self {
61            frequency: Frequency::F(Kilohertz::MHz(1)),
62            mode: Mode::Mode0,
63            bit_order: BitOrder::default(),
64        }
65    }
66}
67
68/// SPI bus frequency.
69#[derive(Debug, Copy, Clone, PartialEq, Eq)]
70#[cfg_attr(feature = "defmt", derive(defmt::Format))]
71#[repr(u32)]
72pub enum Frequency {
73    /// Arbitrary frequency.
74    F(Kilohertz),
75}
76
77impl From<Frequency> for Hertz {
78    fn from(freq: Frequency) -> Self {
79        match freq {
80            Frequency::F(kilohertz) => Hertz::khz(kilohertz.to_kHz()),
81        }
82    }
83}
84
85ariel_os_embassy_common::impl_spi_from_frequency!();
86ariel_os_embassy_common::impl_spi_frequency_const_functions!(MAX_FREQUENCY);
87
88macro_rules! define_spi_drivers {
89    ($( $interrupt:ident => $peripheral:ident ),* $(,)?) => {
90        $(
91            /// Peripheral-specific SPI driver.
92            pub struct $peripheral {
93                spim: YieldingAsync<BlockingAsync<InnerSpi<'static, Blocking>>>,
94            }
95
96            // Ensure this peripheral has only one active Instance.
97            paste::paste! {
98                static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
99            }
100
101            impl $peripheral {
102                /// Returns a driver implementing [`embedded_hal_async::spi::SpiBus`] for this SPI
103                /// peripheral.
104                #[expect(clippy::new_ret_no_self)]
105                #[must_use]
106                pub fn new<
107                    SCK: SckPin<peripherals::$peripheral>,
108                    MISO: MisoPin<peripherals::$peripheral>,
109                    MOSI: MosiPin<peripherals::$peripheral>,
110                >(
111                    sck_pin: impl $crate::IntoPeripheral<'static, SCK>,
112                    miso_pin: impl $crate::IntoPeripheral<'static, MISO>,
113                    mosi_pin: impl $crate::IntoPeripheral<'static, MOSI>,
114                    config: Config,
115                ) -> Spi {
116                    // Make this struct a compile-time-enforced singleton: having multiple statics
117                    // defined with the same name would result in a compile-time error.
118                    paste::paste! {
119                        #[allow(dead_code)]
120                        static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
121                    }
122
123                    let mut spi_config = embassy_stm32::spi::Config::default();
124                    spi_config.frequency = config.frequency.into();
125                    spi_config.mode = crate::spi::from_mode(config.mode);
126                    spi_config.bit_order = crate::spi::from_bit_order(config.bit_order);
127                    spi_config.miso_pull = gpio::Pull::None;
128
129                    // Check if we can initialize this peripheral (check if the value was previously false, set it to true).
130                    paste::paste! {
131                        if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
132                            panic!("SPI peripheral already initialized")
133                        }
134                    }
135
136                    // FIXME(safety): enforce that the init code indeed has run
137                    // SAFETY: We check with an AtomicBool that only one instance of this peripheral
138                    // is active at once.
139                    let spim_peripheral = unsafe { peripherals::$peripheral::steal() };
140
141                    // The order of MOSI/MISO pins is inverted.
142                    let spim = InnerSpi::new_blocking(
143                        spim_peripheral,
144                        sck_pin.into_hal_peripheral(),
145                        mosi_pin.into_hal_peripheral(),
146                        miso_pin.into_hal_peripheral(),
147                        spi_config,
148                    );
149
150                    Spi::$peripheral(Self { spim: YieldingAsync::new(BlockingAsync::new(spim)) })
151                }
152            }
153
154            impl Drop for $peripheral {
155                fn drop(&mut self) {
156                    paste::paste! {
157                        [< ACTIVE_ $peripheral >].store(false, Ordering::Release);
158                    }
159                }
160            }
161        )*
162
163        /// Peripheral-agnostic driver.
164        pub enum Spi {
165            $(
166                #[doc = concat!(stringify!($peripheral), " peripheral.")]
167                $peripheral($peripheral)
168            ),*
169        }
170
171        impl embedded_hal_async::spi::ErrorType for Spi {
172            type Error = embassy_stm32::spi::Error;
173        }
174
175        impl_async_spibus_for_driver_enum!(Spi, $( $peripheral ),*);
176    };
177}
178
179// Define a driver per peripheral
180#[cfg(context = "stm32c031c6")]
181define_spi_drivers!(
182   SPI1 => SPI1,
183);
184#[cfg(context = "stm32f303cb")]
185define_spi_drivers!(
186   SPI1 => SPI1,
187   SPI2 => SPI2,
188   SPI3 => SPI3,
189);
190#[cfg(context = "stm32f303re")]
191define_spi_drivers!(
192   SPI1 => SPI1,
193   SPI2 => SPI2,
194   SPI3 => SPI3,
195   // TODO: the MCU has a fourth SPI peripheral, but it does not seem supported by Embassy.
196);
197#[cfg(context = "stm32f401re")]
198define_spi_drivers!(
199   SPI1 => SPI1,
200   SPI2 => SPI2,
201   SPI3 => SPI3,
202);
203#[cfg(context = "stm32f411re")]
204define_spi_drivers!(
205   SPI1 => SPI1,
206   SPI2 => SPI2,
207   SPI3 => SPI3,
208   SPI4 => SPI4,
209   SPI5 => SPI5,
210);
211#[cfg(context = "stm32g431rb")]
212define_spi_drivers!(
213   SPI1 => SPI1,
214   SPI2 => SPI2,
215   SPI3 => SPI3,
216);
217#[cfg(any(context = "stm32h755zi", context = "stm32h753zi"))]
218define_spi_drivers!(
219   SPI1 => SPI1,
220   SPI2 => SPI2,
221   SPI3 => SPI3,
222   SPI4 => SPI4,
223   SPI5 => SPI5,
224   SPI6 => SPI6,
225);
226#[cfg(context = "stm32l475vg")]
227define_spi_drivers!(
228   SPI1 => SPI1,
229   SPI2 => SPI2,
230   SPI3 => SPI3,
231);
232#[cfg(any(context = "stm32u073kc", context = "stm32u083mc"))]
233define_spi_drivers!(
234   SPI1 => SPI1,
235   // FIXME: the other two SPI peripherals share the same interrupt
236);
237#[cfg(context = "stm32u585ai")]
238define_spi_drivers!(
239   SPI1 => SPI1,
240   SPI2 => SPI2,
241   SPI3 => SPI3,
242);
243#[cfg(context = "stm32wb55rg")]
244define_spi_drivers!(
245   SPI1 => SPI1,
246   SPI2 => SPI2,
247);