aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2025-09-05 19:00:01 +0000
committerGitHub <[email protected]>2025-09-05 19:00:01 +0000
commitbbcf9af87eddbf9e1dba3281a3f1a9e5e419477f (patch)
tree7dfaa5b0435e7210410f7f730d78a774c44707d6
parent0407f7ebe8fabeb81b8a77811ec5dda0fee0b44b (diff)
parent815ba8aa7507d0aa27dfb3f3823793e3567719f8 (diff)
Merge pull request #4639 from embassy-rs/rp-pio-spi
rp: add PIO SPI
-rw-r--r--embassy-rp/src/pio/mod.rs86
-rw-r--r--embassy-rp/src/pio_programs/mod.rs1
-rw-r--r--embassy-rp/src/pio_programs/spi.rs474
-rw-r--r--examples/rp/src/bin/pio_spi.rs48
-rw-r--r--examples/rp/src/bin/pio_spi_async.rs57
5 files changed, 656 insertions, 10 deletions
diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs
index 0d8a94776..5f554dfe3 100644
--- a/embassy-rp/src/pio/mod.rs
+++ b/embassy-rp/src/pio/mod.rs
@@ -12,7 +12,7 @@ use fixed::types::extra::U8;
12use fixed::FixedU32; 12use fixed::FixedU32;
13use pio::{Program, SideSet, Wrap}; 13use pio::{Program, SideSet, Wrap};
14 14
15use crate::dma::{Channel, Transfer, Word}; 15use crate::dma::{self, Channel, Transfer, Word};
16use crate::gpio::{self, AnyPin, Drive, Level, Pull, SealedPin, SlewRate}; 16use crate::gpio::{self, AnyPin, Drive, Level, Pull, SealedPin, SlewRate};
17use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; 17use crate::interrupt::typelevel::{Binding, Handler, Interrupt};
18use crate::relocate::RelocatedProgram; 18use crate::relocate::RelocatedProgram;
@@ -281,6 +281,18 @@ impl<'l, PIO: Instance> Pin<'l, PIO> {
281 }); 281 });
282 } 282 }
283 283
284 /// Configure the output logic inversion of this pin.
285 #[inline]
286 pub fn set_output_inversion(&mut self, invert: bool) {
287 self.pin.gpio().ctrl().modify(|w| {
288 w.set_outover(if invert {
289 pac::io::vals::Outover::INVERT
290 } else {
291 pac::io::vals::Outover::NORMAL
292 })
293 });
294 }
295
284 /// Set the pin's input sync bypass. 296 /// Set the pin's input sync bypass.
285 pub fn set_input_sync_bypass(&mut self, bypass: bool) { 297 pub fn set_input_sync_bypass(&mut self, bypass: bool) {
286 let mask = 1 << self.pin(); 298 let mask = 1 << self.pin();
@@ -360,6 +372,10 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> {
360 FifoInFuture::new(self) 372 FifoInFuture::new(self)
361 } 373 }
362 374
375 fn dreq() -> crate::pac::dma::vals::TreqSel {
376 crate::pac::dma::vals::TreqSel::from(PIO::PIO_NO * 8 + SM as u8 + 4)
377 }
378
363 /// Prepare DMA transfer from RX FIFO. 379 /// Prepare DMA transfer from RX FIFO.
364 pub fn dma_pull<'a, C: Channel, W: Word>( 380 pub fn dma_pull<'a, C: Channel, W: Word>(
365 &'a mut self, 381 &'a mut self,
@@ -367,7 +383,6 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> {
367 data: &'a mut [W], 383 data: &'a mut [W],
368 bswap: bool, 384 bswap: bool,
369 ) -> Transfer<'a, C> { 385 ) -> Transfer<'a, C> {
370 let pio_no = PIO::PIO_NO;
371 let p = ch.regs(); 386 let p = ch.regs();
372 p.write_addr().write_value(data.as_ptr() as u32); 387 p.write_addr().write_value(data.as_ptr() as u32);
373 p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); 388 p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32);
@@ -377,8 +392,7 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> {
377 p.trans_count().write(|w| w.set_count(data.len() as u32)); 392 p.trans_count().write(|w| w.set_count(data.len() as u32));
378 compiler_fence(Ordering::SeqCst); 393 compiler_fence(Ordering::SeqCst);
379 p.ctrl_trig().write(|w| { 394 p.ctrl_trig().write(|w| {
380 // Set RX DREQ for this statemachine 395 w.set_treq_sel(Self::dreq());
381 w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8 + 4));
382 w.set_data_size(W::size()); 396 w.set_data_size(W::size());
383 w.set_chain_to(ch.number()); 397 w.set_chain_to(ch.number());
384 w.set_incr_read(false); 398 w.set_incr_read(false);
@@ -389,6 +403,36 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> {
389 compiler_fence(Ordering::SeqCst); 403 compiler_fence(Ordering::SeqCst);
390 Transfer::new(ch) 404 Transfer::new(ch)
391 } 405 }
406
407 /// Prepare a repeated DMA transfer from RX FIFO.
408 pub fn dma_pull_repeated<'a, C: Channel, W: Word>(&'a mut self, ch: Peri<'a, C>, len: usize) -> Transfer<'a, C> {
409 // This is the read version of dma::write_repeated. This allows us to
410 // discard reads from the RX FIFO through DMA.
411
412 // static mut so it gets allocated in RAM
413 static mut DUMMY: u32 = 0;
414
415 let p = ch.regs();
416 p.write_addr().write_value(core::ptr::addr_of_mut!(DUMMY) as u32);
417 p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32);
418
419 #[cfg(feature = "rp2040")]
420 p.trans_count().write(|w| *w = len as u32);
421 #[cfg(feature = "_rp235x")]
422 p.trans_count().write(|w| w.set_count(len as u32));
423
424 compiler_fence(Ordering::SeqCst);
425 p.ctrl_trig().write(|w| {
426 w.set_treq_sel(Self::dreq());
427 w.set_data_size(W::size());
428 w.set_chain_to(ch.number());
429 w.set_incr_read(false);
430 w.set_incr_write(false);
431 w.set_en(true);
432 });
433 compiler_fence(Ordering::SeqCst);
434 Transfer::new(ch)
435 }
392} 436}
393 437
394/// Type representing a state machine TX FIFO. 438/// Type representing a state machine TX FIFO.
@@ -412,7 +456,7 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> {
412 (PIO::PIO.flevel().read().0 >> (SM * 8)) as u8 & 0x0f 456 (PIO::PIO.flevel().read().0 >> (SM * 8)) as u8 & 0x0f
413 } 457 }
414 458
415 /// Check state machine has stalled on empty TX FIFO. 459 /// Check if state machine has stalled on empty TX FIFO.
416 pub fn stalled(&self) -> bool { 460 pub fn stalled(&self) -> bool {
417 let fdebug = PIO::PIO.fdebug(); 461 let fdebug = PIO::PIO.fdebug();
418 let ret = fdebug.read().txstall() & (1 << SM) != 0; 462 let ret = fdebug.read().txstall() & (1 << SM) != 0;
@@ -451,6 +495,10 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> {
451 FifoOutFuture::new(self, value) 495 FifoOutFuture::new(self, value)
452 } 496 }
453 497
498 fn dreq() -> crate::pac::dma::vals::TreqSel {
499 crate::pac::dma::vals::TreqSel::from(PIO::PIO_NO * 8 + SM as u8)
500 }
501
454 /// Prepare a DMA transfer to TX FIFO. 502 /// Prepare a DMA transfer to TX FIFO.
455 pub fn dma_push<'a, C: Channel, W: Word>( 503 pub fn dma_push<'a, C: Channel, W: Word>(
456 &'a mut self, 504 &'a mut self,
@@ -458,7 +506,6 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> {
458 data: &'a [W], 506 data: &'a [W],
459 bswap: bool, 507 bswap: bool,
460 ) -> Transfer<'a, C> { 508 ) -> Transfer<'a, C> {
461 let pio_no = PIO::PIO_NO;
462 let p = ch.regs(); 509 let p = ch.regs();
463 p.read_addr().write_value(data.as_ptr() as u32); 510 p.read_addr().write_value(data.as_ptr() as u32);
464 p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32); 511 p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32);
@@ -468,8 +515,7 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> {
468 p.trans_count().write(|w| w.set_count(data.len() as u32)); 515 p.trans_count().write(|w| w.set_count(data.len() as u32));
469 compiler_fence(Ordering::SeqCst); 516 compiler_fence(Ordering::SeqCst);
470 p.ctrl_trig().write(|w| { 517 p.ctrl_trig().write(|w| {
471 // Set TX DREQ for this statemachine 518 w.set_treq_sel(Self::dreq());
472 w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8));
473 w.set_data_size(W::size()); 519 w.set_data_size(W::size());
474 w.set_chain_to(ch.number()); 520 w.set_chain_to(ch.number());
475 w.set_incr_read(true); 521 w.set_incr_read(true);
@@ -480,6 +526,11 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> {
480 compiler_fence(Ordering::SeqCst); 526 compiler_fence(Ordering::SeqCst);
481 Transfer::new(ch) 527 Transfer::new(ch)
482 } 528 }
529
530 /// Prepare a repeated DMA transfer to TX FIFO.
531 pub fn dma_push_repeated<'a, C: Channel, W: Word>(&'a mut self, ch: Peri<'a, C>, len: usize) -> Transfer<'a, C> {
532 unsafe { dma::write_repeated(ch, PIO::PIO.txf(SM).as_ptr(), len, Self::dreq()) }
533 }
483} 534}
484 535
485/// A type representing a single PIO state machine. 536/// A type representing a single PIO state machine.
@@ -926,13 +977,27 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> {
926 self.set_enable(enabled); 977 self.set_enable(enabled);
927 } 978 }
928 979
980 #[cfg(feature = "rp2040")]
981 fn pin_base() -> u8 {
982 0
983 }
984
985 #[cfg(feature = "_rp235x")]
986 fn pin_base() -> u8 {
987 if PIO::PIO.gpiobase().read().gpiobase() {
988 16
989 } else {
990 0
991 }
992 }
993
929 /// Sets pin directions. This pauses the current state machine to run `SET` commands 994 /// Sets pin directions. This pauses the current state machine to run `SET` commands
930 /// and temporarily unsets the `OUT_STICKY` bit. 995 /// and temporarily unsets the `OUT_STICKY` bit.
931 pub fn set_pin_dirs(&mut self, dir: Direction, pins: &[&Pin<'d, PIO>]) { 996 pub fn set_pin_dirs(&mut self, dir: Direction, pins: &[&Pin<'d, PIO>]) {
932 self.with_paused(|sm| { 997 self.with_paused(|sm| {
933 for pin in pins { 998 for pin in pins {
934 Self::this_sm().pinctrl().write(|w| { 999 Self::this_sm().pinctrl().write(|w| {
935 w.set_set_base(pin.pin()); 1000 w.set_set_base(pin.pin() - Self::pin_base());
936 w.set_set_count(1); 1001 w.set_set_count(1);
937 }); 1002 });
938 // SET PINDIRS, (dir) 1003 // SET PINDIRS, (dir)
@@ -947,7 +1012,7 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> {
947 self.with_paused(|sm| { 1012 self.with_paused(|sm| {
948 for pin in pins { 1013 for pin in pins {
949 Self::this_sm().pinctrl().write(|w| { 1014 Self::this_sm().pinctrl().write(|w| {
950 w.set_set_base(pin.pin()); 1015 w.set_set_base(pin.pin() - Self::pin_base());
951 w.set_set_count(1); 1016 w.set_set_count(1);
952 }); 1017 });
953 // SET PINS, (dir) 1018 // SET PINS, (dir)
@@ -1310,6 +1375,7 @@ impl<'d, PIO: Instance> Pio<'d, PIO> {
1310 PIO::state().users.store(5, Ordering::Release); 1375 PIO::state().users.store(5, Ordering::Release);
1311 PIO::state().used_pins.store(0, Ordering::Release); 1376 PIO::state().used_pins.store(0, Ordering::Release);
1312 PIO::Interrupt::unpend(); 1377 PIO::Interrupt::unpend();
1378
1313 unsafe { PIO::Interrupt::enable() }; 1379 unsafe { PIO::Interrupt::enable() };
1314 Self { 1380 Self {
1315 common: Common { 1381 common: Common {
diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs
index 8eac328b3..d05ba3884 100644
--- a/embassy-rp/src/pio_programs/mod.rs
+++ b/embassy-rp/src/pio_programs/mod.rs
@@ -6,6 +6,7 @@ pub mod i2s;
6pub mod onewire; 6pub mod onewire;
7pub mod pwm; 7pub mod pwm;
8pub mod rotary_encoder; 8pub mod rotary_encoder;
9pub mod spi;
9pub mod stepper; 10pub mod stepper;
10pub mod uart; 11pub mod uart;
11pub mod ws2812; 12pub mod ws2812;
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}
diff --git a/examples/rp/src/bin/pio_spi.rs b/examples/rp/src/bin/pio_spi.rs
new file mode 100644
index 000000000..4218327ec
--- /dev/null
+++ b/examples/rp/src/bin/pio_spi.rs
@@ -0,0 +1,48 @@
1//! This example shows how to use a PIO state machine as an additional SPI
2//! (Serial Peripheral Interface) on the RP2040 chip. No specific hardware is
3//! specified in this example.
4//!
5//! If you connect pin 6 and 7 you should get the same data back.
6
7#![no_std]
8#![no_main]
9
10use defmt::*;
11use embassy_executor::Spawner;
12use embassy_rp::peripherals::PIO0;
13use embassy_rp::pio_programs::spi::Spi;
14use embassy_rp::spi::Config;
15use embassy_rp::{bind_interrupts, pio};
16use embassy_time::Timer;
17use {defmt_rtt as _, panic_probe as _};
18
19bind_interrupts!(struct Irqs {
20 PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
21});
22
23#[embassy_executor::main]
24async fn main(_spawner: Spawner) {
25 let p = embassy_rp::init(Default::default());
26 info!("Hello World!");
27
28 // These pins are routed to different hardware SPI peripherals, but we can
29 // use them together regardless
30 let mosi = p.PIN_6; // SPI0 SCLK
31 let miso = p.PIN_7; // SPI0 MOSI
32 let clk = p.PIN_8; // SPI1 MISO
33
34 let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs);
35
36 // Construct an SPI driver backed by a PIO state machine
37 let mut spi = Spi::new_blocking(&mut common, sm0, clk, mosi, miso, Config::default());
38
39 loop {
40 let tx_buf = [1_u8, 2, 3, 4, 5, 6];
41 let mut rx_buf = [0_u8; 6];
42
43 spi.blocking_transfer(&mut rx_buf, &tx_buf).unwrap();
44 info!("{:?}", rx_buf);
45
46 Timer::after_secs(1).await;
47 }
48}
diff --git a/examples/rp/src/bin/pio_spi_async.rs b/examples/rp/src/bin/pio_spi_async.rs
new file mode 100644
index 000000000..18b57d26e
--- /dev/null
+++ b/examples/rp/src/bin/pio_spi_async.rs
@@ -0,0 +1,57 @@
1//! This example shows how to use a PIO state machine as an additional SPI
2//! (Serial Peripheral Interface) on the RP2040 chip. No specific hardware is
3//! specified in this example.
4//!
5//! If you connect pin 6 and 7 you should get the same data back.
6
7#![no_std]
8#![no_main]
9
10use defmt::*;
11use embassy_executor::Spawner;
12use embassy_rp::peripherals::PIO0;
13use embassy_rp::pio_programs::spi::Spi;
14use embassy_rp::spi::Config;
15use embassy_rp::{bind_interrupts, pio};
16use embassy_time::Timer;
17use {defmt_rtt as _, panic_probe as _};
18
19bind_interrupts!(struct Irqs {
20 PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
21});
22
23#[embassy_executor::main]
24async fn main(_spawner: Spawner) {
25 let p = embassy_rp::init(Default::default());
26 info!("Hello World!");
27
28 // These pins are routed to different hardware SPI peripherals, but we can
29 // use them together regardless
30 let mosi = p.PIN_6; // SPI0 SCLK
31 let miso = p.PIN_7; // SPI0 MOSI
32 let clk = p.PIN_8; // SPI1 MISO
33
34 let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs);
35
36 // Construct an SPI driver backed by a PIO state machine
37 let mut spi = Spi::new(
38 &mut common,
39 sm0,
40 clk,
41 mosi,
42 miso,
43 p.DMA_CH0,
44 p.DMA_CH1,
45 Config::default(),
46 );
47
48 loop {
49 let tx_buf = [1_u8, 2, 3, 4, 5, 6];
50 let mut rx_buf = [0_u8; 6];
51
52 spi.transfer(&mut rx_buf, &tx_buf).await.unwrap();
53 info!("{:?}", rx_buf);
54
55 Timer::after_secs(1).await;
56 }
57}