From 4d63a87b75802b15708a8faae0cba7027e0490e8 Mon Sep 17 00:00:00 2001 From: Ales Katona Date: Thu, 29 Jul 2021 23:02:06 -0700 Subject: [PATCH] refactor spi into bus and devices for sharing --- CHANGELOG.md | 2 + Cargo.toml | 2 +- src/spi.rs | 452 ++++-------------------------------- src/spi/bus.rs | 250 ++++++++++++++++++++ src/spi/config.rs | 69 ++++++ src/spi/exclusive_device.rs | 120 ++++++++++ src/spi/shared_bus.rs | 70 ++++++ src/spi/shared_device.rs | 165 +++++++++++++ src/spi/traits.rs | 206 ++++++++++++++++ 9 files changed, 928 insertions(+), 408 deletions(-) create mode 100644 src/spi/bus.rs create mode 100644 src/spi/config.rs create mode 100644 src/spi/exclusive_device.rs create mode 100644 src/spi/shared_bus.rs create mode 100644 src/spi/shared_device.rs create mode 100644 src/spi/traits.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f782c0f..9966133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- Refactored `e310x-hal::spi` module, splitting the abstraction into `SpiBus` and `SpiExclusiveDevice/SpiSharedDevice` to allow multiple devices on a single SPI bus to co-exist + ## [v0.9.3] - 2021-08-15 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 1d64127..e79aed9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "ISC" edition = "2018" [dependencies] -embedded-hal = { version = "0.2.3", features = ["unproven"] } +embedded-hal = { version = "0.2.6", features = ["unproven"] } nb = "1.0.0" riscv = "0.7.0" e310x = { version = "0.9.0", features = ["rt"] } diff --git a/src/spi.rs b/src/spi.rs index 39ff562..d508849 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -9,10 +9,10 @@ //! - MOSI: Pin 3 IOF0 //! - MISO: Pin 4 IOF0 //! - SCK: Pin 5 IOF0 -//! - SS0: Pin 2 IOF0 -//! - SS1: Pin 8 IOF0 (not connected to package in FE310) -//! - SS2: Pin 9 IOF0 -//! - SS3: Pin 10 IOF0 +//! - CS0: Pin 2 IOF0 +//! - CS1: Pin 8 IOF0 (not connected to package in FE310) +//! - CS2: Pin 9 IOF0 +//! - CS3: Pin 10 IOF0 //! - Interrupt::QSPI1 //! //! # QSPI2 @@ -20,407 +20,45 @@ //! - MOSI: Pin 27 IOF0 //! - MISO: Pin 28 IOF0 //! - SCK: Pin 29 IOF0 -//! - SS: Pin 26 IOF0 +//! - CS: Pin 26 IOF0 //! - Interrupt::QSPI2 - -use crate::clock::Clocks; -use crate::time::Hertz; -use core::convert::Infallible; -use core::ops::Deref; -use e310x::qspi0::csmode::MODE_A; -use e310x::{qspi0, QSPI0, QSPI1, QSPI2}; -pub use embedded_hal::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; -use nb; - -/// SPI pins - DO NOT IMPLEMENT THIS TRAIT -/// -/// This trait is implemented for pin tuples (), (MOSI, MISO, SCK) and (MOSI, MISO, SCK, SS) -/// and combinations without MOSI/MISO -pub trait Pins { - #[doc(hidden)] - const CS_INDEX: Option; -} - -/* SPI0 pins */ -impl Pins for () { - const CS_INDEX: Option = Some(0); -} - -/* SPI1 pins */ -mod spi1_impl { - use super::{Pins, QSPI1}; - use crate::gpio::gpio0; - use crate::gpio::{NoInvert, IOF0}; - - type MOSI = gpio0::Pin3>; - type MISO = gpio0::Pin4>; - type SCK = gpio0::Pin5>; - type SS0 = gpio0::Pin2>; - type SS1 = gpio0::Pin8>; - type SS2 = gpio0::Pin9>; - type SS3 = gpio0::Pin10>; - - impl Pins for (MOSI, MISO, SCK) { - const CS_INDEX: Option = None; - } - impl Pins for (MOSI, (), SCK) { - const CS_INDEX: Option = None; - } - impl Pins for ((), MISO, SCK) { - const CS_INDEX: Option = None; - } - impl Pins for (MOSI, MISO, SCK, SS0) { - const CS_INDEX: Option = Some(0); - } - impl Pins for (MOSI, (), SCK, SS0) { - const CS_INDEX: Option = Some(0); - } - impl Pins for ((), MISO, SCK, SS0) { - const CS_INDEX: Option = Some(0); - } - impl Pins for (MOSI, MISO, SCK, SS1) { - const CS_INDEX: Option = Some(1); - } - impl Pins for (MOSI, (), SCK, SS1) { - const CS_INDEX: Option = Some(1); - } - impl Pins for ((), MISO, SCK, SS1) { - const CS_INDEX: Option = Some(1); - } - impl Pins for (MOSI, MISO, SCK, SS2) { - const CS_INDEX: Option = Some(2); - } - impl Pins for (MOSI, (), SCK, SS2) { - const CS_INDEX: Option = Some(2); - } - impl Pins for ((), MISO, SCK, SS2) { - const CS_INDEX: Option = Some(2); - } - impl Pins for (MOSI, MISO, SCK, SS3) { - const CS_INDEX: Option = Some(3); - } - impl Pins for (MOSI, (), SCK, SS3) { - const CS_INDEX: Option = Some(3); - } - impl Pins for ((), MISO, SCK, SS3) { - const CS_INDEX: Option = Some(3); - } -} - -/* SPI2 pins */ -mod spi2_impl { - use super::{Pins, QSPI2}; - use crate::gpio::gpio0; - use crate::gpio::{NoInvert, IOF0}; - - type MOSI = gpio0::Pin27>; - type MISO = gpio0::Pin28>; - type SCK = gpio0::Pin29>; - type SS0 = gpio0::Pin26>; - - impl Pins for (MOSI, MISO, SCK) { - const CS_INDEX: Option = None; - } - impl Pins for (MOSI, (), SCK) { - const CS_INDEX: Option = None; - } - impl Pins for ((), MISO, SCK) { - const CS_INDEX: Option = None; - } - impl Pins for (MOSI, MISO, SCK, SS0) { - const CS_INDEX: Option = Some(0); - } - impl Pins for (MOSI, (), SCK, SS0) { - const CS_INDEX: Option = Some(0); - } - impl Pins for ((), MISO, SCK, SS0) { - const CS_INDEX: Option = Some(0); - } -} - -#[doc(hidden)] -pub trait SpiX: Deref {} -impl SpiX for QSPI0 {} -impl SpiX for QSPI1 {} -impl SpiX for QSPI2 {} - -/// SPI abstraction -pub struct Spi { - spi: SPI, - pins: PINS, -} - -impl Spi { - /// Configures the SPI peripheral to operate in full duplex master mode - /// Defaults to using AUTO CS in FRAME mode if PINS configuration allows it - pub fn new(spi: SPI, pins: PINS, mode: Mode, freq: Hertz, clocks: Clocks) -> Self - where - PINS: Pins, - { - let div = clocks.tlclk().0 / (2 * freq.0) - 1; - assert!(div <= 0xfff); - spi.sckdiv.write(|w| unsafe { w.div().bits(div as u16) }); - - let cs_mode = if let Some(cs_index) = PINS::CS_INDEX { - spi.csid.write(|w| unsafe { w.bits(cs_index) }); - - MODE_A::HOLD // Keep CS continuously asserted after the initial frame - } else { - MODE_A::OFF // Disable hardware control of the CS pin - }; - spi.csmode.write(|w| w.mode().variant(cs_mode)); - - // Set CS pin polarity to high - spi.csdef.reset(); - - // Set SPI mode - let phase = mode.phase == Phase::CaptureOnSecondTransition; - let polarity = mode.polarity == Polarity::IdleHigh; - spi.sckmode - .write(|w| w.pha().bit(phase).pol().bit(polarity)); - - spi.fmt.write(|w| unsafe { - w.proto().single(); - w.endian().big(); // Transmit most-significant bit (MSB) first - w.dir().rx(); - w.len().bits(8) - }); - - // Set watermark levels - spi.txmark.write(|w| unsafe { w.txmark().bits(1) }); - spi.rxmark.write(|w| unsafe { w.rxmark().bits(0) }); - - spi.delay0.reset(); - spi.delay1.reset(); - - Self { spi, pins } - } - - /// Sets transmit watermark level - pub fn set_tx_watermark(&mut self, value: u8) { - self.spi.txmark.write(|w| unsafe { w.txmark().bits(value) }); - } - - /// Sets receive watermark level - pub fn set_rx_watermark(&mut self, value: u8) { - self.spi.rxmark.write(|w| unsafe { w.rxmark().bits(value) }); - } - - /// Returns transmit watermark event status - pub fn tx_wm_is_pending(&self) -> bool { - self.spi.ip.read().txwm().bit() - } - - /// Returns receive watermark event status - pub fn rx_wm_is_pending(&self) -> bool { - self.spi.ip.read().rxwm().bit() - } - - /// Starts listening for transmit watermark interrupt event - pub fn listen_tx_wm(&mut self) { - self.spi.ie.write(|w| w.txwm().set_bit()) - } - - /// Starts listening for receive watermark interrupt event - pub fn listen_rx_wm(&mut self) { - self.spi.ie.write(|w| w.rxwm().set_bit()) - } - - /// Stops listening for transmit watermark interrupt event - pub fn unlisten_tx_wm(&mut self) { - self.spi.ie.write(|w| w.txwm().clear_bit()) - } - - /// Stops listening for receive watermark interrupt event - pub fn unlisten_rx_wm(&mut self) { - self.spi.ie.write(|w| w.rxwm().clear_bit()) - } - - /// Set AUTO CS mode to per-word operation - pub fn cs_mode_word(&mut self) { - if !self.spi.csmode.read().mode().is_off() { - self.spi.csmode.write(|w| w.mode().auto()); - } - } - - /// Set HOLD CS mode to per-frame operation - pub fn cs_mode_frame(&mut self) { - if !self.spi.csmode.read().mode().is_off() { - self.spi.csmode.write(|w| w.mode().hold()); - } - } - - /// Finishes transfer by deasserting CS (only for hardware-controlled CS) - pub fn end_transfer(&mut self) { - self.cs_mode_word() - } - - /// Releases the SPI peripheral and associated pins - pub fn free(self) -> (SPI, PINS) { - (self.spi, self.pins) - } -} - -impl embedded_hal::spi::FullDuplex for Spi { - type Error = Infallible; - - fn read(&mut self) -> nb::Result { - let rxdata = self.spi.rxdata.read(); - - if rxdata.empty().bit_is_set() { - Err(nb::Error::WouldBlock) - } else { - Ok(rxdata.data().bits()) - } - } - - fn send(&mut self, byte: u8) -> nb::Result<(), Infallible> { - let txdata = self.spi.txdata.read(); - - if txdata.full().bit_is_set() { - Err(nb::Error::WouldBlock) - } else { - self.spi.txdata.write(|w| unsafe { w.data().bits(byte) }); - Ok(()) - } - } -} - -impl embedded_hal::blocking::spi::Transfer for Spi { - type Error = Infallible; - - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { - // Ensure that RX FIFO is empty - while self.spi.rxdata.read().empty().bit_is_clear() {} - - self.cs_mode_frame(); - - let mut iwrite = 0; - let mut iread = 0; - while iwrite < words.len() || iread < words.len() { - if iwrite < words.len() && self.spi.txdata.read().full().bit_is_clear() { - let byte = unsafe { words.get_unchecked(iwrite) }; - iwrite += 1; - self.spi.txdata.write(|w| unsafe { w.data().bits(*byte) }); - } - - if iread < iwrite { - let data = self.spi.rxdata.read(); - if data.empty().bit_is_clear() { - unsafe { *words.get_unchecked_mut(iread) = data.data().bits() }; - iread += 1; - } - } - } - - self.cs_mode_word(); - - Ok(words) - } -} - -impl embedded_hal::blocking::spi::Write for Spi { - type Error = Infallible; - - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - // Ensure that RX FIFO is empty - while self.spi.rxdata.read().empty().bit_is_clear() {} - - self.cs_mode_frame(); - - let mut iwrite = 0; - let mut iread = 0; - while iwrite < words.len() || iread < words.len() { - if iwrite < words.len() && self.spi.txdata.read().full().bit_is_clear() { - let byte = unsafe { words.get_unchecked(iwrite) }; - iwrite += 1; - self.spi.txdata.write(|w| unsafe { w.data().bits(*byte) }); - } - - if iread < iwrite { - // Read and discard byte, if any - if self.spi.rxdata.read().empty().bit_is_clear() { - iread += 1; - } - } - } - - self.cs_mode_word(); - - Ok(()) - } -} - -impl embedded_hal::blocking::spi::WriteIter for Spi { - type Error = Infallible; - - fn write_iter(&mut self, words: WI) -> Result<(), Self::Error> - where - WI: IntoIterator, - { - let mut iter = words.into_iter(); - - // Ensure that RX FIFO is empty - while self.spi.rxdata.read().empty().bit_is_clear() {} - - self.cs_mode_frame(); - - let mut read_count = 0; - let mut has_data = true; - while has_data || read_count > 0 { - if has_data && self.spi.txdata.read().full().bit_is_clear() { - if let Some(byte) = iter.next() { - self.spi.txdata.write(|w| unsafe { w.data().bits(byte) }); - read_count += 1; - } else { - has_data = false; - } - } - - if read_count > 0 { - // Read and discard byte, if any - if self.spi.rxdata.read().empty().bit_is_clear() { - read_count -= 1; - } - } - } - - self.cs_mode_word(); - - Ok(()) - } -} - -// Backward compatibility -impl Spi { - /// Configures the SPI peripheral to operate in full duplex master mode - #[deprecated(note = "Please use Spi::new function instead")] - pub fn spi0(spi: QSPI0, pins: PINS, mode: Mode, freq: Hertz, clocks: Clocks) -> Self - where - PINS: Pins, - { - Self::new(spi, pins, mode, freq, clocks) - } -} - -impl Spi { - /// Configures the SPI peripheral to operate in full duplex master mode - #[deprecated(note = "Please use Spi::new function instead")] - pub fn spi1(spi: QSPI1, pins: PINS, mode: Mode, freq: Hertz, clocks: Clocks) -> Self - where - PINS: Pins, - { - Self::new(spi, pins, mode, freq, clocks) - } -} - -impl Spi { - /// Configures the SPI peripheral to operate in full duplex master mode - #[deprecated(note = "Please use Spi::new function instead")] - pub fn spi2(spi: QSPI2, pins: PINS, mode: Mode, freq: Hertz, clocks: Clocks) -> Self - where - PINS: Pins, - { - Self::new(spi, pins, mode, freq, clocks) - } -} +//! +//! # Exclusive Bus usage example +//!``` +//! let pins = (mosi, miso, sck, cs0); +//! let spi_bus = SpiBus::new(p.QSPI1, pins); +//! +//! let spi_config = SpiConfig::new(MODE_0, 100.khz().into(), &clocks); +//! let mut dev = spi_bus.new_device(&spi_config); +//! +//! dev.write(&[1, 2, 3]).unwrap(); +//!``` +//! +//! # Shared Bus usage example +//!``` +//! let pins = (mosi, miso, sck); +//! let spi_bus = SpiBus::shared(p.QSPI1, pins); +//! +//! let spi_config1 = SpiConfig::new(MODE_0, 100.khz().into(), &clocks); +//! let mut dev1 = spi_bus.new_device(cs0, &spi_config1); +//! +//! let spi_config2 = SpiConfig::new(MODE_3, 2.mhz().into(), &clocks); +//! let mut dev2 = spi_bus.new_device(cs1, &spi_config2); +//! +//! dev1.write(&[1, 2, 3]).unwrap(); +//! dev2.write(&[4, 5]).unwrap(); +//!``` + +mod bus; // contains the SPI Bus abstraction +mod config; +mod exclusive_device; // contains the exclusive SPI device abstraction +mod shared_bus; // shared bus newtype +mod shared_device; // contains the shared SPI device abstraction +mod traits; // contains SPI device abstraction + +pub use bus::*; +pub use config::*; +pub use exclusive_device::*; +pub use shared_bus::*; +pub use shared_device::*; +pub use traits::*; diff --git a/src/spi/bus.rs b/src/spi/bus.rs new file mode 100644 index 0000000..94ea72b --- /dev/null +++ b/src/spi/bus.rs @@ -0,0 +1,250 @@ +use core::convert::Infallible; +use embedded_hal::blocking::spi::Operation; +pub use embedded_hal::blocking::spi::{Transfer, Write, WriteIter}; +pub use embedded_hal::spi::{FullDuplex, Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; + +use nb; + +use super::{Pins, PinsNoCS, SharedBus, SpiConfig, SpiExclusiveDevice, SpiX}; + +/// SPI bus abstraction +pub struct SpiBus { + pub(crate) spi: SPI, + pub(crate) pins: PINS, +} + +impl SpiBus +where + SPI: SpiX, +{ + /// Construct the [SpiBus] for use with [SpiSharedDevice](super::SpiSharedDevice) or [SpiExclusiveDevice] + pub fn new(spi: SPI, pins: PINS) -> Self + where + PINS: Pins, + { + Self { spi, pins } + } + + /// Releases the SPI peripheral and associated pins + pub fn release(self) -> (SPI, PINS) { + (self.spi, self.pins) + } + + /// Configure the [SpiBus] with given [SpiConfig] + pub(crate) fn configure(&mut self, config: &SpiConfig, cs_index: Option) + where + PINS: Pins, + { + self.spi + .sckdiv + .write(|w| unsafe { w.div().bits(config.clock_divisor as u16) }); + + if let Some(index) = cs_index { + self.spi.csid.write(|w| unsafe { w.bits(index) }); + } + self.spi.csmode.write(|w| w.mode().variant(config.cs_mode)); + + // Set CS pin polarity to high + self.spi.csdef.reset(); + + // Set SPI mode + let phase = config.mode.phase == Phase::CaptureOnSecondTransition; + let polarity = config.mode.polarity == Polarity::IdleHigh; + self.spi + .sckmode + .write(|w| w.pha().bit(phase).pol().bit(polarity)); + + self.spi.fmt.write(|w| unsafe { + w.proto().single(); + w.endian().big(); // Transmit most-significant bit (MSB) first + w.dir().rx(); + w.len().bits(8) + }); + + // Set watermark levels + self.spi + .txmark + .write(|w| unsafe { w.txmark().bits(config.txmark) }); + self.spi + .rxmark + .write(|w| unsafe { w.rxmark().bits(config.rxmark) }); + + // set delays + self.spi.delay0.write(|w| unsafe { + w.cssck().bits(config.delays.cssck); // delay between assert and clock + w.sckcs().bits(config.delays.sckcs) // delay between clock and de-assert + }); + self.spi.delay1.write(|w| unsafe { + w.intercs().bits(config.delays.intercs); // delay between CS re-assets + w.interxfr().bits(config.delays.interxfr) // intra-frame delay without CS re-asserts + }); + + self.end_frame(); // ensure CS is de-asserted before we begin + } + + fn wait_for_rxfifo(&self) { + // Ensure that RX FIFO is empty + while self.spi.rxdata.read().empty().bit_is_clear() {} + } + + /// Starts frame by flagging CS assert, unless CSMODE = OFF + pub(crate) fn start_frame(&mut self) { + if !self.spi.csmode.read().mode().is_off() { + self.spi.csmode.write(|w| w.mode().hold()); + } + } + + /// Finishes frame flagging CS deassert, unless CSMODE = OFF + pub(crate) fn end_frame(&mut self) { + if !self.spi.csmode.read().mode().is_off() { + self.spi.csmode.write(|w| w.mode().auto()); + } + } + + // ex-traits now only accessible via devices + + pub(crate) fn read(&mut self) -> nb::Result { + let rxdata = self.spi.rxdata.read(); + + if rxdata.empty().bit_is_set() { + Err(nb::Error::WouldBlock) + } else { + Ok(rxdata.data().bits()) + } + } + + pub(crate) fn send(&mut self, byte: u8) -> nb::Result<(), Infallible> { + let txdata = self.spi.txdata.read(); + + if txdata.full().bit_is_set() { + Err(nb::Error::WouldBlock) + } else { + self.spi.txdata.write(|w| unsafe { w.data().bits(byte) }); + Ok(()) + } + } + + pub(crate) fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Infallible> { + let mut iwrite = 0; + let mut iread = 0; + + // Ensure that RX FIFO is empty + self.wait_for_rxfifo(); + + while iwrite < words.len() || iread < words.len() { + if iwrite < words.len() && self.spi.txdata.read().full().bit_is_clear() { + let byte = unsafe { words.get_unchecked(iwrite) }; + iwrite += 1; + self.spi.txdata.write(|w| unsafe { w.data().bits(*byte) }); + } + + if iread < iwrite { + let data = self.spi.rxdata.read(); + if data.empty().bit_is_clear() { + unsafe { *words.get_unchecked_mut(iread) = data.data().bits() }; + iread += 1; + } + } + } + + Ok(words) + } + + pub(crate) fn write(&mut self, words: &[u8]) -> Result<(), Infallible> { + let mut iwrite = 0; + let mut iread = 0; + + // Ensure that RX FIFO is empty + self.wait_for_rxfifo(); + + while iwrite < words.len() || iread < words.len() { + if iwrite < words.len() && self.spi.txdata.read().full().bit_is_clear() { + let byte = unsafe { words.get_unchecked(iwrite) }; + iwrite += 1; + self.spi.txdata.write(|w| unsafe { w.data().bits(*byte) }); + } + + if iread < iwrite { + // Read and discard byte, if any + if self.spi.rxdata.read().empty().bit_is_clear() { + iread += 1; + } + } + } + + Ok(()) + } + + pub(crate) fn write_iter(&mut self, words: WI) -> Result<(), Infallible> + where + WI: IntoIterator, + { + let mut iter = words.into_iter(); + + let mut read_count = 0; + let mut has_data = true; + + // Ensure that RX FIFO is empty + self.wait_for_rxfifo(); + + while has_data || read_count > 0 { + if has_data && self.spi.txdata.read().full().bit_is_clear() { + if let Some(byte) = iter.next() { + self.spi.txdata.write(|w| unsafe { w.data().bits(byte) }); + read_count += 1; + } else { + has_data = false; + } + } + + if read_count > 0 { + // Read and discard byte, if any + if self.spi.rxdata.read().empty().bit_is_clear() { + read_count -= 1; + } + } + } + + Ok(()) + } + + pub(crate) fn exec<'op>( + &mut self, + operations: &mut [Operation<'op, u8>], + ) -> Result<(), Infallible> { + for op in operations { + match op { + Operation::Transfer(words) => { + self.transfer(words)?; + } + Operation::Write(words) => { + self.write(words)?; + } + } + } + + Ok(()) + } +} + +impl SpiBus +where + SPI: SpiX, + PINS: Pins, +{ + /// Create a new [SpiExclusiveDevice] for exclusive use on this bus + pub fn new_device(self, config: &SpiConfig) -> SpiExclusiveDevice { + SpiExclusiveDevice::new(self, config) + } +} + +impl SpiBus +where + SPI: SpiX, + PINS: PinsNoCS, +{ + /// Create a [SharedBus] for use with multiple devices. + pub fn shared(spi: SPI, pins: PINS) -> SharedBus { + SharedBus::new(Self::new(spi, pins)) + } +} diff --git a/src/spi/config.rs b/src/spi/config.rs new file mode 100644 index 0000000..6b30e72 --- /dev/null +++ b/src/spi/config.rs @@ -0,0 +1,69 @@ +use e310x::qspi0::csmode::MODE_A; +use embedded_hal::spi::Mode; + +use crate::{clock::Clocks, time::Hertz}; + +/// SPI Bus configuration + +#[derive(Clone)] +/// SPI Bus configuration +pub struct SpiConfig { + /// SPI Mode + pub mode: Mode, + /// Clock Divisor calculated from frozen core clock frequency and SPI frequency + pub(crate) clock_divisor: u32, + /// CS Mode + pub cs_mode: MODE_A, + /// Watermark level for transmits + pub txmark: u8, + /// Watermark level for received + pub rxmark: u8, + /// Configuration values for CS and SCK related delays + pub delays: SpiDelayConfig, +} + +#[derive(Clone)] +/// Configuration values for CS and SCK related delays +pub struct SpiDelayConfig { + /// delay between assert and clock in clock ticks + pub cssck: u8, + /// delay between clock and de-assert in clock ticks + pub sckcs: u8, + /// delay between CS re-assets in clock ticks + pub intercs: u8, + /// delay between frames when not re-asserting CS in clock ticks + pub interxfr: u8, +} + +impl SpiConfig { + /// Create new default configuration with given [Mode] and frequency using core [Clocks] + pub fn new(mode: Mode, freq: Hertz, clocks: &Clocks) -> Self { + let clock_divisor = clocks.tlclk().0 / (2 * freq.0) - 1; + assert!(clock_divisor <= 0xfff); + + Self { + mode, + clock_divisor, + cs_mode: MODE_A::HOLD, + txmark: 1, + rxmark: 0, + delays: SpiDelayConfig::default(), + } + } + + /// Calculated clock divisor + pub fn clock_divisor(&self) -> u32 { + self.clock_divisor + } +} + +impl Default for SpiDelayConfig { + fn default() -> Self { + Self { + cssck: 1, // 1 cycle delay between CS assert and first clock + sckcs: 1, // 1 cycle delay between last clock and CS de-assert + intercs: 1, // 1 cycle delay between CS re-asserts + interxfr: 0, // no delay intra-frame when not CS re-asserting + } + } +} diff --git a/src/spi/exclusive_device.rs b/src/spi/exclusive_device.rs new file mode 100644 index 0000000..d9e808e --- /dev/null +++ b/src/spi/exclusive_device.rs @@ -0,0 +1,120 @@ +use core::convert::Infallible; + +use embedded_hal::{ + blocking::spi::{Operation, Transactional, Transfer, Write, WriteIter}, + spi::FullDuplex, +}; + +use crate::spi::SpiConfig; + +use super::{Pins, SpiBus, SpiX}; + +/// SPI exclusive device abstraction +pub struct SpiExclusiveDevice { + bus: SpiBus, +} + +impl SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + /// Create [SpiExclusiveDevice] using the existing [SpiBus](super::SpiBus) + /// with the given [SpiConfig] + pub fn new(mut bus: SpiBus, config: &SpiConfig) -> Self + where + PINS: Pins, + { + bus.configure(config, PINS::CS_INDEX); + + Self { bus } + } + + /// Releases the Bus back deconstructing it + pub fn release(self) -> (SPI, PINS) { + self.bus.release() + } +} + +impl FullDuplex for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn read(&mut self) -> nb::Result { + self.bus.read() + } + + fn send(&mut self, byte: u8) -> nb::Result<(), Infallible> { + self.bus.send(byte) + } +} + +impl Transfer for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.bus.start_frame(); + let result = self.bus.transfer(words); + self.bus.end_frame(); + + result + } +} + +impl Write for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.bus.start_frame(); + let result = self.bus.write(words); + self.bus.end_frame(); + + result + } +} + +impl WriteIter for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn write_iter(&mut self, words: WI) -> Result<(), Self::Error> + where + WI: IntoIterator, + { + self.bus.start_frame(); + let result = self.bus.write_iter(words); + self.bus.end_frame(); + + result + } +} + +impl Transactional for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn exec<'op>(&mut self, operations: &mut [Operation<'op, u8>]) -> Result<(), Infallible> { + self.bus.start_frame(); + let result = self.bus.exec(operations); + self.bus.end_frame(); + + result + } +} diff --git a/src/spi/shared_bus.rs b/src/spi/shared_bus.rs new file mode 100644 index 0000000..ab10025 --- /dev/null +++ b/src/spi/shared_bus.rs @@ -0,0 +1,70 @@ +use core::cell::RefCell; +use core::ops::Deref; +pub use embedded_hal::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; +use riscv::interrupt; +use riscv::interrupt::Mutex; + +use super::{PinCS, PinsNoCS, SpiBus, SpiConfig, SpiSharedDevice, SpiX}; + +/// Newtype for RefCell locked behind a Mutex. +/// Used to hold the [SpiBus] instance so it can be used for multiple [SpiSharedDevice] instances. +pub struct SharedBus(Mutex>>); + +impl SharedBus +where + SPI: SpiX, + PINS: PinsNoCS, +{ + pub(crate) fn new(bus: SpiBus) -> Self { + Self(Mutex::new(RefCell::new(bus))) + } + + /// Create a new shared device on this SPI bus. + pub fn new_device<'bus, CS>( + &'bus self, + cs: CS, + config: &SpiConfig, + ) -> SpiSharedDevice<'bus, SPI, PINS, CS> + where + CS: PinCS, + { + SpiSharedDevice::new(self, cs, config) + } +} + +impl SharedBus +where + SPI: SpiX, + PINS: PinsNoCS, +{ + /// Set HOLD CS mode to per-frame operation, unless CSMODE is set to OFF + pub fn start_frame(&mut self) { + interrupt::free(|cs| { + let mut bus = self.0.borrow(*cs).borrow_mut(); + bus.start_frame(); + }); + } + + /// Finishes transfer by deasserting CS (only for hardware-controlled CS) + pub fn end_frame(&mut self) { + interrupt::free(|cs| { + let mut bus = self.0.borrow(*cs).borrow_mut(); + bus.end_frame(); + }); + } + + /// Releases the SPI peripheral and associated pins + pub fn release(self) -> (SPI, PINS) { + let bus = self.0.into_inner().into_inner(); + + (bus.spi, bus.pins) + } +} + +impl Deref for SharedBus { + type Target = Mutex>>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/src/spi/shared_device.rs b/src/spi/shared_device.rs new file mode 100644 index 0000000..e611273 --- /dev/null +++ b/src/spi/shared_device.rs @@ -0,0 +1,165 @@ +use core::convert::Infallible; + +use embedded_hal::{ + blocking::spi::{Operation, Transactional, Transfer, Write, WriteIter}, + spi::FullDuplex, +}; +use riscv::interrupt; + +use super::{PinCS, Pins, PinsNoCS, SharedBus, SpiConfig, SpiX}; + +/// SPI shared device abstraction +pub struct SpiSharedDevice<'bus, SPI, PINS, CS> { + bus: &'bus SharedBus, + cs: CS, + config: SpiConfig, +} + +impl<'bus, SPI, PINS, CS> SpiSharedDevice<'bus, SPI, PINS, CS> +where + SPI: SpiX, + PINS: PinsNoCS, + CS: PinCS, +{ + /// Create shared [SpiSharedDevice] using the existing [SharedBus] + /// and given [SpiConfig]. The config gets cloned. + pub fn new(bus: &'bus SharedBus, cs: CS, config: &SpiConfig) -> Self + where + PINS: PinsNoCS, + { + Self { + bus, + cs, + config: config.clone(), + } + } + + /// Releases the CS pin back + pub fn release(self) -> CS { + self.cs + } +} + +impl FullDuplex for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn read(&mut self) -> nb::Result { + interrupt::free(|cs| { + let mut bus = self.bus.borrow(*cs).borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.read() + }) + } + + fn send(&mut self, byte: u8) -> nb::Result<(), Infallible> { + interrupt::free(|cs| { + let mut bus = self.bus.borrow(*cs).borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.send(byte) + }) + } +} + +impl Transfer for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + interrupt::free(move |cs| { + let mut bus = self.bus.borrow(*cs).borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.start_frame(); + let result = bus.transfer(words); + bus.end_frame(); + + result + }) + } +} + +impl Write for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + interrupt::free(|cs| { + let mut bus = self.bus.borrow(*cs).borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.start_frame(); + let result = bus.write(words); + bus.end_frame(); + + result + }) + } +} + +impl WriteIter for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn write_iter(&mut self, words: WI) -> Result<(), Self::Error> + where + WI: IntoIterator, + { + interrupt::free(|cs| { + let mut bus = self.bus.borrow(*cs).borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.start_frame(); + let result = bus.write_iter(words); + bus.end_frame(); + + result + }) + } +} + +impl Transactional for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn exec<'op>(&mut self, operations: &mut [Operation<'op, u8>]) -> Result<(), Infallible> { + interrupt::free(|cs| { + let mut bus = self.bus.borrow(*cs).borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.start_frame(); + let result = bus.exec(operations); + bus.end_frame(); + + result + }) + } +} diff --git a/src/spi/traits.rs b/src/spi/traits.rs new file mode 100644 index 0000000..16f91f5 --- /dev/null +++ b/src/spi/traits.rs @@ -0,0 +1,206 @@ +/// Helper traits for SPI pins +use core::ops::Deref; +use e310x::{qspi0, QSPI0, QSPI1, QSPI2}; + +#[doc(hidden)] +pub trait SpiX: Deref + private::Sealed {} +impl SpiX for QSPI0 {} +impl SpiX for QSPI1 {} +impl SpiX for QSPI2 {} + +/// SPI pins - DO NOT IMPLEMENT THIS TRAIT +/// +/// This trait is implemented for pin tuples (), (MOSI, MISO, SCK) and (MOSI, MISO, SCK, CS) +/// and combinations without MOSI/MISO +pub trait Pins: private::Sealed { + #[doc(hidden)] + const CS_INDEX: Option; +} + +/// SPI pins without CS - DO NOT IMPLEMENT THIS TRAIT +/// +/// This trait is implemented for pin tuples (), (MOSI, MISO, SCK) only without CS pin +/// and combinations without MOSI/MISO +pub trait PinsNoCS: Pins {} + +/// SPI Chip Select pin - DO NOT IMPLEMENT THIS TRAIT +/// +/// This trait is implemented for chip select pins only +pub trait PinCS: private::Sealed { + #[doc(hidden)] + const CS_INDEX: u32; +} + +/* SPI0 pins */ +impl Pins for () { + const CS_INDEX: Option = Some(0); +} + +/* SPI1 pins */ +mod spi1_impl { + use super::{PinCS, Pins, PinsNoCS, QSPI1}; + use crate::gpio::gpio0; + use crate::gpio::{NoInvert, IOF0}; + + type MOSI = gpio0::Pin3>; + type MISO = gpio0::Pin4>; + type SCK = gpio0::Pin5>; + type CS0 = gpio0::Pin2>; + type CS1 = gpio0::Pin8>; + type CS2 = gpio0::Pin9>; + type CS3 = gpio0::Pin10>; + + // ensure only the correct CS pins can be used to make SpiSharedDevice instances + impl PinCS for CS0 { + const CS_INDEX: u32 = 0; + } + impl PinCS for CS1 { + const CS_INDEX: u32 = 1; + } + impl PinCS for CS2 { + const CS_INDEX: u32 = 2; + } + impl PinCS for CS3 { + const CS_INDEX: u32 = 3; + } + + impl PinsNoCS for (MOSI, MISO, SCK) {} + impl PinsNoCS for (MOSI, (), SCK) {} + impl PinsNoCS for ((), MISO, SCK) {} + + impl Pins for (MOSI, MISO, SCK) { + const CS_INDEX: Option = None; + } + impl Pins for (MOSI, (), SCK) { + const CS_INDEX: Option = None; + } + impl Pins for ((), MISO, SCK) { + const CS_INDEX: Option = None; + } + impl Pins for (MOSI, MISO, SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for (MOSI, (), SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for ((), MISO, SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for (MOSI, MISO, SCK, CS1) { + const CS_INDEX: Option = Some(1); + } + impl Pins for (MOSI, (), SCK, CS1) { + const CS_INDEX: Option = Some(1); + } + impl Pins for ((), MISO, SCK, CS1) { + const CS_INDEX: Option = Some(1); + } + impl Pins for (MOSI, MISO, SCK, CS2) { + const CS_INDEX: Option = Some(2); + } + impl Pins for (MOSI, (), SCK, CS2) { + const CS_INDEX: Option = Some(2); + } + impl Pins for ((), MISO, SCK, CS2) { + const CS_INDEX: Option = Some(2); + } + impl Pins for (MOSI, MISO, SCK, CS3) { + const CS_INDEX: Option = Some(3); + } + impl Pins for (MOSI, (), SCK, CS3) { + const CS_INDEX: Option = Some(3); + } + impl Pins for ((), MISO, SCK, CS3) { + const CS_INDEX: Option = Some(3); + } + + // seal the "private" traits + mod spi1_private { + use super::super::private::Sealed; + use super::*; + + impl Sealed for CS0 {} + impl Sealed for CS1 {} + impl Sealed for CS2 {} + impl Sealed for CS3 {} + impl Sealed for (MOSI, MISO, SCK) {} + impl Sealed for (MOSI, (), SCK) {} + impl Sealed for ((), MISO, SCK) {} + impl Sealed for (MOSI, MISO, SCK, CS0) {} + impl Sealed for (MOSI, (), SCK, CS0) {} + impl Sealed for ((), MISO, SCK, CS0) {} + impl Sealed for (MOSI, MISO, SCK, CS1) {} + impl Sealed for (MOSI, (), SCK, CS1) {} + impl Sealed for ((), MISO, SCK, CS1) {} + impl Sealed for (MOSI, MISO, SCK, CS2) {} + impl Sealed for (MOSI, (), SCK, CS2) {} + impl Sealed for ((), MISO, SCK, CS2) {} + impl Sealed for (MOSI, MISO, SCK, CS3) {} + impl Sealed for (MOSI, (), SCK, CS3) {} + impl Sealed for ((), MISO, SCK, CS3) {} + } +} + +/* SPI2 pins */ +mod spi2_impl { + use super::{PinCS, Pins, PinsNoCS, QSPI2}; + use crate::gpio::gpio0; + use crate::gpio::{NoInvert, IOF0}; + + type MOSI = gpio0::Pin27>; + type MISO = gpio0::Pin28>; + type SCK = gpio0::Pin29>; + type CS0 = gpio0::Pin26>; + + impl PinCS for CS0 { + const CS_INDEX: u32 = 0; + } + + impl PinsNoCS for (MOSI, MISO, SCK) {} + impl PinsNoCS for (MOSI, (), SCK) {} + impl PinsNoCS for ((), MISO, SCK) {} + + impl Pins for (MOSI, MISO, SCK) { + const CS_INDEX: Option = None; + } + impl Pins for (MOSI, (), SCK) { + const CS_INDEX: Option = None; + } + impl Pins for ((), MISO, SCK) { + const CS_INDEX: Option = None; + } + impl Pins for (MOSI, MISO, SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for (MOSI, (), SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for ((), MISO, SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + + // seal the "private" traits + mod spi2_private { + use super::super::private::Sealed; + use super::*; + + impl Sealed for CS0 {} + impl Sealed for (MOSI, MISO, SCK) {} + impl Sealed for (MOSI, (), SCK) {} + impl Sealed for ((), MISO, SCK) {} + impl Sealed for (MOSI, MISO, SCK, CS0) {} + impl Sealed for (MOSI, (), SCK, CS0) {} + impl Sealed for ((), MISO, SCK, CS0) {} + } +} + +// seal the "private" traits +mod private { + pub trait Sealed {} + + impl Sealed for () {} + + impl Sealed for super::QSPI0 {} + impl Sealed for super::QSPI1 {} + impl Sealed for super::QSPI2 {} +}