aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp/src/pio_programs/spi.rs
diff options
context:
space:
mode:
author1-rafael-1 <[email protected]>2025-09-15 20:07:18 +0200
committer1-rafael-1 <[email protected]>2025-09-15 20:07:18 +0200
commit6bb3d2c0720fa082f27d3cdb70f516058497ec87 (patch)
tree5a1e255cff999b00800f203b91a759c720c973e5 /embassy-rp/src/pio_programs/spi.rs
parenteb685574601d98c44faed9a3534d056199b46e20 (diff)
parent92a6fd2946f2cbb15359290f68aa360953da2ff7 (diff)
Merge branch 'main' into rp2040-rtc-alarm
Diffstat (limited to 'embassy-rp/src/pio_programs/spi.rs')
-rw-r--r--embassy-rp/src/pio_programs/spi.rs474
1 files changed, 474 insertions, 0 deletions
diff --git a/embassy-rp/src/pio_programs/spi.rs b/embassy-rp/src/pio_programs/spi.rs
new file mode 100644
index 000000000..b10fc6628
--- /dev/null
+++ b/embassy-rp/src/pio_programs/spi.rs
@@ -0,0 +1,474 @@
1//! PIO backed SPi drivers
2
3use core::marker::PhantomData;
4
5use embassy_futures::join::join;
6use embassy_hal_internal::Peri;
7use embedded_hal_02::spi::{Phase, Polarity};
8use fixed::traits::ToFixed;
9use fixed::types::extra::U8;
10
11use crate::clocks::clk_sys_freq;
12use crate::dma::{AnyChannel, Channel};
13use crate::gpio::Level;
14use crate::pio::{Common, Direction, Instance, LoadedProgram, Pin, PioPin, ShiftDirection, StateMachine};
15use crate::spi::{Async, Blocking, Config, Mode};
16
17/// This struct represents an SPI program loaded into pio instruction memory.
18struct PioSpiProgram<'d, PIO: Instance> {
19 prg: LoadedProgram<'d, PIO>,
20 phase: Phase,
21}
22
23impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
24 /// Load the spi program into the given pio
25 pub fn new(common: &mut Common<'d, PIO>, phase: Phase) -> Self {
26 // These PIO programs are taken straight from the datasheet (3.6.1 in
27 // RP2040 datasheet, 11.6.1 in RP2350 datasheet)
28
29 // Pin assignments:
30 // - SCK is side-set pin 0
31 // - MOSI is OUT pin 0
32 // - MISO is IN pin 0
33 //
34 // Auto-push and auto-pull must be enabled, and the serial frame size is set by
35 // configuring the push/pull threshold. Shift left/right is fine, but you must
36 // justify the data yourself. This is done most conveniently for frame sizes of
37 // 8 or 16 bits by using the narrow store replication and narrow load byte
38 // picking behavior of RP2040's IO fabric.
39
40 let prg = match phase {
41 Phase::CaptureOnFirstTransition => {
42 let prg = pio::pio_asm!(
43 r#"
44 .side_set 1
45
46 ; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and
47 ; transitions on the trailing edge, or some time before the first leading edge.
48
49 out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if
50 in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low)
51 "#
52 );
53
54 common.load_program(&prg.program)
55 }
56 Phase::CaptureOnSecondTransition => {
57 let prg = pio::pio_asm!(
58 r#"
59 .side_set 1
60
61 ; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and
62 ; is captured on the trailing edge.
63
64 out x, 1 side 0 ; Stall here on empty (keep SCK de-asserted)
65 mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping)
66 in pins, 1 side 0 ; Input data, de-assert SCK
67 "#
68 );
69
70 common.load_program(&prg.program)
71 }
72 };
73
74 Self { prg, phase }
75 }
76}
77
78/// PIO SPI errors.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[cfg_attr(feature = "defmt", derive(defmt::Format))]
81#[non_exhaustive]
82pub enum Error {
83 // No errors for now
84}
85
86/// PIO based Spi driver.
87/// Unlike other PIO programs, the PIO SPI driver owns and holds a reference to
88/// the PIO memory it uses. This is so that it can be reconfigured at runtime if
89/// desired.
90pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> {
91 sm: StateMachine<'d, PIO, SM>,
92 cfg: crate::pio::Config<'d, PIO>,
93 program: Option<PioSpiProgram<'d, PIO>>,
94 clk_pin: Pin<'d, PIO>,
95 tx_dma: Option<Peri<'d, AnyChannel>>,
96 rx_dma: Option<Peri<'d, AnyChannel>>,
97 phantom: PhantomData<M>,
98}
99
100impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
101 #[allow(clippy::too_many_arguments)]
102 fn new_inner(
103 pio: &mut Common<'d, PIO>,
104 mut sm: StateMachine<'d, PIO, SM>,
105 clk_pin: Peri<'d, impl PioPin>,
106 mosi_pin: Peri<'d, impl PioPin>,
107 miso_pin: Peri<'d, impl PioPin>,
108 tx_dma: Option<Peri<'d, AnyChannel>>,
109 rx_dma: Option<Peri<'d, AnyChannel>>,
110 config: Config,
111 ) -> Self {
112 let program = PioSpiProgram::new(pio, config.phase);
113
114 let mut clk_pin = pio.make_pio_pin(clk_pin);
115 let mosi_pin = pio.make_pio_pin(mosi_pin);
116 let miso_pin = pio.make_pio_pin(miso_pin);
117
118 if let Polarity::IdleHigh = config.polarity {
119 clk_pin.set_output_inversion(true);
120 } else {
121 clk_pin.set_output_inversion(false);
122 }
123
124 let mut cfg = crate::pio::Config::default();
125
126 cfg.use_program(&program.prg, &[&clk_pin]);
127 cfg.set_out_pins(&[&mosi_pin]);
128 cfg.set_in_pins(&[&miso_pin]);
129
130 cfg.shift_in.auto_fill = true;
131 cfg.shift_in.direction = ShiftDirection::Left;
132 cfg.shift_in.threshold = 8;
133
134 cfg.shift_out.auto_fill = true;
135 cfg.shift_out.direction = ShiftDirection::Left;
136 cfg.shift_out.threshold = 8;
137
138 cfg.clock_divider = calculate_clock_divider(config.frequency);
139
140 sm.set_config(&cfg);
141
142 sm.set_pins(Level::Low, &[&clk_pin, &mosi_pin]);
143 sm.set_pin_dirs(Direction::Out, &[&clk_pin, &mosi_pin]);
144 sm.set_pin_dirs(Direction::In, &[&miso_pin]);
145
146 sm.set_enable(true);
147
148 Self {
149 sm,
150 program: Some(program),
151 cfg,
152 clk_pin,
153 tx_dma,
154 rx_dma,
155 phantom: PhantomData,
156 }
157 }
158
159 fn blocking_read_u8(&mut self) -> Result<u8, Error> {
160 while self.sm.rx().empty() {}
161 let value = self.sm.rx().pull() as u8;
162
163 Ok(value)
164 }
165
166 fn blocking_write_u8(&mut self, v: u8) -> Result<(), Error> {
167 let value = u32::from_be_bytes([v, 0, 0, 0]);
168
169 while !self.sm.tx().try_push(value) {}
170
171 // need to clear here for flush to work correctly
172 self.sm.tx().stalled();
173
174 Ok(())
175 }
176
177 /// Read data from SPI blocking execution until done.
178 pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> {
179 for v in data {
180 self.blocking_write_u8(0)?;
181 *v = self.blocking_read_u8()?;
182 }
183 self.flush()?;
184 Ok(())
185 }
186
187 /// Write data to SPI blocking execution until done.
188 pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> {
189 for v in data {
190 self.blocking_write_u8(*v)?;
191 let _ = self.blocking_read_u8()?;
192 }
193 self.flush()?;
194 Ok(())
195 }
196
197 /// Transfer data to SPI blocking execution until done.
198 pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
199 let len = read.len().max(write.len());
200 for i in 0..len {
201 let wb = write.get(i).copied().unwrap_or(0);
202 self.blocking_write_u8(wb)?;
203
204 let rb = self.blocking_read_u8()?;
205 if let Some(r) = read.get_mut(i) {
206 *r = rb;
207 }
208 }
209 self.flush()?;
210 Ok(())
211 }
212
213 /// Transfer data in place to SPI blocking execution until done.
214 pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> {
215 for v in data {
216 self.blocking_write_u8(*v)?;
217 *v = self.blocking_read_u8()?;
218 }
219 self.flush()?;
220 Ok(())
221 }
222
223 /// Block execution until SPI is done.
224 pub fn flush(&mut self) -> Result<(), Error> {
225 // Wait for all words in the FIFO to have been pulled by the SM
226 while !self.sm.tx().empty() {}
227
228 // Wait for last value to be written out to the wire
229 while !self.sm.tx().stalled() {}
230
231 Ok(())
232 }
233
234 /// Set SPI frequency.
235 pub fn set_frequency(&mut self, freq: u32) {
236 self.sm.set_enable(false);
237
238 let divider = calculate_clock_divider(freq);
239
240 // save into the config for later but dont use sm.set_config() since
241 // that operation is relatively more expensive than just setting the
242 // clock divider
243 self.cfg.clock_divider = divider;
244 self.sm.set_clock_divider(divider);
245
246 self.sm.set_enable(true);
247 }
248
249 /// Set SPI config.
250 ///
251 /// This operation will panic if the PIO program needs to be reloaded and
252 /// there is insufficient room. This is unlikely since the programs for each
253 /// phase only differ in size by a single instruction.
254 pub fn set_config(&mut self, pio: &mut Common<'d, PIO>, config: &Config) {
255 self.sm.set_enable(false);
256
257 self.cfg.clock_divider = calculate_clock_divider(config.frequency);
258
259 if let Polarity::IdleHigh = config.polarity {
260 self.clk_pin.set_output_inversion(true);
261 } else {
262 self.clk_pin.set_output_inversion(false);
263 }
264
265 if self.program.as_ref().unwrap().phase != config.phase {
266 let old_program = self.program.take().unwrap();
267
268 // SAFETY: the state machine is disabled while this happens
269 unsafe { pio.free_instr(old_program.prg.used_memory) };
270
271 let new_program = PioSpiProgram::new(pio, config.phase);
272
273 self.cfg.use_program(&new_program.prg, &[&self.clk_pin]);
274 self.program = Some(new_program);
275 }
276
277 self.sm.set_config(&self.cfg);
278 self.sm.restart();
279
280 self.sm.set_enable(true);
281 }
282}
283
284fn calculate_clock_divider(frequency_hz: u32) -> fixed::FixedU32<U8> {
285 // we multiply by 4 since each clock period is equal to 4 instructions
286
287 let sys_freq = clk_sys_freq().to_fixed::<fixed::FixedU64<U8>>();
288 let target_freq = (frequency_hz * 4).to_fixed::<fixed::FixedU64<U8>>();
289 (sys_freq / target_freq).to_fixed()
290}
291
292impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> {
293 /// Create an SPI driver in blocking mode.
294 pub fn new_blocking(
295 pio: &mut Common<'d, PIO>,
296 sm: StateMachine<'d, PIO, SM>,
297 clk: Peri<'d, impl PioPin>,
298 mosi: Peri<'d, impl PioPin>,
299 miso: Peri<'d, impl PioPin>,
300 config: Config,
301 ) -> Self {
302 Self::new_inner(pio, sm, clk, mosi, miso, None, None, config)
303 }
304}
305
306impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> {
307 /// Create an SPI driver in async mode supporting DMA operations.
308 #[allow(clippy::too_many_arguments)]
309 pub fn new(
310 pio: &mut Common<'d, PIO>,
311 sm: StateMachine<'d, PIO, SM>,
312 clk: Peri<'d, impl PioPin>,
313 mosi: Peri<'d, impl PioPin>,
314 miso: Peri<'d, impl PioPin>,
315 tx_dma: Peri<'d, impl Channel>,
316 rx_dma: Peri<'d, impl Channel>,
317 config: Config,
318 ) -> Self {
319 Self::new_inner(
320 pio,
321 sm,
322 clk,
323 mosi,
324 miso,
325 Some(tx_dma.into()),
326 Some(rx_dma.into()),
327 config,
328 )
329 }
330
331 /// Read data from SPI using DMA.
332 pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
333 let (rx, tx) = self.sm.rx_tx();
334
335 let len = buffer.len();
336
337 let rx_ch = self.rx_dma.as_mut().unwrap().reborrow();
338 let rx_transfer = rx.dma_pull(rx_ch, buffer, false);
339
340 let tx_ch = self.tx_dma.as_mut().unwrap().reborrow();
341 let tx_transfer = tx.dma_push_repeated::<_, u8>(tx_ch, len);
342
343 join(tx_transfer, rx_transfer).await;
344
345 Ok(())
346 }
347
348 /// Write data to SPI using DMA.
349 pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
350 let (rx, tx) = self.sm.rx_tx();
351
352 let rx_ch = self.rx_dma.as_mut().unwrap().reborrow();
353 let rx_transfer = rx.dma_pull_repeated::<_, u8>(rx_ch, buffer.len());
354
355 let tx_ch = self.tx_dma.as_mut().unwrap().reborrow();
356 let tx_transfer = tx.dma_push(tx_ch, buffer, false);
357
358 join(tx_transfer, rx_transfer).await;
359
360 Ok(())
361 }
362
363 /// Transfer data to SPI using DMA.
364 pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> {
365 self.transfer_inner(rx_buffer, tx_buffer).await
366 }
367
368 /// Transfer data in place to SPI using DMA.
369 pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> {
370 self.transfer_inner(words, words).await
371 }
372
373 async fn transfer_inner(&mut self, rx_buffer: *mut [u8], tx_buffer: *const [u8]) -> Result<(), Error> {
374 let (rx, tx) = self.sm.rx_tx();
375
376 let mut rx_ch = self.rx_dma.as_mut().unwrap().reborrow();
377 let rx_transfer = async {
378 rx.dma_pull(rx_ch.reborrow(), unsafe { &mut *rx_buffer }, false).await;
379
380 if tx_buffer.len() > rx_buffer.len() {
381 let read_bytes_len = tx_buffer.len() - rx_buffer.len();
382
383 rx.dma_pull_repeated::<_, u8>(rx_ch, read_bytes_len).await;
384 }
385 };
386
387 let mut tx_ch = self.tx_dma.as_mut().unwrap().reborrow();
388 let tx_transfer = async {
389 tx.dma_push(tx_ch.reborrow(), unsafe { &*tx_buffer }, false).await;
390
391 if rx_buffer.len() > tx_buffer.len() {
392 let write_bytes_len = rx_buffer.len() - tx_buffer.len();
393
394 tx.dma_push_repeated::<_, u8>(tx_ch, write_bytes_len).await;
395 }
396 };
397
398 join(tx_transfer, rx_transfer).await;
399
400 Ok(())
401 }
402}
403
404// ====================
405
406impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_02::blocking::spi::Transfer<u8> for Spi<'d, PIO, SM, M> {
407 type Error = Error;
408 fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
409 self.blocking_transfer_in_place(words)?;
410 Ok(words)
411 }
412}
413
414impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_02::blocking::spi::Write<u8> for Spi<'d, PIO, SM, M> {
415 type Error = Error;
416
417 fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
418 self.blocking_write(words)
419 }
420}
421
422impl embedded_hal_1::spi::Error for Error {
423 fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
424 match *self {}
425 }
426}
427
428impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_1::spi::ErrorType for Spi<'d, PIO, SM, M> {
429 type Error = Error;
430}
431
432impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_1::spi::SpiBus<u8> for Spi<'d, PIO, SM, M> {
433 fn flush(&mut self) -> Result<(), Self::Error> {
434 Ok(())
435 }
436
437 fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
438 self.blocking_transfer(words, &[])
439 }
440
441 fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
442 self.blocking_write(words)
443 }
444
445 fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
446 self.blocking_transfer(read, write)
447 }
448
449 fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
450 self.blocking_transfer_in_place(words)
451 }
452}
453
454impl<'d, PIO: Instance, const SM: usize> embedded_hal_async::spi::SpiBus<u8> for Spi<'d, PIO, SM, Async> {
455 async fn flush(&mut self) -> Result<(), Self::Error> {
456 Ok(())
457 }
458
459 async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
460 self.write(words).await
461 }
462
463 async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
464 self.read(words).await
465 }
466
467 async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
468 self.transfer(read, write).await
469 }
470
471 async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
472 self.transfer_in_place(words).await
473 }
474}