Skip to main content

ariel_os_rp/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::{Mode, main::Kilohertz},
10};
11use embassy_embedded_hal::adapter::{BlockingAsync, YieldingAsync};
12use embassy_rp::{
13    peripherals,
14    spi::{Blocking, ClkPin, MisoPin, MosiPin, Spi as InnerSpi},
15};
16
17// TODO: we could consider making this `pub`
18// NOTE(hal): values from the datasheets.
19#[cfg(context = "rp2040")]
20const MAX_FREQUENCY: Kilohertz = Kilohertz::kHz(62_500);
21#[cfg(context = "rp235xa")]
22const MAX_FREQUENCY: Kilohertz = Kilohertz::kHz(70_500);
23
24/// SPI bus configuration.
25#[derive(Clone)]
26#[non_exhaustive]
27pub struct Config {
28    /// The frequency at which the bus should operate.
29    pub frequency: Frequency,
30    /// The SPI mode to use.
31    pub mode: Mode,
32}
33
34impl Default for Config {
35    fn default() -> Self {
36        Self {
37            frequency: Frequency::F(Kilohertz::MHz(1)),
38            mode: Mode::Mode0,
39        }
40    }
41}
42
43/// SPI bus frequency.
44#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46#[repr(u32)]
47pub enum Frequency {
48    /// Arbitrary frequency.
49    F(Kilohertz),
50}
51
52ariel_os_embassy_common::impl_spi_from_frequency!();
53ariel_os_embassy_common::impl_spi_frequency_const_functions!(MAX_FREQUENCY);
54
55impl Frequency {
56    fn as_hz(self) -> u32 {
57        match self {
58            Self::F(kilohertz) => kilohertz.to_Hz(),
59        }
60    }
61}
62
63macro_rules! define_spi_drivers {
64    ($( $peripheral:ident ),* $(,)?) => {
65        $(
66            /// Peripheral-specific SPI driver.
67            pub struct $peripheral {
68                spim: YieldingAsync<BlockingAsync<InnerSpi<'static, peripherals::$peripheral, Blocking>>>,
69            }
70
71            // Ensure this peripheral has only one active Instance.
72            paste::paste! {
73                static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
74            }
75
76            impl $peripheral {
77                /// Returns a driver implementing [`embedded_hal_async::spi::SpiBus`] for this SPI
78                /// peripheral.
79                #[expect(clippy::new_ret_no_self)]
80                #[must_use]
81                pub fn new<
82                    SCK: ClkPin<peripherals::$peripheral>,
83                    MISO: MisoPin<peripherals::$peripheral>,
84                    MOSI: MosiPin<peripherals::$peripheral>
85                >(
86                    sck_pin: impl $crate::IntoPeripheral<'static, SCK>,
87                    miso_pin: impl $crate::IntoPeripheral<'static, MISO>,
88                    mosi_pin: impl $crate::IntoPeripheral<'static, MOSI>,
89                    config: Config,
90                ) -> Spi {
91                    // Make this struct a compile-time-enforced singleton: having multiple statics
92                    // defined with the same name would result in a compile-time error.
93                    paste::paste! {
94                        #[allow(dead_code)]
95                        static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
96                    }
97
98                    let (pol, phase) = crate::spi::from_mode(config.mode);
99
100                    let mut spi_config = embassy_rp::spi::Config::default();
101                    spi_config.frequency = config.frequency.as_hz();
102                    spi_config.polarity = pol;
103                    spi_config.phase = phase;
104
105                    // Check if we can initialize this peripheral (check if the value was previously false, set it to true).
106                    paste::paste! {
107                        if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
108                            panic!("SPI peripheral already initialized")
109                        }
110                    }
111
112                    // FIXME(safety): enforce that the init code indeed has run
113                    // SAFETY: We check with an AtomicBool that only one instance of this peripheral
114                    // is active at once.
115                    let spi_peripheral = unsafe { peripherals::$peripheral::steal() };
116
117                    // The order of MOSI/MISO pins is inverted.
118                    let spi = InnerSpi::new_blocking(
119                        spi_peripheral,
120                        sck_pin.into_hal_peripheral(),
121                        mosi_pin.into_hal_peripheral(),
122                        miso_pin.into_hal_peripheral(),
123                        spi_config,
124                    );
125
126                    Spi::$peripheral(Self { spim: YieldingAsync::new(BlockingAsync::new(spi)) })
127                }
128            }
129
130            impl Drop for $peripheral {
131                fn drop(&mut self) {
132                    paste::paste! {
133                        [< ACTIVE_ $peripheral >].store(false, Ordering::Release);
134                    }
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_rp::spi::Error;
149        }
150
151        impl_async_spibus_for_driver_enum!(Spi, $( $peripheral ),*);
152    };
153}
154
155// Define a driver per peripheral
156define_spi_drivers!(SPI0, SPI1);