Created
December 18, 2021 12:12
-
-
Save 9names/c4d61450690c0fb33a06ed27e2c21fc2 to your computer and use it in GitHub Desktop.
RP2040 DMA: SPI tx on one peripheral, rx on the second
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! 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]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! # 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