Add SPI support to the HAL
This commit is contained in:
Hmvp 2021-07-06 01:42:05 +02:00 committed by GitHub
parent 711c0230b1
commit 1c6a336104
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 223 additions and 2 deletions

View file

@ -5,6 +5,7 @@ use rp2040_pac as pac;
mod private {
pub trait SubsystemReset {
fn reset_bring_up(&self, resets: &mut pac::RESETS);
fn reset_bring_down(&self, resets: &mut pac::RESETS);
}
}
@ -17,6 +18,9 @@ macro_rules! generate_reset {
resets.reset.modify(|_, w| w.$module().clear_bit());
while resets.reset_done.read().$module().bit_is_clear() {}
}
fn reset_bring_down(&self, resets: &mut pac::RESETS) {
resets.reset.modify(|_, w| w.$module().set_bit());
}
}
};
}

View file

@ -1,3 +1,220 @@
//! Serial Peripheral Interface (SPI)
// See [Chapter 4 Section 4](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details
// TODO
//!
//! See [Chapter 4 Section 4](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details
//!
//! ## Usage
//!
//! ```rust
//! let sio = Sio::new(p.SIO);
//! let gpio_pins = Pins::new(p.IO_BANK0, p.PADS_BANK0, sio.gpio_bank0, &mut p.RESETS);
//!
//! let _ = gpio_pins.spi_sclk.into_mode::<FunctionSpi>();
//! let _ = gpio_pins.spi_mosi.into_mode::<FunctionSpi>();
//!
//! let spi = Spi::<_, _, 8>::new(p.SPI0).init(&mut p.RESETS, 125_000_000.Hz(), 16_000_000.Hz(), &MODE_0);
//! ```
use crate::resets::SubsystemReset;
use core::{convert::Infallible, marker::PhantomData, ops::Deref};
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 {}
impl SpiDevice for pac::SPI0 {}
impl SpiDevice for pac::SPI1 {}
/// 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
}
}
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 baudrate based on peripheral clock
///
/// Typically the peripheral clock is set to 125_000_000
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 (1..=256u32).rev() {
if freq_in / (prescale as u32 * (postdiv_option - 1)) > baudrate {
postdiv = postdiv_option as u8;
break;
}
}
self.device
.sspcpsr
.write(|w| unsafe { w.cpsdvsr().bits(prescale) });
self.device
.sspcr0
.modify(|_, w| unsafe { w.scr().bits(postdiv - 1) });
// Return the frequency we were able to achieve
(freq_in / (prescale as u32 * postdiv as u32)).Hz()
}
/// 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: () })
}
}
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> {}
)+
};
}
impl_write!(u8, [4, 5, 6, 7, 8]);
impl_write!(u16, [9, 10, 11, 22, 13, 14, 15, 16]);