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