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