diff --git a/examples/simple/examples/blinky-hal.rs b/examples/simple/examples/blinky-hal.rs index 13eab01..3056748 100644 --- a/examples/simple/examples/blinky-hal.rs +++ b/examples/simple/examples/blinky-hal.rs @@ -14,7 +14,7 @@ fn main() -> ! { rprintln!("VA416xx HAL blinky example"); let mut dp = pac::Peripherals::take().unwrap(); - let portg = PinsG::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portg); + let portg = PinsG::new(&mut dp.sysconfig, dp.portg); let mut led = portg.pg5.into_readable_push_pull_output(); //let mut delay = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM0); loop { diff --git a/examples/simple/examples/spi.rs b/examples/simple/examples/spi.rs new file mode 100644 index 0000000..8324249 --- /dev/null +++ b/examples/simple/examples/spi.rs @@ -0,0 +1,96 @@ +//! SPI example application. +//! +//! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board. +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use embedded_hal::spi::{Mode, SpiBus, MODE_0}; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use simple_examples::peb1; +use va416xx_hal::spi::{Spi, TransferConfig}; +use va416xx_hal::{ + gpio::{PinsB, PinsC}, + pac, + prelude::*, + spi::SpiConfig, +}; + +#[derive(PartialEq, Debug)] +pub enum ExampleSelect { + // Enter loopback mode. It is not necessary to tie MOSI/MISO together for this + Loopback, + // Send a test buffer and print everything received. You need to tie together MOSI/MISO in this + // mode. + TestBuffer, +} + +const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback; +const SPI_SPEED_KHZ: u32 = 1000; +const SPI_MODE: Mode = MODE_0; +const BLOCKMODE: bool = true; +const FILL_WORD: u8 = 0x0f; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("-- VA108xx SPI example application--"); + let cp = cortex_m::Peripherals::take().unwrap(); + let mut dp = pac::Peripherals::take().unwrap(); + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) + .freeze(&mut dp.sysconfig) + .unwrap(); + let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw()); + + let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb); + let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc); + // Configure SPI1 pins. + let (sck, miso, mosi) = ( + pins_b.pb15.into_funsel_1(), + pins_c.pc0.into_funsel_1(), + pins_c.pc1.into_funsel_1(), + ); + + let mut spi_cfg = SpiConfig::default(); + if EXAMPLE_SEL == ExampleSelect::Loopback { + spi_cfg = spi_cfg.loopback(true) + } + let transfer_cfg = + TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false); + // Create SPI peripheral. + let mut spi0 = Spi::spi0( + dp.spi0, + (sck, miso, mosi), + &clocks, + spi_cfg, + &mut dp.sysconfig, + Some(&transfer_cfg.downgrade()), + ); + spi0.set_fill_word(FILL_WORD); + loop { + let mut tx_buf: [u8; 3] = [1, 2, 3]; + let mut rx_buf: [u8; 3] = [0; 3]; + // Can't really verify correct reply here. + spi0.write(&[0x42]).expect("write failed"); + // Need small delay.. otherwise we will read back the sent byte (which we don't want here). + // The write function will return as soon as all bytes were shifted out, ignoring the + // reply bytes. + delay_sysclk.delay_us(50); + // Because of the loopback mode, we should get back the fill word here. + spi0.read(&mut rx_buf[0..1]).unwrap(); + assert_eq!(rx_buf[0], FILL_WORD); + + spi0.transfer_in_place(&mut tx_buf) + .expect("SPI transfer_in_place failed"); + assert_eq!([1, 2, 3], tx_buf); + spi0.transfer(&mut rx_buf, &tx_buf) + .expect("SPI transfer failed"); + assert_eq!(rx_buf, tx_buf); + delay_sysclk.delay_ms(500); + } +} diff --git a/examples/simple/examples/uart.rs b/examples/simple/examples/uart.rs index 51242b1..ad627ed 100644 --- a/examples/simple/examples/uart.rs +++ b/examples/simple/examples/uart.rs @@ -28,7 +28,7 @@ fn main() -> ! { .freeze(&mut dp.sysconfig) .unwrap(); - let gpiob = PinsG::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portg); + let gpiob = PinsG::new(&mut dp.sysconfig, dp.portg); let tx = gpiob.pg0.into_funsel_1(); let rx = gpiob.pg1.into_funsel_1(); diff --git a/va416xx-hal/src/gpio/pin.rs b/va416xx-hal/src/gpio/pin.rs index 7dc88b8..feb9b29 100644 --- a/va416xx-hal/src/gpio/pin.rs +++ b/va416xx-hal/src/gpio/pin.rs @@ -1,7 +1,7 @@ use core::{convert::Infallible, marker::PhantomData, mem::transmute}; pub use crate::clock::FilterClkSel; -use crate::Sealed; +use crate::typelevel::Sealed; use embedded_hal::digital::{ErrorType, InputPin, OutputPin, StatefulOutputPin}; use va416xx::{Porta, Portb, Portc, Portd, Porte, Portf, Portg}; @@ -622,7 +622,6 @@ macro_rules! pins { paste::paste!( /// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB) pub struct $PinsName { - iocfg: Option, port: $Port, $( #[doc = "Pin " $Id] @@ -637,7 +636,6 @@ macro_rules! pins { #[inline] pub fn new( syscfg: &mut va416xx::Sysconfig, - iocfg: Option, port: $Port ) -> $PinsName { syscfg.peripheral_clk_enable().modify(|_, w| { @@ -645,7 +643,6 @@ macro_rules! pins { w.ioconfig().set_bit() }); $PinsName { - iocfg, port, // Safe because we only create one `Pin` per `PinId` $( @@ -662,8 +659,8 @@ macro_rules! pins { } /// Consumes the Pins struct and returns the port definitions - pub fn release(self) -> (Option, $Port) { - (self.iocfg, self.port) + pub fn release(self) -> $Port { + self.port } } ); diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index 4c438c1..1b395cf 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -9,6 +9,7 @@ pub mod prelude; pub mod clock; pub mod gpio; +pub mod spi; pub mod time; pub mod typelevel; pub mod uart; @@ -41,11 +42,3 @@ impl IrqCfg { IrqCfg { irq, route, enable } } } - -mod private { - /// Super trait used to mark traits with an exhaustive set of - /// implementations - pub trait Sealed {} -} - -pub(crate) use private::Sealed; diff --git a/va416xx-hal/src/spi.rs b/va416xx-hal/src/spi.rs new file mode 100644 index 0000000..672e416 --- /dev/null +++ b/va416xx-hal/src/spi.rs @@ -0,0 +1,885 @@ +use core::{convert::Infallible, marker::PhantomData, ops::Deref}; + +use embedded_hal::spi::Mode; + +use crate::{ + clock::PeripheralSelect, + gpio::{ + AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0, + PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1, + PC10, PC11, PC7, PC8, PC9, PE10, PE11, PE12, PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9, + PF0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PG2, PG3, PG4, + }, + pac, + time::Hertz, + typelevel::{NoneT, Sealed}, +}; + +//================================================================================================== +// Defintions +//================================================================================================== + +// FIFO has a depth of 16. +const FILL_DEPTH: usize = 12; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum HwChipSelectId { + Id0 = 0, + Id1 = 1, + Id2 = 2, + Id3 = 3, + Id4 = 4, + Id5 = 5, + Id6 = 6, + Id7 = 7, + Invalid = 0xff, +} + +#[derive(Debug)] +pub enum SpiId { + Spi0, + Spi1, + Spi2, + Spi3, + Invalid, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum WordSize { + OneBit = 0x00, + FourBits = 0x03, + EightBits = 0x07, + SixteenBits = 0x0f, +} + +//================================================================================================== +// Pin type definitions +//================================================================================================== + +pub trait PinSck: Sealed {} +pub trait PinMosi: Sealed {} +pub trait PinMiso: Sealed {} + +pub trait HwCsProvider: Sealed { + const CS_ID: HwChipSelectId; + const SPI_ID: SpiId; +} + +pub trait OptionalHwCs: HwCsProvider + Sealed {} + +macro_rules! hw_cs_pins { + ($SPIx:path, $portId: path: + $( + ($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident), + )+ + ) => { + $( + impl HwCsProvider for Pin<$PXx, $AFx> { + const CS_ID: HwChipSelectId = $HwCsIdent; + const SPI_ID: SpiId = $portId; + } + impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {} + pub type $typedef = Pin<$PXx, $AFx>; + )+ + }; +} + +impl HwCsProvider for NoneT { + const CS_ID: HwChipSelectId = HwChipSelectId::Invalid; + const SPI_ID: SpiId = SpiId::Invalid; +} + +impl OptionalHwCs for NoneT {} +impl OptionalHwCs for NoneT {} +impl OptionalHwCs for NoneT {} +impl OptionalHwCs for NoneT {} + +// SPI 0 + +impl PinSck for Pin {} +impl PinMosi for Pin {} +impl PinMiso for Pin {} + +// SPI 1 + +impl PinSck for Pin {} +impl PinMosi for Pin {} +impl PinMiso for Pin {} + +impl PinSck for Pin {} +impl PinMosi for Pin {} +impl PinMiso for Pin {} + +impl PinSck for Pin {} +impl PinMiso for Pin {} + +impl PinSck for Pin {} +impl PinMosi for Pin {} +impl PinMiso for Pin {} + +impl PinSck for Pin {} +impl PinMosi for Pin {} +impl PinMiso for Pin {} + +// SPI 2 + +impl PinSck for Pin {} +impl PinMosi for Pin {} +impl PinMiso for Pin {} + +impl PinSck for Pin {} +impl PinMosi for Pin {} +impl PinMiso for Pin {} + +// SPI3 is shared with the ROM SPI pins and has its own dedicated pins. + +// SPI 0 HW CS pins + +hw_cs_pins!( + pac::Spi0, SpiId::Spi0: + (PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0), + (PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0), + (PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0), + (PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0), +); + +hw_cs_pins!( + pac::Spi1, SpiId::Spi1: + (PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb), + (PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb), + (PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb), + (PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb), + (PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb), + (PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb), + (PB1, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pb), + (PB0, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pb), + (PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc), + (PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc), + (PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe), + (PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe), + (PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe), + (PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe), + (PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe), + (PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe), + (PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe), + (PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe), + (PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf), + (PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg), +); + +hw_cs_pins!( + pac::Spi2, SpiId::Spi2: + (PA4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pa), + (PA3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pa), + (PA2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pa), + (PA1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pa), + (PA0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pa), + (PA8, AltFunc2, HwChipSelectId::Id6, HwCs6Spi2Pa), + (PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa), + (PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf), + (PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf), + (PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf), + (PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf), + (PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf), +); + +//================================================================================================== +// Config +//================================================================================================== + +pub trait TransferConfigProvider { + fn sod(&mut self, sod: bool); + fn blockmode(&mut self, blockmode: bool); + fn mode(&mut self, mode: Mode); + fn frequency(&mut self, spi_clk: Hertz); + fn hw_cs_id(&self) -> u8; +} + +/// This struct contains all configuration parameter which are transfer specific +/// and might change for transfers to different SPI slaves +#[derive(Copy, Clone)] +pub struct TransferConfig { + pub spi_clk: Hertz, + pub mode: Mode, + /// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to + /// false + pub hw_cs: Option, + pub sod: bool, + /// If this is enabled, all data in the FIFO is transmitted in a single frame unless + /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the + /// duration of multiple data words + pub blockmode: bool, +} + +/// Type erased variant of the transfer configuration. This is required to avoid generics in +/// the SPI constructor. +pub struct ErasedTransferConfig { + pub spi_clk: Hertz, + pub mode: Mode, + pub sod: bool, + /// If this is enabled, all data in the FIFO is transmitted in a single frame unless + /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the + /// duration of multiple data words + pub blockmode: bool, + pub hw_cs: HwChipSelectId, +} + +impl TransferConfig { + pub fn new_no_hw_cs(spi_clk: impl Into, mode: Mode, blockmode: bool, sod: bool) -> Self { + TransferConfig { + spi_clk: spi_clk.into(), + mode, + hw_cs: None, + sod, + blockmode, + } + } +} + +impl TransferConfig { + pub fn new( + spi_clk: impl Into, + mode: Mode, + hw_cs: Option, + blockmode: bool, + sod: bool, + ) -> Self { + TransferConfig { + spi_clk: spi_clk.into(), + mode, + hw_cs, + sod, + blockmode, + } + } + + pub fn downgrade(self) -> ErasedTransferConfig { + ErasedTransferConfig { + spi_clk: self.spi_clk, + mode: self.mode, + sod: self.sod, + blockmode: self.blockmode, + hw_cs: HwCs::CS_ID, + } + } +} + +impl TransferConfigProvider for TransferConfig { + /// Slave Output Disable + fn sod(&mut self, sod: bool) { + self.sod = sod; + } + + fn blockmode(&mut self, blockmode: bool) { + self.blockmode = blockmode; + } + + fn mode(&mut self, mode: Mode) { + self.mode = mode; + } + + fn frequency(&mut self, spi_clk: Hertz) { + self.spi_clk = spi_clk; + } + + fn hw_cs_id(&self) -> u8 { + HwCs::CS_ID as u8 + } +} + +#[derive(Default)] +/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details +pub struct SpiConfig { + /// Serial clock rate divider. Together with the CLKPRESCALE register, it determines + /// the SPI clock rate in master mode. 0 by default. Specifying a higher value + /// limits the maximum attainable SPI speed + pub ser_clock_rate_div: u8, + /// By default, configure SPI for master mode (ms == false) + ms: bool, + /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control + pub slave_output_disable: bool, + /// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally + pub loopback_mode: bool, + /// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details + pub master_delayer_capture: bool, +} + +impl SpiConfig { + pub fn loopback(mut self, enable: bool) -> Self { + self.loopback_mode = enable; + self + } + + pub fn master_mode(mut self, master: bool) -> Self { + self.ms = !master; + self + } + + pub fn slave_output_disable(mut self, sod: bool) -> Self { + self.slave_output_disable = sod; + self + } +} + +//================================================================================================== +// Word Size +//================================================================================================== + +/// Configuration trait for the Word Size used by the SPI peripheral +pub trait WordProvider: Copy + Default + Into + TryFrom + 'static { + const MASK: u32; + fn word_reg() -> u8; +} + +impl WordProvider for u8 { + const MASK: u32 = 0xff; + fn word_reg() -> u8 { + 0x07 + } +} + +impl WordProvider for u16 { + const MASK: u32 = 0xffff; + fn word_reg() -> u8 { + 0x0f + } +} + +pub type SpiRegBlock = pac::spi0::RegisterBlock; + +/// Common trait implemented by all PAC peripheral access structures. The register block +/// format is the same for all SPI blocks. +pub trait Instance: Deref { + const IDX: u8; + + fn ptr() -> *const SpiRegBlock; +} + +impl Instance for pac::Spi0 { + const IDX: u8 = 0; + + fn ptr() -> *const SpiRegBlock { + Self::ptr() + } +} + +impl Instance for pac::Spi1 { + const IDX: u8 = 1; + + fn ptr() -> *const SpiRegBlock { + Self::ptr() + } +} + +impl Instance for pac::Spi2 { + const IDX: u8 = 2; + + fn ptr() -> *const SpiRegBlock { + Self::ptr() + } +} + +//================================================================================================== +// Spi +//================================================================================================== + +pub struct SpiBase { + spi: SpiInstance, + cfg: SpiConfig, + apb1_clk: Hertz, + /// Fill word for read-only SPI transactions. + pub fill_word: Word, + blockmode: bool, + word: PhantomData, +} + +pub struct Spi { + inner: SpiBase, + pins: Pins, +} + +fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) { + match mode { + embedded_hal::spi::MODE_0 => (false, false), + embedded_hal::spi::MODE_1 => (false, true), + embedded_hal::spi::MODE_2 => (true, false), + embedded_hal::spi::MODE_3 => (true, true), + } +} + +impl SpiBase +where + >::Error: core::fmt::Debug, +{ + #[inline] + pub fn cfg_clock(&mut self, spi_clk: impl Into) { + let clk_prescale = + self.apb1_clk.raw() / (spi_clk.into().raw() * (self.cfg.ser_clock_rate_div as u32 + 1)); + self.spi + .clkprescale() + .write(|w| unsafe { w.bits(clk_prescale) }); + } + + #[inline] + pub fn cfg_mode(&mut self, mode: Mode) { + let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode); + self.spi.ctrl0().modify(|_, w| { + w.spo().bit(cpo_bit); + w.sph().bit(cph_bit) + }); + } + + #[inline] + pub fn clear_tx_fifo(&self) { + self.spi.fifo_clr().write(|w| w.txfifo().set_bit()); + } + + #[inline] + pub fn clear_rx_fifo(&self) { + self.spi.fifo_clr().write(|w| w.rxfifo().set_bit()); + } + + #[inline] + pub fn perid(&self) -> u32 { + self.spi.perid().read().bits() + } + + #[inline] + pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) { + if hw_cs == HwChipSelectId::Invalid { + return; + } + self.spi.ctrl1().modify(|_, w| { + w.sod().clear_bit(); + unsafe { + w.ss().bits(hw_cs as u8); + } + w + }); + } + + #[inline] + pub fn cfg_hw_cs_with_pin>(&mut self, _: &HwCs) { + self.cfg_hw_cs(HwCs::CS_ID); + } + + pub fn cfg_hw_cs_disable(&mut self) { + self.spi.ctrl1().modify(|_, w| { + w.sod().set_bit(); + w + }); + } + + pub fn cfg_transfer>( + &mut self, + transfer_cfg: &TransferConfig, + ) { + self.cfg_clock(transfer_cfg.spi_clk); + self.cfg_mode(transfer_cfg.mode); + self.blockmode = transfer_cfg.blockmode; + self.spi.ctrl1().modify(|_, w| { + if transfer_cfg.sod { + w.sod().set_bit(); + } else if transfer_cfg.hw_cs.is_some() { + w.sod().clear_bit(); + unsafe { + w.ss().bits(HwCs::CS_ID as u8); + } + } else { + w.sod().clear_bit(); + } + if transfer_cfg.blockmode { + w.blockmode().set_bit(); + } else { + w.blockmode().clear_bit(); + } + w + }); + } + + /// Sends a word to the slave + #[inline(always)] + fn send_blocking(&self, word: Word) { + // TODO: Upper limit for wait cycles to avoid complete hangups? + while self.spi.status().read().tnf().bit_is_clear() {} + self.send(word) + } + + #[inline(always)] + fn send(&self, word: Word) { + self.spi.data().write(|w| unsafe { w.bits(word.into()) }); + } + + /// Read a word from the slave. Must be preceeded by a [`send`](Self::send) call + #[inline(always)] + fn read_blocking(&self) -> Word { + // TODO: Upper limit for wait cycles to avoid complete hangups? + while self.spi.status().read().rne().bit_is_clear() {} + self.read_single_word() + } + + #[inline(always)] + fn read_single_word(&self) -> Word { + (self.spi.data().read().bits() & Word::MASK) + .try_into() + .unwrap() + } + + fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> { + if words.is_empty() { + return Ok(()); + } + let mut status_reg = self.spi.status().read(); + // Wait until all bytes have been transferred. + while status_reg.tfe().bit_is_clear() { + // Ignore all received read words. + if status_reg.rne().bit_is_set() { + self.clear_rx_fifo(); + } + status_reg = self.spi.status().read(); + } + // Ignore all received read words. + if status_reg.rne().bit_is_set() { + self.clear_rx_fifo(); + } + Ok(()) + } + + fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize { + if self.blockmode { + self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()) + } + // Fill the first half of the write FIFO + let mut current_write_idx = 0; + for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) { + self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx])); + current_write_idx += 1; + } + if self.blockmode { + self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit()) + } + current_write_idx + } +} + +macro_rules! spi_ctor { + ($spiI:ident, $PeriphSel: path) => { + /// Create a new SPI struct + /// + /// You can delete the pin type information by calling the + /// [`downgrade`](Self::downgrade) function + /// + /// ## Arguments + /// * `spi` - SPI bus to use + /// * `pins` - Pins to be used for SPI transactions. These pins are consumed + /// to ensure the pins can not be used for other purposes anymore + /// * `spi_cfg` - Configuration specific to the SPI bus + /// * `transfer_cfg` - Optional initial transfer configuration which includes + /// configuration which can change across individual SPI transfers like SPI mode + /// or SPI clock. If only one device is connected, this configuration only needs + /// to be done once. + /// * `syscfg` - Can be passed optionally to enable the peripheral clock + pub fn $spiI( + spi: SpiI, + pins: (Sck, Miso, Mosi), + clocks: &crate::clock::Clocks, + spi_cfg: SpiConfig, + syscfg: &mut pac::Sysconfig, + transfer_cfg: Option<&ErasedTransferConfig>, + ) -> Self { + crate::clock::enable_peripheral_clock(syscfg, $PeriphSel); + let SpiConfig { + ser_clock_rate_div, + ms, + slave_output_disable, + loopback_mode, + master_delayer_capture, + } = spi_cfg; + let mut mode = embedded_hal::spi::MODE_0; + let mut clk_prescale = 0x02; + let mut ss = 0; + let mut init_blockmode = false; + let apb1_clk = clocks.apb1(); + if let Some(transfer_cfg) = transfer_cfg { + mode = transfer_cfg.mode; + clk_prescale = + apb1_clk.raw() / (transfer_cfg.spi_clk.raw() * (ser_clock_rate_div as u32 + 1)); + if transfer_cfg.hw_cs != HwChipSelectId::Invalid { + ss = transfer_cfg.hw_cs as u8; + } + init_blockmode = transfer_cfg.blockmode; + } + + let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode); + spi.ctrl0().write(|w| { + unsafe { + w.size().bits(Word::word_reg()); + w.scrdv().bits(ser_clock_rate_div); + // Clear clock phase and polarity. Will be set to correct value for each + // transfer + w.spo().bit(cpo_bit); + w.sph().bit(cph_bit) + } + }); + spi.ctrl1().write(|w| { + w.lbm().bit(loopback_mode); + w.sod().bit(slave_output_disable); + w.ms().bit(ms); + w.mdlycap().bit(master_delayer_capture); + w.blockmode().bit(init_blockmode); + unsafe { w.ss().bits(ss) } + }); + + spi.fifo_clr().write(|w| { + w.rxfifo().set_bit(); + w.txfifo().set_bit() + }); + spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) }); + // Enable the peripheral as the last step as recommended in the + // programmers guide + spi.ctrl1().modify(|_, w| w.enable().set_bit()); + Spi { + inner: SpiBase { + spi, + cfg: spi_cfg, + apb1_clk, + fill_word: Default::default(), + blockmode: init_blockmode, + word: PhantomData, + }, + pins, + } + } + }; +} + +impl< + SpiI: Instance, + Sck: PinSck, + Miso: PinMiso, + Mosi: PinMosi, + Word: WordProvider, + > Spi +where + >::Error: core::fmt::Debug, +{ + spi_ctor!(spi0, PeripheralSelect::Spi0); + spi_ctor!(spi1, PeripheralSelect::Spi1); + spi_ctor!(spi2, PeripheralSelect::Spi2); + spi_ctor!(spi3, PeripheralSelect::Spi3); + + #[inline] + pub fn cfg_clock(&mut self, spi_clk: impl Into) { + self.inner.cfg_clock(spi_clk); + } + + #[inline] + pub fn cfg_mode(&mut self, mode: Mode) { + self.inner.cfg_mode(mode); + } + + pub fn set_fill_word(&mut self, fill_word: Word) { + self.inner.fill_word = fill_word; + } + + pub fn fill_word(&self) -> Word { + self.inner.fill_word + } + + #[inline] + pub fn perid(&self) -> u32 { + self.inner.perid() + } + + pub fn cfg_transfer>(&mut self, transfer_cfg: &TransferConfig) { + self.inner.cfg_transfer(transfer_cfg); + } + + /// Releases the SPI peripheral and associated pins + pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) { + (self.inner.spi, self.pins, self.inner.cfg) + } + + pub fn downgrade(self) -> SpiBase { + self.inner + } +} + +/// Changing the word size also requires a type conversion +impl, Miso: PinMiso, Mosi: PinMosi> + From> for Spi +{ + fn from(old_spi: Spi) -> Self { + old_spi + .inner + .spi + .ctrl0() + .modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) }); + Spi { + inner: SpiBase { + spi: old_spi.inner.spi, + cfg: old_spi.inner.cfg, + blockmode: old_spi.inner.blockmode, + fill_word: Default::default(), + apb1_clk: old_spi.inner.apb1_clk, + word: PhantomData, + }, + pins: old_spi.pins, + } + } +} + +/// Changing the word size also requires a type conversion +impl, Miso: PinMiso, Mosi: PinMosi> + From> for Spi +{ + fn from(old_spi: Spi) -> Self { + old_spi + .inner + .spi + .ctrl0() + .modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) }); + Spi { + inner: SpiBase { + spi: old_spi.inner.spi, + cfg: old_spi.inner.cfg, + blockmode: old_spi.inner.blockmode, + apb1_clk: old_spi.inner.apb1_clk, + fill_word: Default::default(), + word: PhantomData, + }, + pins: old_spi.pins, + } + } +} + +impl embedded_hal::spi::ErrorType for SpiBase { + type Error = Infallible; +} + +impl embedded_hal::spi::SpiBus for SpiBase +where + >::Error: core::fmt::Debug, +{ + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping(None); + loop { + if current_write_idx < words.len() { + self.send_blocking(self.fill_word); + current_write_idx += 1; + } + if current_read_idx < words.len() { + words[current_read_idx] = self.read_blocking(); + current_read_idx += 1; + } + if current_read_idx >= words.len() && current_write_idx >= words.len() { + break; + } + } + Ok(()) + } + + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_write_idx = self.initial_send_fifo_pumping(Some(words)); + while current_write_idx < words.len() { + self.send_blocking(words[current_write_idx]); + current_write_idx += 1; + // Ignore received words. + if self.spi.status().read().rne().bit_is_set() { + self.clear_rx_fifo(); + } + } + Ok(()) + } + + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.transfer_preparation(write)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping(Some(write)); + while current_read_idx < read.len() || current_write_idx < write.len() { + if current_write_idx < write.len() { + self.send_blocking(write[current_write_idx]); + current_write_idx += 1; + } + if current_read_idx < read.len() { + read[current_read_idx] = self.read_blocking(); + current_read_idx += 1; + } + } + + Ok(()) + } + + fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping(Some(words)); + + while current_read_idx < words.len() || current_write_idx < words.len() { + if current_write_idx < words.len() { + self.send_blocking(words[current_write_idx]); + current_write_idx += 1; + } + if current_read_idx < words.len() && current_read_idx < current_write_idx { + words[current_read_idx] = self.read_blocking(); + current_read_idx += 1; + } + } + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + let status_reg = self.spi.status().read(); + while status_reg.tfe().bit_is_clear() || status_reg.rne().bit_is_set() { + if status_reg.rne().bit_is_set() { + self.read_single_word(); + } + } + Ok(()) + } +} + +impl< + SpiI: Instance, + Word: WordProvider, + Sck: PinSck, + Miso: PinMiso, + Mosi: PinMosi, + > embedded_hal::spi::ErrorType for Spi +{ + type Error = Infallible; +} + +impl< + SpiI: Instance, + Word: WordProvider, + Sck: PinSck, + Miso: PinMiso, + Mosi: PinMosi, + > embedded_hal::spi::SpiBus for Spi +where + >::Error: core::fmt::Debug, +{ + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.inner.read(words) + } + + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { + self.inner.write(words) + } + + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.inner.transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.inner.transfer_in_place(words) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.inner.flush() + } +}