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