Skip to main content

ariel_os_esp/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 esp_hal::{
13    gpio::{
14        self,
15        interconnect::{PeripheralInput, PeripheralOutput},
16    },
17    peripherals,
18    spi::master::Spi as InnerSpi,
19};
20
21// TODO: we could consider making this `pub`
22// NOTE(hal): values from the datasheets.
23#[cfg(any(
24    context = "esp32",
25    context = "esp32c2",
26    context = "esp32c3",
27    context = "esp32c6",
28    context = "esp32h2",
29    context = "esp32s2",
30    context = "esp32s3"
31))]
32const MAX_FREQUENCY: Kilohertz = Kilohertz::MHz(80);
33
34/// SPI bus configuration.
35#[derive(Clone)]
36#[non_exhaustive]
37pub struct Config {
38    /// The frequency at which the bus should operate.
39    pub frequency: Frequency,
40    /// The SPI mode to use.
41    pub mode: Mode,
42    #[doc(hidden)]
43    pub bit_order: BitOrder,
44}
45
46impl Default for Config {
47    fn default() -> Self {
48        Self {
49            frequency: Frequency::F(Kilohertz::MHz(80)),
50            mode: Mode::Mode0,
51            bit_order: BitOrder::default(),
52        }
53    }
54}
55
56/// SPI bus frequency.
57// Possible values are copied from embassy-nrf
58#[derive(Debug, Copy, Clone, PartialEq, Eq)]
59#[cfg_attr(feature = "defmt", derive(defmt::Format))]
60#[repr(u32)]
61pub enum Frequency {
62    /// Arbitrary frequency.
63    F(Kilohertz),
64}
65
66ariel_os_embassy_common::impl_spi_from_frequency!();
67ariel_os_embassy_common::impl_spi_frequency_const_functions!(MAX_FREQUENCY);
68
69impl From<Frequency> for esp_hal::time::Rate {
70    fn from(freq: Frequency) -> Self {
71        match freq {
72            Frequency::F(kilohertz) => esp_hal::time::Rate::from_khz(kilohertz.raw()),
73        }
74    }
75}
76
77macro_rules! define_spi_drivers {
78    ($( $peripheral:ident ),* $(,)?) => {
79        $(
80            /// Peripheral-specific SPI driver.
81            pub struct $peripheral {
82                spim: YieldingAsync<BlockingAsync<InnerSpi<'static, esp_hal::Blocking>>>,
83            }
84
85            // Ensure this peripheral has only one active Instance.
86            paste::paste! {
87                static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
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: PeripheralOutput<'static>,
97                    MISO: PeripheralInput<'static>,
98                    MOSI: PeripheralOutput<'static>,
99                >(
100                    sck_pin: impl $crate::IntoPeripheral<'static, SCK>,
101                    miso_pin: impl $crate::IntoPeripheral<'static, MISO>,
102                    mosi_pin: impl $crate::IntoPeripheral<'static, MOSI>,
103                    config: Config,
104                ) -> Spi {
105                    // Make this struct a compile-time-enforced singleton: having multiple statics
106                    // defined with the same name would result in a compile-time error.
107                    paste::paste! {
108                        #[allow(dead_code)]
109                        static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
110                    }
111
112                    let spi_config = esp_hal::spi::master::Config::default()
113                        .with_frequency(config.frequency.into())
114                        .with_mode(crate::spi::from_mode(config.mode))
115                        .with_read_bit_order(crate::spi::from_bit_order(config.bit_order))
116                        .with_write_bit_order(crate::spi::from_bit_order(config.bit_order));
117
118                    // Check if we can initialize this peripheral (check if the value was previously false, set it to true).
119                    paste::paste! {
120                        if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
121                            panic!("SPI peripheral already initialized")
122                        }
123                    }
124
125                    // FIXME(safety): enforce that the init code indeed has run
126                    // SAFETY: We check with an AtomicBool that only one instance of this peripheral
127                    // is active at once.
128                    let spi_peripheral = unsafe { peripherals::$peripheral::steal() };
129
130                    let spi = esp_hal::spi::master::Spi::new(
131                        spi_peripheral,
132                        spi_config,
133                    )
134                        .unwrap()
135                        .with_sck(sck_pin.into_hal_peripheral())
136                        .with_mosi(mosi_pin.into_hal_peripheral())
137                        .with_miso(miso_pin.into_hal_peripheral())
138                        .with_cs(gpio::NoPin); // The CS pin is managed separately
139
140                    Spi::$peripheral(Self { spim: YieldingAsync::new(BlockingAsync::new(spi)) })
141                }
142            }
143
144            impl Drop for $peripheral {
145                fn drop(&mut self) {
146                    paste::paste! {
147                        [< ACTIVE_ $peripheral >].store(false, Ordering::Release);
148                    }
149                }
150            }
151        )*
152
153        /// Peripheral-agnostic driver.
154        pub enum Spi {
155            $(
156                #[doc = concat!(stringify!($peripheral), " peripheral.")]
157                $peripheral($peripheral)
158            ),*
159        }
160
161        impl embedded_hal_async::spi::ErrorType for Spi {
162            type Error = esp_hal::spi::Error;
163        }
164
165        impl_async_spibus_for_driver_enum!(Spi, $( $peripheral ),*);
166    };
167}
168
169// Define a driver per peripheral
170// SPI0 and SPI1 exist but are not general-purpose SPI peripherals.
171#[cfg(context = "esp32")]
172define_spi_drivers!(SPI2, SPI3);
173#[cfg(context = "esp32c2")]
174define_spi_drivers!(SPI2);
175#[cfg(context = "esp32c3")]
176define_spi_drivers!(SPI2);
177#[cfg(context = "esp32c6")]
178define_spi_drivers!(SPI2);
179#[cfg(context = "esp32h2")]
180define_spi_drivers!(SPI2);
181#[cfg(context = "esp32s2")]
182define_spi_drivers!(SPI2, SPI3);
183#[cfg(context = "esp32s3")]
184define_spi_drivers!(SPI2, SPI3);