Skip to main content

ariel_os_nrf/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},
10};
11
12use embassy_nrf::{
13    bind_interrupts,
14    gpio::Pin as GpioPin,
15    peripherals,
16    spim::{InterruptHandler, Spim},
17};
18
19/// SPI bus configuration.
20#[derive(Clone)]
21#[non_exhaustive]
22pub struct Config {
23    /// The frequency at which the bus should operate.
24    pub frequency: Frequency,
25    /// The SPI mode to use.
26    pub mode: Mode,
27    #[doc(hidden)]
28    pub bit_order: BitOrder,
29}
30
31impl Default for Config {
32    fn default() -> Self {
33        Self {
34            frequency: Frequency::_1M,
35            mode: Mode::Mode0,
36            bit_order: BitOrder::default(),
37        }
38    }
39}
40
41/// SPI bus frequency.
42// NOTE(hal): limited set of frequencies available.
43#[derive(Debug, Copy, Clone, PartialEq, Eq)]
44#[cfg_attr(feature = "defmt", derive(defmt::Format))]
45#[repr(u32)]
46pub enum Frequency {
47    /// 125 kHz.
48    _125k,
49    /// 250 kHz.
50    _250k,
51    /// 500 kHz.
52    _500k,
53    /// 1 MHz.
54    _1M,
55    /// 2 MHz.
56    _2M,
57    /// 4 MHz.
58    _4M,
59    /// 8 MHz.
60    _8M,
61    // FIXME(embassy): these frequencies are supported by hardware but do not seem supported by
62    // Embassy.
63    // #[cfg(any(context = "nrf52833", context = "nrf5340-app"))]
64    // _16M,
65    // #[cfg(any(context = "nrf52833", context = "nrf5340-app"))]
66    // _32M,
67}
68
69#[doc(hidden)]
70impl Frequency {
71    #[must_use]
72    pub const fn first() -> Self {
73        Self::_125k
74    }
75
76    #[must_use]
77    pub const fn last() -> Self {
78        Self::_8M
79    }
80
81    #[must_use]
82    pub const fn next(self) -> Option<Self> {
83        match self {
84            Self::_125k => Some(Self::_250k),
85            Self::_250k => Some(Self::_500k),
86            Self::_500k => Some(Self::_1M),
87            Self::_1M => Some(Self::_2M),
88            Self::_2M => Some(Self::_4M),
89            Self::_4M => Some(Self::_8M),
90            Self::_8M => None,
91        }
92    }
93
94    #[must_use]
95    pub const fn prev(self) -> Option<Self> {
96        match self {
97            Self::_125k => None,
98            Self::_250k => Some(Self::_125k),
99            Self::_500k => Some(Self::_250k),
100            Self::_1M => Some(Self::_500k),
101            Self::_2M => Some(Self::_1M),
102            Self::_4M => Some(Self::_2M),
103            Self::_8M => Some(Self::_4M),
104        }
105    }
106
107    #[must_use]
108    pub const fn khz(self) -> u32 {
109        match self {
110            Self::_125k => 125,
111            Self::_250k => 250,
112            Self::_500k => 500,
113            Self::_1M => 1000,
114            Self::_2M => 2000,
115            Self::_4M => 4000,
116            Self::_8M => 8000,
117        }
118    }
119}
120
121impl From<ariel_os_embassy_common::spi::main::Frequency> for Frequency {
122    fn from(freq: ariel_os_embassy_common::spi::main::Frequency) -> Self {
123        match freq {
124            ariel_os_embassy_common::spi::main::Frequency::_125k => Self::_125k,
125            ariel_os_embassy_common::spi::main::Frequency::_250k => Self::_250k,
126            ariel_os_embassy_common::spi::main::Frequency::_500k => Self::_500k,
127            ariel_os_embassy_common::spi::main::Frequency::_1M => Self::_1M,
128            ariel_os_embassy_common::spi::main::Frequency::_2M => Self::_2M,
129            ariel_os_embassy_common::spi::main::Frequency::_4M => Self::_4M,
130            ariel_os_embassy_common::spi::main::Frequency::_8M => Self::_8M,
131        }
132    }
133}
134
135impl From<Frequency> for embassy_nrf::spim::Frequency {
136    fn from(freq: Frequency) -> Self {
137        match freq {
138            Frequency::_125k => embassy_nrf::spim::Frequency::K125,
139            Frequency::_250k => embassy_nrf::spim::Frequency::K250,
140            Frequency::_500k => embassy_nrf::spim::Frequency::K500,
141            Frequency::_1M => embassy_nrf::spim::Frequency::M1,
142            Frequency::_2M => embassy_nrf::spim::Frequency::M2,
143            Frequency::_4M => embassy_nrf::spim::Frequency::M4,
144            Frequency::_8M => embassy_nrf::spim::Frequency::M8,
145        }
146    }
147}
148
149macro_rules! define_spi_drivers {
150    ($( $interrupt:ident => $peripheral:ident ),* $(,)?) => {
151        $(
152            /// Peripheral-specific SPI driver.
153            pub struct $peripheral {
154                spim: Spim<'static>,
155            }
156
157            // Ensure this peripheral has only one active Instance.
158            paste::paste! {
159                static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
160            }
161
162            impl $peripheral {
163                /// Returns a driver implementing [`embedded_hal_async::spi::SpiBus`] for this SPI
164                /// peripheral.
165                #[expect(clippy::new_ret_no_self)]
166                #[must_use]
167                pub fn new<SCK: GpioPin, MISO: GpioPin, MOSI: GpioPin>(
168                    sck_pin: impl $crate::IntoPeripheral<'static, SCK>,
169                    miso_pin: impl $crate::IntoPeripheral<'static, MISO>,
170                    mosi_pin: impl $crate::IntoPeripheral<'static, MOSI>,
171                    config: Config,
172                ) -> Spi {
173                    let mut spi_config = embassy_nrf::spim::Config::default();
174                    spi_config.frequency = config.frequency.into();
175                    spi_config.mode = crate::spi::from_mode(config.mode);
176                    spi_config.bit_order = crate::spi::from_bit_order(config.bit_order);
177
178                    bind_interrupts!(
179                        struct Irqs {
180                            $interrupt => InterruptHandler<peripherals::$peripheral>;
181                        }
182                    );
183
184                    // Make this struct a compile-time-enforced singleton: having multiple statics
185                    // defined with the same name would result in a compile-time error.
186                    paste::paste! {
187                        #[allow(dead_code)]
188                        static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
189                    }
190
191                    // Check if we can initialize this peripheral (check if the value was previously false, set it to true).
192                    paste::paste! {
193                        if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
194                            panic!("SPI peripheral already initialized")
195                        }
196                    }
197
198                    // FIXME(safety): enforce that the init code indeed has run
199                    // SAFETY: We check with an AtomicBool that only one instance of this peripheral
200                    // is active at once.
201                    let spim_peripheral = unsafe { peripherals::$peripheral::steal() };
202
203                    let spim = Spim::new(
204                        spim_peripheral,
205                        Irqs,
206                        sck_pin.into_hal_peripheral(),
207                        miso_pin.into_hal_peripheral(),
208                        mosi_pin.into_hal_peripheral(),
209                        spi_config,
210                    );
211
212                    Spi::$peripheral(Self { spim })
213                }
214            }
215
216            impl Drop for $peripheral {
217                fn drop(&mut self) {
218                    paste::paste! {
219                        [< ACTIVE_ $peripheral >].store(false, Ordering::Release);
220                    }
221                }
222            }
223        )*
224
225        /// Peripheral-agnostic driver.
226        pub enum Spi {
227            $(
228                #[doc = concat!(stringify!($peripheral), " peripheral.")]
229                $peripheral($peripheral)
230            ),*
231        }
232
233        impl embedded_hal_async::spi::ErrorType for Spi {
234            type Error = embassy_nrf::spim::Error;
235        }
236
237        impl_async_spibus_for_driver_enum!(Spi, $( $peripheral ),*);
238    };
239}
240
241// Define a driver per peripheral
242#[cfg(context = "nrf52833")]
243define_spi_drivers!(
244    // FIXME: arbitrary selected peripherals
245    // SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => TWISPI0,
246    // SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 => TWISPI1,
247    // SPIM2_SPIS2_SPI2 => SPI2,
248    SPIM3 => SPI3,
249);
250#[cfg(context = "nrf52840")]
251define_spi_drivers!(
252    // FIXME: arbitrary selected peripherals
253    // SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => TWISPI0,
254    // SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 => TWISPI1,
255    // SPIM2_SPIS2_SPI2 => SPI2,
256    SPIM3 => SPI3,
257);
258// FIXME: arbitrary selected peripherals
259#[cfg(context = "nrf5340-app")]
260define_spi_drivers!(
261    SERIAL2 => SERIAL2,
262    // Used by UART
263    // SERIAL3 => SERIAL3,
264);
265// FIXME: arbitrary selected peripherals
266#[cfg(context = "nrf91")]
267define_spi_drivers!(
268    SERIAL2 => SERIAL2,
269    // Used by UART
270    // SERIAL3 => SERIAL3,
271);