diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index 875859182e..d7f07b1c89 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -81,19 +81,25 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { tx_dma: Option>, rx_dma: Option>, config: Config, + slave: bool, ) -> Self { - Self::apply_config(&inner, &config); - let p = inner.regs(); + // disable (to ensure correct master/slave bit) + p.cr1().write(|w| w.set_sse(false)); + // Its necessary to disable SPI before setting slave! + p.cr1().modify(|w| w.set_ms(slave)); + + Self::apply_config(&inner, &config); + // Always enable DREQ signals -- harmless if DMA is not listening p.dmacr().write(|reg| { reg.set_rxdmae(true); reg.set_txdmae(true); }); - // finally, enable. - p.cr1().write(|w| w.set_sse(true)); + // finally, enable (preserving MS bit) + p.cr1().modify(|w| w.set_sse(true)); if let Some(pin) = &clk { pin.gpio().ctrl().write(|w| w.set_funcsel(1)); @@ -240,8 +246,8 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { pub fn set_frequency(&mut self, freq: u32) { let (presc, postdiv) = calc_prescs(freq); let p = self.inner.regs(); - // disable - p.cr1().write(|w| w.set_sse(false)); + // disable (preserving MS bit) + p.cr1().modify(|w| w.set_sse(false)); // change stuff p.cpsr().write(|w| w.set_cpsdvsr(presc)); @@ -249,22 +255,22 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { w.set_scr(postdiv); }); - // enable - p.cr1().write(|w| w.set_sse(true)); + // enable (preserving MS bit) + p.cr1().modify(|w| w.set_sse(true)); } /// Set SPI config. pub fn set_config(&mut self, config: &Config) { let p = self.inner.regs(); - // disable - p.cr1().write(|w| w.set_sse(false)); + // disable (preserving MS bit) + p.cr1().modify(|w| w.set_sse(false)); // change stuff Self::apply_config(&self.inner, config); - // enable - p.cr1().write(|w| w.set_sse(true)); + // enable (preserving MS bit) + p.cr1().modify(|w| w.set_sse(true)); } } @@ -286,6 +292,7 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { None, None, config, + false, ) } @@ -305,12 +312,13 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { None, None, config, + false, ) } /// Create an SPI driver in blocking mode supporting writes only, without SCK pin. pub fn new_blocking_txonly_nosck(inner: Peri<'d, T>, mosi: Peri<'d, impl MosiPin + 'd>, config: Config) -> Self { - Self::new_inner(inner, None, Some(mosi.into()), None, None, None, None, config) + Self::new_inner(inner, None, Some(mosi.into()), None, None, None, None, config, false) } /// Create an SPI driver in blocking mode supporting reads only. @@ -329,6 +337,50 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { None, None, config, + false, + ) + } + + /// Create an SPI driver in blocking slave mode. + pub fn new_blocking_slave( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + cs: Peri<'d, impl CsPin + 'd>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + Some(clk.into()), + Some(mosi.into()), + Some(miso.into()), + Some(cs.into()), + None, + None, + config, + true, + ) + } + + /// Create an SPI driver in blocking slave mode supporting reads only. + pub fn new_blocking_slave_rxonly( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + cs: Peri<'d, impl CsPin + 'd>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + Some(clk.into()), + None, + Some(miso.into()), + Some(cs.into()), + None, + None, + config, + true, ) } } @@ -358,6 +410,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> { Some(tx_dma_ch), Some(rx_dma_ch), config, + false, ) } @@ -380,6 +433,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> { Some(tx_dma_ch), None, config, + false, ) } @@ -402,6 +456,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> { Some(tx_dma_ch), None, config, + false, ) } @@ -428,6 +483,64 @@ impl<'d, T: Instance> Spi<'d, T, Async> { Some(tx_dma_ch), Some(rx_dma_ch), config, + false, + ) + } + + /// Create an SPI driver in async mode supporting DMA operations in slave mode. + pub fn new_slave( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + cs: Peri<'d, impl CsPin + 'd>, + tx_dma: Peri<'d, TxDma>, + rx_dma: Peri<'d, RxDma>, + irq: impl interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + 'd, + config: Config, + ) -> Self { + let tx_dma_ch = dma::Channel::new(tx_dma, irq); + let rx_dma_ch = dma::Channel::new(rx_dma, irq); + Self::new_inner( + inner, + Some(clk.into()), + Some(mosi.into()), + Some(miso.into()), + Some(cs.into()), + Some(tx_dma_ch), + Some(rx_dma_ch), + config, + true, + ) + } + + /// Create an SPI driver in async mode supporting DMA read operations only in slave mode. + pub fn new_slave_rxonly( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + cs: Peri<'d, impl CsPin + 'd>, + tx_dma: Peri<'d, TxDma>, + rx_dma: Peri<'d, RxDma>, + irq: impl interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + 'd, + config: Config, + ) -> Self { + let tx_dma_ch = dma::Channel::new(tx_dma, irq); + let rx_dma_ch = dma::Channel::new(rx_dma, irq); + Self::new_inner( + inner, + Some(clk.into()), + None, + Some(miso.into()), + Some(cs.into()), + Some(tx_dma_ch), + Some(rx_dma_ch), + config, + true, ) } diff --git a/examples/rp/src/bin/spi_slave_async.rs b/examples/rp/src/bin/spi_slave_async.rs new file mode 100644 index 0000000000..ae8acaf417 --- /dev/null +++ b/examples/rp/src/bin/spi_slave_async.rs @@ -0,0 +1,113 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) master and slave in the RP2040 chip. +//! Note: By default the SPI slave on the RP2040 requires CS to be toggled for each byte +//! +//! +//! No specific hardware is specified in this example. +//! Connect the following pins: +//! Master (MISO) PIN 12 <- PIN 3 (MOSI) Slave +//! Master (MOSI) PIN 11 -> PIN 0 (MISO) Slave +//! Master (CLK) PIN 10 -> PIN 2 (CLK) Slave +//! Master (CS) PIN 13 -> PIN 1 (CS) Slave +//! +//! You should recieve the tx in rx of the other. +//! Sample output: +//! 0.008 [INFO ] Master: TX: [1, 2, 3, 4, 5, 6] RX: [a, b, c, d, e, f] +//! 0.009 [INFO ] Slave: TX: [a, b, c, d, e, f] RX: [1, 2, 3, 4, 5, 6] + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, DMA_CH1, DMA_CH2, DMA_CH3}; +use embassy_rp::spi::{Config, Spi}; +use embassy_rp::{bind_interrupts, dma}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + DMA_IRQ_0 => dma::InterruptHandler, dma::InterruptHandler, dma::InterruptHandler, dma::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Starting up!"); + + // Master wiring on SPI1 + let master_miso = p.PIN_12; + let master_mosi = p.PIN_11; + let master_clk = p.PIN_10; + let master_cs = Output::new(p.PIN_13, Level::High); + + // Slave wiring on SPI0 + let slave_miso = p.PIN_0; + let slave_mosi = p.PIN_3; + let slave_clk = p.PIN_2; + let slave_cs_pin = p.PIN_1; + + let master_spi = Spi::new( + p.SPI1, + master_clk, + master_mosi, + master_miso, + p.DMA_CH0, + p.DMA_CH1, + Irqs, + Config::default(), + ); + + // Pull down CS for every byte + let config = Config::default(); + // If you need a single CS pulse for multiple bytes, use the following: + // let mut config = Config::default(); + // config.phase = embassy_rp::spi::Phase::CaptureOnSecondTransition; + // config.polarity = embassy_rp::spi::Polarity::IdleHigh; + let slave_spi = Spi::new_slave( + p.SPI0, + slave_clk, + slave_mosi, + slave_miso, + slave_cs_pin, + p.DMA_CH2, + p.DMA_CH3, + Irqs, + config, + ); + + let master_fut = async move { + let mut spi = master_spi; + let mut cs = master_cs; + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + + // CS needs to be toggled for each byte transfer (for slave work correctly) + for i in 0..tx_buf.len() { + cs.set_low(); + spi.transfer(&mut rx_buf[i..i + 1], &tx_buf[i..i + 1]).await.unwrap(); + cs.set_high(); + } + + info!("Master: TX: {:x} RX: {:x}", tx_buf, rx_buf); + Timer::after_secs(1).await; + } + }; + + let slave_fut = async move { + let mut spi = slave_spi; + let tx_buf = [0xA_u8, 0xB, 0xC, 0xD, 0xE, 0xF]; + let mut rx_buf = [0_u8; 6]; + + loop { + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + info!("Slave: TX: {:x} RX: {:x}", tx_buf, rx_buf); + } + }; + + join(slave_fut, master_fut).await; +} diff --git a/examples/rp235x/src/bin/spi_slave_async.rs b/examples/rp235x/src/bin/spi_slave_async.rs new file mode 100644 index 0000000000..ee4f31a256 --- /dev/null +++ b/examples/rp235x/src/bin/spi_slave_async.rs @@ -0,0 +1,112 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) master and slave in the RP235x chip. +//! Note: By default the SPI slave on the RP2040 requires CS to be toggled for each byte +//! +//! No specific hardware is specified in this example. +//! Connect the following pins: +//! Master (MISO) PIN 12 <- PIN 3 (MOSI) Slave +//! Master (MOSI) PIN 11 -> PIN 0 (MISO) Slave +//! Master (CLK) PIN 10 -> PIN 2 (CLK) Slave +//! Master (CS) PIN 13 -> PIN 1 (CS) Slave +//! +//! You should recieve the tx in rx of the other. +//! Sample output: +//! 0.008 [INFO ] Master: TX: [1, 2, 3, 4, 5, 6] RX: [a, b, c, d, e, f] +//! 0.009 [INFO ] Slave: TX: [a, b, c, d, e, f] RX: [1, 2, 3, 4, 5, 6] + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, DMA_CH1, DMA_CH2, DMA_CH3}; +use embassy_rp::spi::{Config, Spi}; +use embassy_rp::{bind_interrupts, dma}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + DMA_IRQ_0 => dma::InterruptHandler, dma::InterruptHandler, dma::InterruptHandler, dma::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Starting up!"); + + // Master wiring on SPI1 + let master_miso = p.PIN_12; + let master_mosi = p.PIN_11; + let master_clk = p.PIN_10; + let master_cs = Output::new(p.PIN_13, Level::High); + + // Slave wiring on SPI0 + let slave_miso = p.PIN_0; + let slave_mosi = p.PIN_3; + let slave_clk = p.PIN_2; + let slave_cs_pin = p.PIN_1; + + let master_spi = Spi::new( + p.SPI1, + master_clk, + master_mosi, + master_miso, + p.DMA_CH0, + p.DMA_CH1, + Irqs, + Config::default(), + ); + + // Pull down CS for every byte + let config = Config::default(); + // If you need a single CS pulse for multiple bytes, use the following: + // let mut config = Config::default(); + // config.phase = embassy_rp::spi::Phase::CaptureOnSecondTransition; + // config.polarity = embassy_rp::spi::Polarity::IdleHigh; + let slave_spi = Spi::new_slave( + p.SPI0, + slave_clk, + slave_mosi, + slave_miso, + slave_cs_pin, + p.DMA_CH2, + p.DMA_CH3, + Irqs, + config, + ); + + let master_fut = async move { + let mut spi = master_spi; + let mut cs = master_cs; + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + + // CS needs to be toggled for each byte transfer (for slave work correctly) + for i in 0..tx_buf.len() { + cs.set_low(); + spi.transfer(&mut rx_buf[i..i + 1], &tx_buf[i..i + 1]).await.unwrap(); + cs.set_high(); + } + + info!("Master: TX: {:x} RX: {:x}", tx_buf, rx_buf); + Timer::after_secs(1).await; + } + }; + + let slave_fut = async move { + let mut spi = slave_spi; + let tx_buf = [0xA_u8, 0xB, 0xC, 0xD, 0xE, 0xF]; + let mut rx_buf = [0_u8; 6]; + + loop { + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + info!("Slave: TX: {:x} RX: {:x}", tx_buf, rx_buf); + } + }; + + join(slave_fut, master_fut).await; +}