ariel_os_esp/spi/main/
mod.rs1#![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#[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#[derive(Clone)]
34#[non_exhaustive]
35pub struct Config {
36 pub frequency: Frequency,
38 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
57#[cfg_attr(feature = "defmt", derive(defmt::Format))]
58#[repr(u32)]
59pub enum Frequency {
60 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 pub struct $peripheral {
80 spim: YieldingAsync<BlockingAsync<InnerSpi<'static, esp_hal::Blocking>>>,
81 }
82
83 paste::paste! {
85 static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
86 }
87
88 impl $peripheral {
89 #[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 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 paste::paste! {
118 if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
119 panic!("SPI peripheral already initialized")
120 }
121 }
122
123 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); 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 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#[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);