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