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; use fixed::FixedU32; use pio::{Program, SideSet, Wrap}; -use crate::dma::{Channel, Transfer, Word}; +use crate::dma::{self, Channel, Transfer, Word}; use crate::gpio::{self, AnyPin, Drive, Level, Pull, SealedPin, SlewRate}; use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; use crate::relocate::RelocatedProgram; @@ -281,6 +281,18 @@ impl<'l, PIO: Instance> Pin<'l, PIO> { }); } + /// Configure the output logic inversion of this pin. + #[inline] + pub fn set_output_inversion(&mut self, invert: bool) { + self.pin.gpio().ctrl().modify(|w| { + w.set_outover(if invert { + pac::io::vals::Outover::INVERT + } else { + pac::io::vals::Outover::NORMAL + }) + }); + } + /// Set the pin's input sync bypass. pub fn set_input_sync_bypass(&mut self, bypass: bool) { let mask = 1 << self.pin(); @@ -360,6 +372,10 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { FifoInFuture::new(self) } + fn dreq() -> crate::pac::dma::vals::TreqSel { + crate::pac::dma::vals::TreqSel::from(PIO::PIO_NO * 8 + SM as u8 + 4) + } + /// Prepare DMA transfer from RX FIFO. pub fn dma_pull<'a, C: Channel, W: Word>( &'a mut self, @@ -367,7 +383,6 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { data: &'a mut [W], bswap: bool, ) -> Transfer<'a, C> { - let pio_no = PIO::PIO_NO; let p = ch.regs(); p.write_addr().write_value(data.as_ptr() as u32); 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> { p.trans_count().write(|w| w.set_count(data.len() as u32)); compiler_fence(Ordering::SeqCst); p.ctrl_trig().write(|w| { - // Set RX DREQ for this statemachine - w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8 + 4)); + w.set_treq_sel(Self::dreq()); w.set_data_size(W::size()); w.set_chain_to(ch.number()); w.set_incr_read(false); @@ -389,6 +403,36 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { compiler_fence(Ordering::SeqCst); Transfer::new(ch) } + + /// Prepare a repeated DMA transfer from RX FIFO. + pub fn dma_pull_repeated<'a, C: Channel, W: Word>(&'a mut self, ch: Peri<'a, C>, len: usize) -> Transfer<'a, C> { + // This is the read version of dma::write_repeated. This allows us to + // discard reads from the RX FIFO through DMA. + + // static mut so it gets allocated in RAM + static mut DUMMY: u32 = 0; + + let p = ch.regs(); + p.write_addr().write_value(core::ptr::addr_of_mut!(DUMMY) as u32); + p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); + + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| *w = len as u32); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| w.set_count(len as u32)); + + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + w.set_treq_sel(Self::dreq()); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(false); + w.set_incr_write(false); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } } /// Type representing a state machine TX FIFO. @@ -412,7 +456,7 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { (PIO::PIO.flevel().read().0 >> (SM * 8)) as u8 & 0x0f } - /// Check state machine has stalled on empty TX FIFO. + /// Check if state machine has stalled on empty TX FIFO. pub fn stalled(&self) -> bool { let fdebug = PIO::PIO.fdebug(); let ret = fdebug.read().txstall() & (1 << SM) != 0; @@ -451,6 +495,10 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { FifoOutFuture::new(self, value) } + fn dreq() -> crate::pac::dma::vals::TreqSel { + crate::pac::dma::vals::TreqSel::from(PIO::PIO_NO * 8 + SM as u8) + } + /// Prepare a DMA transfer to TX FIFO. pub fn dma_push<'a, C: Channel, W: Word>( &'a mut self, @@ -458,7 +506,6 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { data: &'a [W], bswap: bool, ) -> Transfer<'a, C> { - let pio_no = PIO::PIO_NO; let p = ch.regs(); p.read_addr().write_value(data.as_ptr() as u32); 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> { p.trans_count().write(|w| w.set_count(data.len() as u32)); compiler_fence(Ordering::SeqCst); p.ctrl_trig().write(|w| { - // Set TX DREQ for this statemachine - w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8)); + w.set_treq_sel(Self::dreq()); w.set_data_size(W::size()); w.set_chain_to(ch.number()); w.set_incr_read(true); @@ -480,6 +526,11 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { compiler_fence(Ordering::SeqCst); Transfer::new(ch) } + + /// Prepare a repeated DMA transfer to TX FIFO. + pub fn dma_push_repeated<'a, C: Channel, W: Word>(&'a mut self, ch: Peri<'a, C>, len: usize) -> Transfer<'a, C> { + unsafe { dma::write_repeated(ch, PIO::PIO.txf(SM).as_ptr(), len, Self::dreq()) } + } } /// A type representing a single PIO state machine. @@ -926,13 +977,27 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { self.set_enable(enabled); } + #[cfg(feature = "rp2040")] + fn pin_base() -> u8 { + 0 + } + + #[cfg(feature = "_rp235x")] + fn pin_base() -> u8 { + if PIO::PIO.gpiobase().read().gpiobase() { + 16 + } else { + 0 + } + } + /// Sets pin directions. This pauses the current state machine to run `SET` commands /// and temporarily unsets the `OUT_STICKY` bit. pub fn set_pin_dirs(&mut self, dir: Direction, pins: &[&Pin<'d, PIO>]) { self.with_paused(|sm| { for pin in pins { Self::this_sm().pinctrl().write(|w| { - w.set_set_base(pin.pin()); + w.set_set_base(pin.pin() - Self::pin_base()); w.set_set_count(1); }); // SET PINDIRS, (dir) @@ -947,7 +1012,7 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { self.with_paused(|sm| { for pin in pins { Self::this_sm().pinctrl().write(|w| { - w.set_set_base(pin.pin()); + w.set_set_base(pin.pin() - Self::pin_base()); w.set_set_count(1); }); // SET PINS, (dir) @@ -1310,6 +1375,7 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { PIO::state().users.store(5, Ordering::Release); PIO::state().used_pins.store(0, Ordering::Release); PIO::Interrupt::unpend(); + unsafe { PIO::Interrupt::enable() }; Self { 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; pub mod onewire; pub mod pwm; pub mod rotary_encoder; +pub mod spi; pub mod stepper; pub mod uart; pub 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 @@ +//! PIO backed SPi drivers + +use core::marker::PhantomData; + +use embassy_futures::join::join; +use embassy_hal_internal::Peri; +use embedded_hal_02::spi::{Phase, Polarity}; +use fixed::traits::ToFixed; +use fixed::types::extra::U8; + +use crate::clocks::clk_sys_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::Level; +use crate::pio::{Common, Direction, Instance, LoadedProgram, Pin, PioPin, ShiftDirection, StateMachine}; +use crate::spi::{Async, Blocking, Config, Mode}; + +/// This struct represents an SPI program loaded into pio instruction memory. +struct PioSpiProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, + phase: Phase, +} + +impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> { + /// Load the spi program into the given pio + pub fn new(common: &mut Common<'d, PIO>, phase: Phase) -> Self { + // These PIO programs are taken straight from the datasheet (3.6.1 in + // RP2040 datasheet, 11.6.1 in RP2350 datasheet) + + // Pin assignments: + // - SCK is side-set pin 0 + // - MOSI is OUT pin 0 + // - MISO is IN pin 0 + // + // Auto-push and auto-pull must be enabled, and the serial frame size is set by + // configuring the push/pull threshold. Shift left/right is fine, but you must + // justify the data yourself. This is done most conveniently for frame sizes of + // 8 or 16 bits by using the narrow store replication and narrow load byte + // picking behavior of RP2040's IO fabric. + + let prg = match phase { + Phase::CaptureOnFirstTransition => { + let prg = pio::pio_asm!( + r#" + .side_set 1 + + ; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and + ; transitions on the trailing edge, or some time before the first leading edge. + + out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if + in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low) + "# + ); + + common.load_program(&prg.program) + } + Phase::CaptureOnSecondTransition => { + let prg = pio::pio_asm!( + r#" + .side_set 1 + + ; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and + ; is captured on the trailing edge. + + out x, 1 side 0 ; Stall here on empty (keep SCK de-asserted) + mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping) + in pins, 1 side 0 ; Input data, de-assert SCK + "# + ); + + common.load_program(&prg.program) + } + }; + + Self { prg, phase } + } +} + +/// PIO SPI errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + +/// PIO based Spi driver. +/// Unlike other PIO programs, the PIO SPI driver owns and holds a reference to +/// the PIO memory it uses. This is so that it can be reconfigured at runtime if +/// desired. +pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> { + sm: StateMachine<'d, PIO, SM>, + cfg: crate::pio::Config<'d, PIO>, + program: Option>, + clk_pin: Pin<'d, PIO>, + tx_dma: Option>, + rx_dma: Option>, + phantom: PhantomData, +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { + #[allow(clippy::too_many_arguments)] + fn new_inner( + pio: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + clk_pin: Peri<'d, impl PioPin>, + mosi_pin: Peri<'d, impl PioPin>, + miso_pin: Peri<'d, impl PioPin>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + let program = PioSpiProgram::new(pio, config.phase); + + let mut clk_pin = pio.make_pio_pin(clk_pin); + let mosi_pin = pio.make_pio_pin(mosi_pin); + let miso_pin = pio.make_pio_pin(miso_pin); + + if let Polarity::IdleHigh = config.polarity { + clk_pin.set_output_inversion(true); + } else { + clk_pin.set_output_inversion(false); + } + + let mut cfg = crate::pio::Config::default(); + + cfg.use_program(&program.prg, &[&clk_pin]); + cfg.set_out_pins(&[&mosi_pin]); + cfg.set_in_pins(&[&miso_pin]); + + cfg.shift_in.auto_fill = true; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.shift_in.threshold = 8; + + cfg.shift_out.auto_fill = true; + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.threshold = 8; + + cfg.clock_divider = calculate_clock_divider(config.frequency); + + sm.set_config(&cfg); + + sm.set_pins(Level::Low, &[&clk_pin, &mosi_pin]); + sm.set_pin_dirs(Direction::Out, &[&clk_pin, &mosi_pin]); + sm.set_pin_dirs(Direction::In, &[&miso_pin]); + + sm.set_enable(true); + + Self { + sm, + program: Some(program), + cfg, + clk_pin, + tx_dma, + rx_dma, + phantom: PhantomData, + } + } + + fn blocking_read_u8(&mut self) -> Result { + while self.sm.rx().empty() {} + let value = self.sm.rx().pull() as u8; + + Ok(value) + } + + fn blocking_write_u8(&mut self, v: u8) -> Result<(), Error> { + let value = u32::from_be_bytes([v, 0, 0, 0]); + + while !self.sm.tx().try_push(value) {} + + // need to clear here for flush to work correctly + self.sm.tx().stalled(); + + Ok(()) + } + + /// Read data from SPI blocking execution until done. + pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { + for v in data { + self.blocking_write_u8(0)?; + *v = self.blocking_read_u8()?; + } + self.flush()?; + Ok(()) + } + + /// Write data to SPI blocking execution until done. + pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { + for v in data { + self.blocking_write_u8(*v)?; + let _ = self.blocking_read_u8()?; + } + self.flush()?; + Ok(()) + } + + /// Transfer data to SPI blocking execution until done. + pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + let len = read.len().max(write.len()); + for i in 0..len { + let wb = write.get(i).copied().unwrap_or(0); + self.blocking_write_u8(wb)?; + + let rb = self.blocking_read_u8()?; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + self.flush()?; + Ok(()) + } + + /// Transfer data in place to SPI blocking execution until done. + pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { + for v in data { + self.blocking_write_u8(*v)?; + *v = self.blocking_read_u8()?; + } + self.flush()?; + Ok(()) + } + + /// Block execution until SPI is done. + pub fn flush(&mut self) -> Result<(), Error> { + // Wait for all words in the FIFO to have been pulled by the SM + while !self.sm.tx().empty() {} + + // Wait for last value to be written out to the wire + while !self.sm.tx().stalled() {} + + Ok(()) + } + + /// Set SPI frequency. + pub fn set_frequency(&mut self, freq: u32) { + self.sm.set_enable(false); + + let divider = calculate_clock_divider(freq); + + // save into the config for later but dont use sm.set_config() since + // that operation is relatively more expensive than just setting the + // clock divider + self.cfg.clock_divider = divider; + self.sm.set_clock_divider(divider); + + self.sm.set_enable(true); + } + + /// Set SPI config. + /// + /// This operation will panic if the PIO program needs to be reloaded and + /// there is insufficient room. This is unlikely since the programs for each + /// phase only differ in size by a single instruction. + pub fn set_config(&mut self, pio: &mut Common<'d, PIO>, config: &Config) { + self.sm.set_enable(false); + + self.cfg.clock_divider = calculate_clock_divider(config.frequency); + + if let Polarity::IdleHigh = config.polarity { + self.clk_pin.set_output_inversion(true); + } else { + self.clk_pin.set_output_inversion(false); + } + + if self.program.as_ref().unwrap().phase != config.phase { + let old_program = self.program.take().unwrap(); + + // SAFETY: the state machine is disabled while this happens + unsafe { pio.free_instr(old_program.prg.used_memory) }; + + let new_program = PioSpiProgram::new(pio, config.phase); + + self.cfg.use_program(&new_program.prg, &[&self.clk_pin]); + self.program = Some(new_program); + } + + self.sm.set_config(&self.cfg); + self.sm.restart(); + + self.sm.set_enable(true); + } +} + +fn calculate_clock_divider(frequency_hz: u32) -> fixed::FixedU32 { + // we multiply by 4 since each clock period is equal to 4 instructions + + let sys_freq = clk_sys_freq().to_fixed::>(); + let target_freq = (frequency_hz * 4).to_fixed::>(); + (sys_freq / target_freq).to_fixed() +} + +impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> { + /// Create an SPI driver in blocking mode. + pub fn new_blocking( + pio: &mut Common<'d, PIO>, + sm: StateMachine<'d, PIO, SM>, + clk: Peri<'d, impl PioPin>, + mosi: Peri<'d, impl PioPin>, + miso: Peri<'d, impl PioPin>, + config: Config, + ) -> Self { + Self::new_inner(pio, sm, clk, mosi, miso, None, None, config) + } +} + +impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> { + /// Create an SPI driver in async mode supporting DMA operations. + #[allow(clippy::too_many_arguments)] + pub fn new( + pio: &mut Common<'d, PIO>, + sm: StateMachine<'d, PIO, SM>, + clk: Peri<'d, impl PioPin>, + mosi: Peri<'d, impl PioPin>, + miso: Peri<'d, impl PioPin>, + tx_dma: Peri<'d, impl Channel>, + rx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + pio, + sm, + clk, + mosi, + miso, + Some(tx_dma.into()), + Some(rx_dma.into()), + config, + ) + } + + /// Read data from SPI using DMA. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + let (rx, tx) = self.sm.rx_tx(); + + let len = buffer.len(); + + let rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = rx.dma_pull(rx_ch, buffer, false); + + let tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = tx.dma_push_repeated::<_, u8>(tx_ch, len); + + join(tx_transfer, rx_transfer).await; + + Ok(()) + } + + /// Write data to SPI using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let (rx, tx) = self.sm.rx_tx(); + + let rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = rx.dma_pull_repeated::<_, u8>(rx_ch, buffer.len()); + + let tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = tx.dma_push(tx_ch, buffer, false); + + join(tx_transfer, rx_transfer).await; + + Ok(()) + } + + /// Transfer data to SPI using DMA. + pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> { + self.transfer_inner(rx_buffer, tx_buffer).await + } + + /// Transfer data in place to SPI using DMA. + pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.transfer_inner(words, words).await + } + + async fn transfer_inner(&mut self, rx_buffer: *mut [u8], tx_buffer: *const [u8]) -> Result<(), Error> { + let (rx, tx) = self.sm.rx_tx(); + + let mut rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = async { + rx.dma_pull(rx_ch.reborrow(), unsafe { &mut *rx_buffer }, false).await; + + if tx_buffer.len() > rx_buffer.len() { + let read_bytes_len = tx_buffer.len() - rx_buffer.len(); + + rx.dma_pull_repeated::<_, u8>(rx_ch, read_bytes_len).await; + } + }; + + let mut tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = async { + tx.dma_push(tx_ch.reborrow(), unsafe { &*tx_buffer }, false).await; + + if rx_buffer.len() > tx_buffer.len() { + let write_bytes_len = rx_buffer.len() - tx_buffer.len(); + + tx.dma_push_repeated::<_, u8>(tx_ch, write_bytes_len).await; + } + }; + + join(tx_transfer, rx_transfer).await; + + Ok(()) + } +} + +// ==================== + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_02::blocking::spi::Transfer for Spi<'d, PIO, SM, M> { + type Error = Error; + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.blocking_transfer_in_place(words)?; + Ok(words) + } +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_02::blocking::spi::Write for Spi<'d, PIO, SM, M> { + type Error = Error; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } +} + +impl embedded_hal_1::spi::Error for Error { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + match *self {} + } +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_1::spi::ErrorType for Spi<'d, PIO, SM, M> { + type Error = Error; +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_1::spi::SpiBus for Spi<'d, PIO, SM, M> { + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer(words, &[]) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.blocking_transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer_in_place(words) + } +} + +impl<'d, PIO: Instance, const SM: usize> embedded_hal_async::spi::SpiBus for Spi<'d, PIO, SM, Async> { + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await + } +} 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 @@ +//! This example shows how to use a PIO state machine as an additional SPI +//! (Serial Peripheral Interface) on the RP2040 chip. No specific hardware is +//! specified in this example. +//! +//! If you connect pin 6 and 7 you should get the same data back. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio_programs::spi::Spi; +use embassy_rp::spi::Config; +use embassy_rp::{bind_interrupts, pio}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => pio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // These pins are routed to different hardware SPI peripherals, but we can + // use them together regardless + let mosi = p.PIN_6; // SPI0 SCLK + let miso = p.PIN_7; // SPI0 MOSI + let clk = p.PIN_8; // SPI1 MISO + + let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs); + + // Construct an SPI driver backed by a PIO state machine + let mut spi = Spi::new_blocking(&mut common, sm0, clk, mosi, miso, Config::default()); + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + + spi.blocking_transfer(&mut rx_buf, &tx_buf).unwrap(); + info!("{:?}", rx_buf); + + Timer::after_secs(1).await; + } +} 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 @@ +//! This example shows how to use a PIO state machine as an additional SPI +//! (Serial Peripheral Interface) on the RP2040 chip. No specific hardware is +//! specified in this example. +//! +//! If you connect pin 6 and 7 you should get the same data back. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio_programs::spi::Spi; +use embassy_rp::spi::Config; +use embassy_rp::{bind_interrupts, pio}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => pio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // These pins are routed to different hardware SPI peripherals, but we can + // use them together regardless + let mosi = p.PIN_6; // SPI0 SCLK + let miso = p.PIN_7; // SPI0 MOSI + let clk = p.PIN_8; // SPI1 MISO + + let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs); + + // Construct an SPI driver backed by a PIO state machine + let mut spi = Spi::new( + &mut common, + sm0, + clk, + mosi, + miso, + p.DMA_CH0, + p.DMA_CH1, + Config::default(), + ); + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + info!("{:?}", rx_buf); + + Timer::after_secs(1).await; + } +}