Skip to content

Instantly share code, notes, and snippets.

@9names
Created December 18, 2021 12:12
Show Gist options
  • Save 9names/c4d61450690c0fb33a06ed27e2c21fc2 to your computer and use it in GitHub Desktop.
Save 9names/c4d61450690c0fb33a06ed27e2c21fc2 to your computer and use it in GitHub Desktop.
RP2040 DMA: SPI tx on one peripheral, rx on the second
//! Serial Peripheral Interface (SPI)
//!
//! See [Chapter 4 Section 4](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details
//!
//! ## Usage
//!
//! ```no_run
//! use embedded_hal::spi::MODE_0;
//! use embedded_time::rate::*;
//! use rp2040_hal::{spi::Spi, gpio::{Pins, FunctionSpi}, pac, sio::Sio};
//!
//! let mut peripherals = pac::Peripherals::take().unwrap();
//! let sio = Sio::new(peripherals.SIO);
//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS);
//!
//! let _ = pins.gpio2.into_mode::<FunctionSpi>();
//! let _ = pins.gpio3.into_mode::<FunctionSpi>();
//!
//! let spi = Spi::<_, _, 8>::new(peripherals.SPI0).init(&mut peripherals.RESETS, 125_000_000u32.Hz(), 16_000_000u32.Hz(), &MODE_0);
//! ```
use crate::dma::{EndlessReadTarget, EndlessWriteTarget, ReadTarget, WriteTarget};
use crate::resets::SubsystemReset;
use core::{convert::Infallible, marker::PhantomData, ops::Deref};
#[cfg(feature = "eh1_0_alpha")]
use eh1_0_alpha::spi as eh1;
use embedded_hal::blocking::spi;
use embedded_hal::spi::{FullDuplex, Mode, Phase, Polarity};
use embedded_time::rate::*;
use pac::RESETS;
/// State of the SPI
pub trait State {}
/// Spi is disabled
pub struct Disabled {
__private: (),
}
/// Spi is enabled
pub struct Enabled {
__private: (),
}
impl State for Disabled {}
impl State for Enabled {}
/// Pac SPI device
pub trait SpiDevice: Deref<Target = pac::spi0::RegisterBlock> + SubsystemReset {
/// The DREQ number for which TX DMA requests are triggered.
fn tx_dreq() -> u8;
/// The DREQ number for which RX DMA requests are triggered.
fn rx_dreq() -> u8;
}
impl SpiDevice for pac::SPI0 {
fn tx_dreq() -> u8 {
16
}
fn rx_dreq() -> u8 {
17
}
}
impl SpiDevice for pac::SPI1 {
fn tx_dreq() -> u8 {
18
}
fn rx_dreq() -> u8 {
19
}
}
/// Data size used in spi
pub trait DataSize {}
impl DataSize for u8 {}
impl DataSize for u16 {}
/// Spi
pub struct Spi<S: State, D: SpiDevice, const DS: u8> {
device: D,
state: PhantomData<S>,
}
impl<S: State, D: SpiDevice, const DS: u8> Spi<S, D, DS> {
fn transition<To: State>(self, _: To) -> Spi<To, D, DS> {
Spi {
device: self.device,
state: PhantomData,
}
}
/// Releases the underlying device.
pub fn free(self) -> D {
self.device
}
/// Set baudrate based on peripheral clock
///
/// Typically the peripheral clock is set to 125_000_000
pub fn set_baudrate<F: Into<Hertz<u32>>, B: Into<Hertz<u32>>>(
&mut self,
peri_frequency: F,
baudrate: B,
) -> Hertz {
let freq_in = peri_frequency.into().integer();
let baudrate = baudrate.into().integer();
let mut prescale: u8 = u8::MAX;
let mut postdiv: u8 = 1;
// Find smallest prescale value which puts output frequency in range of
// post-divide. Prescale is an even number from 2 to 254 inclusive.
for prescale_option in (2u32..=254).step_by(2) {
// We need to use an saturating_mul here because with a high baudrate certain invalid prescale
// values might not fit in u32. However we can be sure those values exeed the max sys_clk frequency
// So clamping a u32::MAX is fine here...
if freq_in < ((prescale_option + 2) * 256).saturating_mul(baudrate) {
prescale = prescale_option as u8;
break;
}
}
// We might not find a prescale value that lowers the clock freq enough, so we leave it at max
debug_assert_ne!(prescale, u8::MAX);
// Find largest post-divide which makes output <= baudrate. Post-divide is
// an integer in the range 1 to 256 inclusive.
for postdiv_option in (0..=255u8).rev() {
if freq_in / (prescale as u32 * postdiv_option as u32) > baudrate {
postdiv = postdiv_option;
break;
}
}
self.device
.sspcpsr
.write(|w| unsafe { w.cpsdvsr().bits(prescale) });
self.device
.sspcr0
.modify(|_, w| unsafe { w.scr().bits(postdiv) });
// Return the frequency we were able to achieve
(freq_in / (prescale as u32 * (1 + postdiv as u32))).Hz()
}
}
impl<D: SpiDevice, const DS: u8> Spi<Disabled, D, DS> {
/// Create new spi device
pub fn new(device: D) -> Spi<Disabled, D, DS> {
Spi {
device,
state: PhantomData,
}
}
/// Set format and datasize
fn set_format(&mut self, data_bits: u8, mode: &Mode) {
self.device.sspcr0.modify(|_, w| unsafe {
w.dss()
.bits(data_bits - 1)
.spo()
.bit(mode.polarity == Polarity::IdleHigh)
.sph()
.bit(mode.phase == Phase::CaptureOnSecondTransition)
});
}
/// Initialize the SPI
pub fn init<F: Into<Hertz<u32>>, B: Into<Hertz<u32>>>(
mut self,
resets: &mut RESETS,
peri_frequency: F,
baudrate: B,
mode: &Mode,
) -> Spi<Enabled, D, DS> {
self.device.reset_bring_down(resets);
self.device.reset_bring_up(resets);
self.set_baudrate(peri_frequency, baudrate);
self.set_format(DS as u8, mode);
// Always enable DREQ signals -- harmless if DMA is not listening
self.device
.sspdmacr
.modify(|_, w| w.txdmae().set_bit().rxdmae().set_bit());
// Finally enable the SPI
self.device.sspcr1.modify(|_, w| w.sse().set_bit());
self.transition(Enabled { __private: () })
}
/// Initialize the SPI in peripheral (non-controller) mode
pub fn init_peripheral<F: Into<Hertz<u32>>, B: Into<Hertz<u32>>>(
mut self,
resets: &mut RESETS,
peri_frequency: F,
baudrate: B,
mode: &Mode,
) -> Spi<Enabled, D, DS> {
self.device.reset_bring_down(resets);
self.device.reset_bring_up(resets);
self.set_baudrate(peri_frequency, baudrate);
self.set_format(DS as u8, mode);
// Set device in slave mode
self.device.sspcr1.modify(|_, w| w.ms().set_bit());
// Always enable DREQ signals -- harmless if DMA is not listening
self.device
.sspdmacr
.modify(|_, w| w.txdmae().set_bit().rxdmae().set_bit());
// Finally enable the SPI
self.device.sspcr1.modify(|_, w| w.sse().set_bit());
self.transition(Enabled { __private: () })
}
}
impl<D: SpiDevice, const DS: u8> Spi<Enabled, D, DS> {
fn is_writable(&self) -> bool {
self.device.sspsr.read().tnf().bit_is_set()
}
fn is_readable(&self) -> bool {
self.device.sspsr.read().rne().bit_is_set()
}
/// Disable the spi to reset its configuration
pub fn disable(self) -> Spi<Disabled, D, DS> {
self.device.sspcr1.modify(|_, w| w.sse().clear_bit());
self.transition(Disabled { __private: () })
}
}
macro_rules! impl_write {
($type:ident, [$($nr:expr),+]) => {
$(
impl<D: SpiDevice> FullDuplex<$type> for Spi<Enabled, D, $nr> {
type Error = Infallible;
fn read(&mut self) -> Result<$type, nb::Error<Infallible>> {
if !self.is_readable() {
return Err(nb::Error::WouldBlock);
}
Ok(self.device.sspdr.read().data().bits() as $type)
}
fn send(&mut self, word: $type) -> Result<(), nb::Error<Infallible>> {
// Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX
// is full, PL022 inhibits RX pushes, and sets a sticky flag on
// push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set.
if !self.is_writable() {
return Err(nb::Error::WouldBlock);
}
self.device
.sspdr
.write(|w| unsafe { w.data().bits(word as u16) });
Ok(())
}
}
impl<D: SpiDevice> spi::write::Default<$type> for Spi<Enabled, D, $nr> {}
impl<D: SpiDevice> spi::transfer::Default<$type> for Spi<Enabled, D, $nr> {}
impl<D: SpiDevice> spi::write_iter::Default<$type> for Spi<Enabled, D, $nr> {}
#[cfg(feature = "eh1_0_alpha")]
impl<D: SpiDevice> eh1::nb::FullDuplex<$type> for Spi<Enabled, D, $nr> {
type Error = Infallible;
fn read(&mut self) -> Result<$type, nb::Error<Infallible>> {
if !self.is_readable() {
return Err(nb::Error::WouldBlock);
}
Ok(self.device.sspdr.read().data().bits() as $type)
}
fn write(&mut self, word: $type) -> Result<(), nb::Error<Infallible>> {
// Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX
// is full, PL022 inhibits RX pushes, and sets a sticky flag on
// push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set.
if !self.is_writable() {
return Err(nb::Error::WouldBlock);
}
self.device
.sspdr
.write(|w| unsafe { w.data().bits(word as u16) });
Ok(())
}
}
impl<D: SpiDevice> ReadTarget for Spi<Enabled, D, $nr> {
type ReceivedWord = $type;
fn rx_treq() -> Option<u8> {
Some(D::rx_dreq())
}
fn rx_address_count(&self) -> (u32, u32) {
(
&self.device.sspdr as *const _ as u32,
u32::MAX,
)
}
fn rx_increment(&self) -> bool {
false
}
}
impl<D: SpiDevice> EndlessReadTarget for Spi<Enabled, D, $nr> {}
impl<D: SpiDevice> WriteTarget for Spi<Enabled, D, $nr> {
type TransmittedWord = $type;
fn tx_treq() -> Option<u8> {
Some(D::tx_dreq())
}
fn tx_address_count(&mut self) -> (u32, u32) {
(
&self.device.sspdr as *const _ as u32,
u32::MAX,
)
}
fn tx_increment(&self) -> bool {
false
}
}
impl<D: SpiDevice> EndlessWriteTarget for Spi<Enabled, D, $nr> {}
)+
};
}
impl_write!(u8, [4, 5, 6, 7, 8]);
impl_write!(u16, [9, 10, 11, 22, 13, 14, 15, 16]);
//! # SPI DMA Example
//!
//! This application demonstrates how to use DMA for SPI transfers.
//!
//! The application expects the SPI0 and SPI1 to be wired together to check
//! whether the data was sent and received correctly.
//!
//! See the `Cargo.toml` file for Copyright and licence details.
#![no_std]
#![no_main]
use core::ops::Deref;
use cortex_m::singleton;
use cortex_m_rt::entry;
use defmt::debug;
use defmt_rtt as _;
use embedded_time::rate::Extensions;
use hal::dma::{DMAExt, SingleBufferingConfig};
use hal::pac;
use panic_probe as _;
use rp2040_hal as hal;
use rp2040_hal::clocks::Clock;
/// The linker will place this boot block at the start of our program image. We
/// need this to help the ROM bootloader get our code up and running.
#[link_section = ".boot2"]
#[used]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER;
/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust
/// if your board has a different frequency
const XTAL_FREQ_HZ: u32 = 12_000_000u32;
#[entry]
fn main() -> ! {
let mut pac = pac::Peripherals::take().unwrap();
// Setup clocks and the watchdog.
let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG);
let clocks = hal::clocks::init_clocks_and_plls(
XTAL_FREQ_HZ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// Setup the pins.
let sio = hal::sio::Sio::new(pac.SIO);
let pins = hal::gpio::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
defmt::debug!("We're running!");
// These are implicitly used by the spi driver if they are in the correct mode
let _spi_sclk = pins.gpio6.into_mode::<hal::gpio::FunctionSpi>();
let _spi_mosi = pins.gpio7.into_mode::<hal::gpio::FunctionSpi>();
// Return data not used in this example
//let _spi_miso = pins.gpio4.into_mode::<hal::gpio::FunctionSpi>();
let _spi_ss = pins.gpio5.into_mode::<hal::gpio::FunctionSpi>();
let spi0 = hal::spi::Spi::<_, _, 8>::new(pac.SPI0);
let _spi1_sclk = pins.gpio10.into_mode::<hal::gpio::FunctionSpi>();
// Note: MOSI and MISO swapped when in Peripheral mode (master bit disabled)
// Not transmitting from SPI1
// let _spi1_mosi = pins.gpio11.into_mode::<hal::gpio::FunctionSpi>();
let _spi1_miso = pins.gpio12.into_mode::<hal::gpio::FunctionSpi>();
let _spi1_ss = pins.gpio13.into_mode::<hal::gpio::FunctionSpi>();
let spi1 = hal::spi::Spi::<_, _, 8>::new(pac.SPI1);
// Exchange the uninitialised SPI driver for an initialised one
let spi0 = spi0.init(
&mut pac.RESETS,
clocks.peripheral_clock.freq(),
1_000_000u32.Hz(),
&embedded_hal::spi::MODE_0,
);
// Exchange the uninitialised SPI driver for an initialised one
let spi1 = spi1.init_peripheral(
&mut pac.RESETS,
clocks.peripheral_clock.freq(),
16_000_000u32.Hz(),
&embedded_hal::spi::MODE_0,
);
// Initialize DMA.
let dma = pac.DMA.split(&mut pac.RESETS);
// Use DMA to transfer some bytes (single buffering).
let tx_buf = singleton!(: [u8; 16] = [0x40, 0x50, 0x60, 0x70, 0x45, 0x55, 0x65, 0x75, 0x80,0x90, 0xa0, 0xb0, 0x85, 0x95, 0xa5, 0xb5
]).unwrap();
let rx_buf = singleton!(: [u8; 16] = [0xA0; 16]).unwrap();
debug!("Start tx");
let transfer_rx = SingleBufferingConfig::new(dma.ch1, spi1, rx_buf).start();
debug!("Start rx");
let transfer_tx = SingleBufferingConfig::new(dma.ch0, tx_buf, spi0).start();
debug!("wait tx");
let (_ch0, tx_buf, _spi0) = transfer_tx.wait();
debug!("wait rx");
let (_ch1, _spi1, rx_buf) = transfer_rx.wait();
debug!("compare data");
for i in 0..rx_buf.deref().len() {
if rx_buf[i] != tx_buf[i] {
debug!(
"rx_buf[i] ({}) != tx_buf[i] {} where i=={}",
rx_buf[i], tx_buf[i], i
);
// TODO: Signal failure using an LED.
} else {
debug!("buffers agree on data")
}
}
debug!("done");
// Use DMA to continuously transfer data (double buffering).
// TODO
#[allow(clippy::empty_loop)]
loop {
// Empty loop
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment