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