From 4cac3ac1d24d6b651d79bfca8401824c28f5102c Mon Sep 17 00:00:00 2001 From: Adrian Wowk Date: Wed, 2 Jul 2025 20:35:18 -0500 Subject: rp: add new pio spi program This commit adds a new PIO program which implements SPI. This allows you to drive more than 2 SPI buses by using PIO state machines as additional duplex SPI interfaces. The driver supports both blocking and async modes of operation and exclusively uses the DMA for async IO. --- embassy-rp/src/pio_programs/mod.rs | 1 + embassy-rp/src/pio_programs/spi.rs | 433 +++++++++++++++++++++++++++++++++++++ 2 files changed, 434 insertions(+) create mode 100644 embassy-rp/src/pio_programs/spi.rs (limited to 'embassy-rp/src/pio_programs') 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..27d401fc9 --- /dev/null +++ b/embassy-rp/src/pio_programs/spi.rs @@ -0,0 +1,433 @@ +//! 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, types::extra::U8}; + +use crate::{ + clocks::clk_sys_freq, + dma::{AnyChannel, Channel}, + gpio::Level, + pio::{Common, Direction, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}, + spi::{Async, Blocking, Mode}, +}; + +/// This struct represents a uart tx program loaded into pio instruction memory. +pub struct PioSpiProgram<'d, PIO: crate::pio::Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: crate::pio::Instance> PioSpiProgram<'d, PIO> { + /// Load the spi program into the given pio + pub fn new(common: &mut crate::pio::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 + // + // Autopush and autopull 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 behaviour 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 deasserted) + mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping) + in pins, 1 side 0 ; Input data, deassert SCK + "# + ); + + common.load_program(&prg.program) + } + }; + + Self { prg } + } +} + +/// 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. +/// +/// This driver is less flexible than the hardware backed one. Configuration can +/// not be changed at runtime. +pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> { + sm: StateMachine<'d, PIO, SM>, + tx_dma: Option>, + rx_dma: Option>, + phantom: PhantomData, +} + +/// PIO SPI configuration. +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + /// Frequency (Hz). + pub frequency: u32, + /// Polarity. + pub polarity: Polarity, +} + +impl Default for Config { + fn default() -> Self { + Self { + frequency: 1_000_000, + polarity: Polarity::IdleLow, + } + } +} + +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>, + program: &PioSpiProgram<'d, PIO>, + config: Config, + ) -> Self { + 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); + } + + 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]); + + 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; + + let sys_freq = clk_sys_freq().to_fixed::>(); + let target_freq = (config.frequency * 4).to_fixed::>(); + cfg.clock_divider = (sys_freq / target_freq).to_fixed(); + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { + sm, + 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(()) + } +} + +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>, + program: &PioSpiProgram<'d, PIO>, + config: Config, + ) -> Self { + Self::new_inner(pio, sm, clk, mosi, miso, None, None, program, 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>, + program: &PioSpiProgram<'d, PIO>, + config: Config, + ) -> Self { + Self::new_inner( + pio, + sm, + clk, + mosi, + miso, + Some(tx_dma.into()), + Some(rx_dma.into()), + program, + 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 + } +} -- cgit From 83b42e0db620b7fc2364763b452b346b711e8d9f Mon Sep 17 00:00:00 2001 From: Adrian Wowk Date: Wed, 2 Jul 2025 21:18:44 -0500 Subject: style: cleanup with rustfmt --- embassy-rp/src/pio_programs/spi.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'embassy-rp/src/pio_programs') diff --git a/embassy-rp/src/pio_programs/spi.rs b/embassy-rp/src/pio_programs/spi.rs index 27d401fc9..a1b36c1c8 100644 --- a/embassy-rp/src/pio_programs/spi.rs +++ b/embassy-rp/src/pio_programs/spi.rs @@ -5,24 +5,23 @@ 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, types::extra::U8}; +use fixed::traits::ToFixed; +use fixed::types::extra::U8; -use crate::{ - clocks::clk_sys_freq, - dma::{AnyChannel, Channel}, - gpio::Level, - pio::{Common, Direction, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}, - spi::{Async, Blocking, Mode}, -}; +use crate::clocks::clk_sys_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::Level; +use crate::pio::{Common, Direction, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}; +use crate::spi::{Async, Blocking, Mode}; /// This struct represents a uart tx program loaded into pio instruction memory. -pub struct PioSpiProgram<'d, PIO: crate::pio::Instance> { +pub struct PioSpiProgram<'d, PIO: Instance> { prg: LoadedProgram<'d, PIO>, } -impl<'d, PIO: crate::pio::Instance> PioSpiProgram<'d, PIO> { +impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> { /// Load the spi program into the given pio - pub fn new(common: &mut crate::pio::Common<'d, PIO>, phase: Phase) -> Self { + 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) -- cgit From 62ff0194f4b7413b17dbc69813ec205638248aa7 Mon Sep 17 00:00:00 2001 From: Adrian Wowk Date: Fri, 18 Jul 2025 19:19:27 -0500 Subject: rp: add pio spi runtime reconfiguration --- embassy-rp/src/pio_programs/spi.rs | 119 +++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 39 deletions(-) (limited to 'embassy-rp/src/pio_programs') diff --git a/embassy-rp/src/pio_programs/spi.rs b/embassy-rp/src/pio_programs/spi.rs index a1b36c1c8..6b97cd0f3 100644 --- a/embassy-rp/src/pio_programs/spi.rs +++ b/embassy-rp/src/pio_programs/spi.rs @@ -11,12 +11,13 @@ 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, PioPin, ShiftDirection, StateMachine}; -use crate::spi::{Async, Blocking, Mode}; +use crate::pio::{Common, Direction, Instance, LoadedProgram, Pin, PioPin, ShiftDirection, StateMachine}; +use crate::spi::{Async, Blocking, Config, Mode}; -/// This struct represents a uart tx program loaded into pio instruction memory. -pub struct PioSpiProgram<'d, PIO: Instance> { +/// 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> { @@ -30,11 +31,11 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> { // - MOSI is OUT pin 0 // - MISO is IN pin 0 // - // Autopush and autopull must be enabled, and the serial frame size is set by + // 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 behaviour of RP2040's IO fabric. + // picking behavior of RP2040's IO fabric. let prg = match phase { Phase::CaptureOnFirstTransition => { @@ -60,9 +61,9 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> { ; 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 deasserted) + 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, deassert SCK + in pins, 1 side 0 ; Input data, de-assert SCK "# ); @@ -70,7 +71,7 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> { } }; - Self { prg } + Self { prg, phase } } } @@ -83,35 +84,19 @@ pub enum Error { } /// PIO based Spi driver. -/// -/// This driver is less flexible than the hardware backed one. Configuration can -/// not be changed at runtime. +/// 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, } -/// PIO SPI configuration. -#[non_exhaustive] -#[derive(Clone)] -pub struct Config { - /// Frequency (Hz). - pub frequency: u32, - /// Polarity. - pub polarity: Polarity, -} - -impl Default for Config { - fn default() -> Self { - Self { - frequency: 1_000_000, - polarity: Polarity::IdleLow, - } - } -} - impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { #[allow(clippy::too_many_arguments)] fn new_inner( @@ -122,9 +107,10 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { miso_pin: Peri<'d, impl PioPin>, tx_dma: Option>, rx_dma: Option>, - program: &PioSpiProgram<'d, PIO>, 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); @@ -153,15 +139,16 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { cfg.shift_out.direction = ShiftDirection::Left; cfg.shift_out.threshold = 8; - let sys_freq = clk_sys_freq().to_fixed::>(); - let target_freq = (config.frequency * 4).to_fixed::>(); - cfg.clock_divider = (sys_freq / target_freq).to_fixed(); + cfg.clock_divider = calculate_clock_divider(config.frequency); sm.set_config(&cfg); sm.set_enable(true); Self { sm, + program: Some(program), + cfg, + clk_pin, tx_dma, rx_dma, phantom: PhantomData, @@ -242,6 +229,63 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { 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> { @@ -252,10 +296,9 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> { clk: Peri<'d, impl PioPin>, mosi: Peri<'d, impl PioPin>, miso: Peri<'d, impl PioPin>, - program: &PioSpiProgram<'d, PIO>, config: Config, ) -> Self { - Self::new_inner(pio, sm, clk, mosi, miso, None, None, program, config) + Self::new_inner(pio, sm, clk, mosi, miso, None, None, config) } } @@ -270,7 +313,6 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> { miso: Peri<'d, impl PioPin>, tx_dma: Peri<'d, impl Channel>, rx_dma: Peri<'d, impl Channel>, - program: &PioSpiProgram<'d, PIO>, config: Config, ) -> Self { Self::new_inner( @@ -281,7 +323,6 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> { miso, Some(tx_dma.into()), Some(rx_dma.into()), - program, config, ) } -- cgit From 51373065752693c1e49d8d9df7c4452d0f62b7f3 Mon Sep 17 00:00:00 2001 From: Adrian Wowk Date: Wed, 20 Aug 2025 13:40:14 -0500 Subject: rp: move pio pin configs after set_config This is needed for the program to work correctly on rp235xb when using the higher pin numbers. --- embassy-rp/src/pio_programs/spi.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'embassy-rp/src/pio_programs') diff --git a/embassy-rp/src/pio_programs/spi.rs b/embassy-rp/src/pio_programs/spi.rs index 6b97cd0f3..b10fc6628 100644 --- a/embassy-rp/src/pio_programs/spi.rs +++ b/embassy-rp/src/pio_programs/spi.rs @@ -121,10 +121,6 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { clk_pin.set_output_inversion(false); } - 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]); - let mut cfg = crate::pio::Config::default(); cfg.use_program(&program.prg, &[&clk_pin]); @@ -142,6 +138,11 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { 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 { -- cgit