Merge pull request #4639 from embassy-rs/rp-pio-spi

rp: add PIO SPI
This commit is contained in:
Dario Nieuwenhuis 2025-09-05 19:00:01 +00:00 committed by GitHub
commit bbcf9af87e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 656 additions and 10 deletions

View File

@ -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 {

View File

@ -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;

View File

@ -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<PioSpiProgram<'d, PIO>>,
clk_pin: Pin<'d, PIO>,
tx_dma: Option<Peri<'d, AnyChannel>>,
rx_dma: Option<Peri<'d, AnyChannel>>,
phantom: PhantomData<M>,
}
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<Peri<'d, AnyChannel>>,
rx_dma: Option<Peri<'d, AnyChannel>>,
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<u8, Error> {
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<U8> {
// we multiply by 4 since each clock period is equal to 4 instructions
let sys_freq = clk_sys_freq().to_fixed::<fixed::FixedU64<U8>>();
let target_freq = (frequency_hz * 4).to_fixed::<fixed::FixedU64<U8>>();
(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<u8> 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<u8> 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<u8> 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<u8> 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
}
}

View File

@ -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<PIO0>;
});
#[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;
}
}

View File

@ -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<PIO0>;
});
#[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;
}
}