embassy_embedded_hal/shared_bus/blocking/
spi.rs

1//! Blocking shared SPI bus
2//!
3//! # Example (nrf52)
4//!
5//! ```rust,ignore
6//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
7//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
8//!
9//! static SPI_BUS: StaticCell<NoopMutex<RefCell<Spim<SPI3>>>> = StaticCell::new();
10//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default());
11//! let spi_bus = NoopMutex::new(RefCell::new(spi));
12//! let spi_bus = SPI_BUS.init(spi_bus);
13//!
14//! // Device 1, using embedded-hal compatible driver for ST7735 LCD display
15//! let cs_pin1 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard);
16//! let spi_dev1 = SpiDevice::new(spi_bus, cs_pin1);
17//! let display1 = ST7735::new(spi_dev1, dc1, rst1, Default::default(), false, 160, 128);
18//! ```
19
20use core::cell::RefCell;
21
22use embassy_sync::blocking_mutex::raw::RawMutex;
23use embassy_sync::blocking_mutex::Mutex;
24use embedded_hal_1::digital::OutputPin;
25use embedded_hal_1::spi::{self, Operation, SpiBus};
26
27use crate::shared_bus::SpiDeviceError;
28use crate::SetConfig;
29
30/// SPI device on a shared bus.
31pub struct SpiDevice<'a, M: RawMutex, BUS, CS> {
32    bus: &'a Mutex<M, RefCell<BUS>>,
33    cs: CS,
34}
35
36impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> {
37    /// Create a new `SpiDevice`.
38    pub fn new(bus: &'a Mutex<M, RefCell<BUS>>, cs: CS) -> Self {
39        Self { bus, cs }
40    }
41}
42
43impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS>
44where
45    BUS: spi::ErrorType,
46    CS: OutputPin,
47{
48    type Error = SpiDeviceError<BUS::Error, CS::Error>;
49}
50
51impl<BUS, M, CS, Word> embedded_hal_1::spi::SpiDevice<Word> for SpiDevice<'_, M, BUS, CS>
52where
53    M: RawMutex,
54    BUS: SpiBus<Word>,
55    CS: OutputPin,
56    Word: Copy + 'static,
57{
58    fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
59        if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
60            return Err(SpiDeviceError::DelayNotSupported);
61        }
62
63        self.bus.lock(|bus| {
64            let mut bus = bus.borrow_mut();
65            self.cs.set_low().map_err(SpiDeviceError::Cs)?;
66
67            let op_res = operations.iter_mut().try_for_each(|op| match op {
68                Operation::Read(buf) => bus.read(buf),
69                Operation::Write(buf) => bus.write(buf),
70                Operation::Transfer(read, write) => bus.transfer(read, write),
71                Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
72                #[cfg(not(feature = "time"))]
73                Operation::DelayNs(_) => unreachable!(),
74                #[cfg(feature = "time")]
75                Operation::DelayNs(ns) => {
76                    embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _));
77                    Ok(())
78                }
79            });
80
81            // On failure, it's important to still flush and deassert CS.
82            let flush_res = bus.flush();
83            let cs_res = self.cs.set_high();
84
85            let op_res = op_res.map_err(SpiDeviceError::Spi)?;
86            flush_res.map_err(SpiDeviceError::Spi)?;
87            cs_res.map_err(SpiDeviceError::Cs)?;
88
89            Ok(op_res)
90        })
91    }
92}
93
94/// SPI device on a shared bus, with its own configuration.
95///
96/// This is like [`SpiDevice`], with an additional bus configuration that's applied
97/// to the bus before each use using [`SetConfig`]. This allows different
98/// devices on the same bus to use different communication settings.
99pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> {
100    bus: &'a Mutex<M, RefCell<BUS>>,
101    cs: CS,
102    config: BUS::Config,
103}
104
105impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> {
106    /// Create a new `SpiDeviceWithConfig`.
107    pub fn new(bus: &'a Mutex<M, RefCell<BUS>>, cs: CS, config: BUS::Config) -> Self {
108        Self { bus, cs, config }
109    }
110
111    /// Change the device's config at runtime
112    pub fn set_config(&mut self, config: BUS::Config) {
113        self.config = config;
114    }
115}
116
117impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS>
118where
119    M: RawMutex,
120    BUS: spi::ErrorType + SetConfig,
121    CS: OutputPin,
122{
123    type Error = SpiDeviceError<BUS::Error, CS::Error>;
124}
125
126impl<BUS, M, CS, Word> embedded_hal_1::spi::SpiDevice<Word> for SpiDeviceWithConfig<'_, M, BUS, CS>
127where
128    M: RawMutex,
129    BUS: SpiBus<Word> + SetConfig,
130    CS: OutputPin,
131    Word: Copy + 'static,
132{
133    fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
134        if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
135            return Err(SpiDeviceError::DelayNotSupported);
136        }
137
138        self.bus.lock(|bus| {
139            let mut bus = bus.borrow_mut();
140            bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
141            self.cs.set_low().map_err(SpiDeviceError::Cs)?;
142
143            let op_res = operations.iter_mut().try_for_each(|op| match op {
144                Operation::Read(buf) => bus.read(buf),
145                Operation::Write(buf) => bus.write(buf),
146                Operation::Transfer(read, write) => bus.transfer(read, write),
147                Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
148                #[cfg(not(feature = "time"))]
149                Operation::DelayNs(_) => unreachable!(),
150                #[cfg(feature = "time")]
151                Operation::DelayNs(ns) => {
152                    embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _));
153                    Ok(())
154                }
155            });
156
157            // On failure, it's important to still flush and deassert CS.
158            let flush_res = bus.flush();
159            let cs_res = self.cs.set_high();
160
161            let op_res = op_res.map_err(SpiDeviceError::Spi)?;
162            flush_res.map_err(SpiDeviceError::Spi)?;
163            cs_res.map_err(SpiDeviceError::Cs)?;
164            Ok(op_res)
165        })
166    }
167}