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