diff --git a/README.md b/README.md index f1b7473..59900bf 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,10 @@ of support for peripherals is shown in the table below. |------------|----|---|---| | GPIO | ✅ | - | | | ICache | ✅ | - | | -| I2C | ✅ | - | | +|| I2C | ✅ | - | | +| SPI | ✅ | - | SPI peripheral only (not implemented for USART peripheral) | | CAN | 🚧 | - | | | Rng | 🚧 | [#34](https://github.com/stm32-rs/stm32h5xx-hal/issues/34)| | -| SPI | 🚧 | [#36](https://github.com/stm32-rs/stm32h5xx-hal/issues/36) | | | UART | 🚧 | - | | | DMA | 🚧 | - | | | ADC | ❌ | [#35](https://github.com/stm32-rs/stm32h5xx-hal/issues/35) | | diff --git a/examples/spi_slave.rs b/examples/spi_slave.rs new file mode 100644 index 0000000..def4c47 --- /dev/null +++ b/examples/spi_slave.rs @@ -0,0 +1,65 @@ +#![deny(warnings)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +mod utilities; +use stm32h5xx_hal::{pac, prelude::*, spi}; + +use log::info; + +#[entry] +fn main() -> ! { + utilities::logger::init(); + let dp = pac::Peripherals::take().unwrap(); + + // Constrain and Freeze power + info!("Setup PWR... "); + let pwr = dp.PWR.constrain(); + let pwrcfg = pwr.freeze(); + + // Constrain and Freeze clock + info!("Setup RCC... "); + let rcc = dp.RCC.constrain(); + let ccdr = rcc + .sys_ck(100.MHz()) + .pll1_q_ck(50.MHz()) + .freeze(pwrcfg, &dp.SBS); + + // Acquire the GPIOB peripheral. This also enables the clock for + // GPIOB in the RCC register. + let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); + + let sck = gpiob.pb13.into_alternate(); + let miso = gpiob.pb14.into_alternate(); + let mosi = gpiob.pb15.into_alternate(); + let hcs = gpiob.pb4.into_alternate(); + + info!(""); + info!("stm32h5xx-hal example - SPI"); + info!(""); + + // Initialise the SPI peripheral. + let mut spi = dp.SPI2.spi_slave( + (sck, miso, mosi, hcs), + spi::Config::new(spi::MODE_0) + // Specify that we use the hardware cs + .hardware_cs(spi::HardwareCSMode::SlaveSelectInput), + ccdr.peripheral.SPI2, + ); + + // This will write the contents of the buffer as long as a clock is provided + spi.write(&[0u8, 1, 2, 3, 4, 5, 6]).unwrap(); + + // This will read into the buffer as long as a clock is provided + let read = &mut [0u8; 4]; + spi.read(read).unwrap(); + + // This will read and write while a clock is provided + let read = &mut [0u8; 3]; + spi.transfer(read, &[0x11, 0x22, 0x33]).unwrap(); + + loop { + cortex_m::asm::nop(); + } +} diff --git a/src/spi.rs b/src/spi.rs index 3e3fa15..96e8bd8 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -1,17 +1,21 @@ //! Serial Peripheral Interface (SPI) //! -//! This module provides functionality for SPI as a Master. It supports full-duplex, half duplex, -//! simplex transmitter and simplex receiver modes. +//! This module provides functionality for SPI as both a Master and Slave. It +//! supports full-duplex, half duplex, simplex transmitter and simplex receiver +//! modes (both as master and slave). +//! +//! Master operation is implemented via the [`Spi`] struct, while slave operation +//! is implemented via [`SpiSlave`]. //! //! It supports both blocking and non-blocking usage. For blocking usage -//! as a Master `Spi` implements the [embedded-hal][embedded-hal] traits ([`SpiBus`][spi_bus] +//! as a Master [`Spi`] implements the [embedded-hal][embedded-hal] traits ([`SpiBus`][spi_bus] //! and [`SpiDevice`][spi_device]). The driver also provides a //! [NonBlocking](nonblocking::NonBlocking) trait that defines the non-blocking API. This must be //! explicitly used to access the non-blocking API. //! //! # Usage //! -//! ## Initialization +//! ## Master //! In the simplest case, SPI can be initialised from the device peripheral //! and the GPIO pins. //! @@ -55,7 +59,7 @@ //! &ccdr.clocks); //!``` //! -//! ## Blocking API +//! ### Blocking API //! Use the (`SpiBus`)[spi_bus] or (`SpiDevice`)[spi_device] (with hardware control of CS) APIs provided by //! [embedded-hal](embedded-hal): //! @@ -79,7 +83,7 @@ //! spi.transfer(read, &[0x11u8, 0x22, 0x33])?; //! ``` //! -//! ## Non-blocking API +//! ### Non-blocking API //! To use the non-blocking API, the [`nonblocking::NonBlocking`] trait must be used. Then, //! transactions need to be started via one of the start_nonblocking_XXX methods. This will return //! a [`transaction::Transaction`] type that can be passed to @@ -101,6 +105,67 @@ //! } //! ``` //! +//! ## Slave +//! +//! `SpiSlave` implementation is used similarly to `Spi`. To get a `SpiSlave` +//! instance without hardware chip select management, initialize with the +//! required pins: +//! +//! ``` +//! use stm32h5xx_hal::spi; +//! +//! let dp = ...; // Device peripherals +//! let (sck, miso, mosi) = ...; // GPIO pins +//! +//! let mut spi_slave = dp.SPI1.spi_slave((sck, miso, mosi), spi::MODE_0, ccdr.peripheral.SPI1); +//! ``` +//! +//! The API for slave is mostly the same as that of the master +//! implementation. Again, non-blocking and blocking modes are supported with +//! the same function calls available to `SpiSlave` (with the exception of the +//! FullDuplex APIs). Note that there are no embedded-hal traits for Slave +//! operation so these are specific to this crate. +//! +//! Hardware chip select can be enabled by providing a CS pin and configuring +//! hardware CS mode to be `HardwareCSMode::SlaveSelectInput`: +//! +//! ``` +//! use stm32h5xx_hal::spi; +//! +//! let dp = ...; // Device peripherals +//! let (sck, miso, mosi, cs) = ...; // GPIO pins +//! +//! let mut spi_slave = dp.SPI2.spi_slave( +//! (sck, miso, mosi, cs), +//! spi::Config::new(spi::MODE_0) +//! .hardware_cs(spi::HardwareCSMode::SlaveSelectInput), +//! ccdr.peripheral.SPI2, +//! ); +//! ``` +//! +//! ### Blocking usage +//! The API is much the same as the (`SpiBus`)[spi_bus] API for master +//! operation. Whether or not hardware CS management is used or not, the calls +//! are the same and will block until all data has been transferrred: +//! +//! ``` +//! // Transmit only. +//! spi.write(&[0x11u8, 0x22, 0x33])?; +//! +//! // Receive only +//! let read = &mut [0u8; 3]; +//! spi.read(read)?; +//! +//! // Full duplex simultaneous transmit & receive +//! spi.transfer(read, &[0x11u8, 0x22, 0x33])?; +//! ``` +//! +//! ### Non-blocking usage +//! [`SpiSlave`] also supports the non-blocking API defined by the [`nonblocking::NonBlocking`] +//! trait and is used in the same way as the master SPI. The only difference is that +//! the slave does not initiate transactions, but rather waits for the master to do so, so the start +//! method should be called before the bus master is expected to start a transaction. +//! //! ## Clocks //! //! The bitrate calculation is based upon the clock currently assigned @@ -127,6 +192,7 @@ //! //! - [SPI Master](https://github.com/stm32-rs/stm32h5xx-hal/blob/master/examples/spi.rs) //! - [SPI Master with Frame Transactions](https://github.com/stm32-rs/stm32h5xx-hal/blob/master/examples/spi_send_frames.rs) +//! - [SPI Slave](https://github.com/stm32-rs/stm32h5xx-hal/blob/master/examples/spi_slave.rs) //! //! [embedded_hal]: https://docs.rs/embedded-hal/1.0.0-rc.1/embedded_hal/spi/index.html //! [spi_bus]: https://docs.rs/embedded-hal/1.0.0-rc.1/embedded_hal/spi/trait.SpiBus.html @@ -238,6 +304,11 @@ pub struct Spi { _word: PhantomData, } +pub struct SpiSlave { + inner: Inner, + _word: PhantomData, +} + // Implemented by all SPI instances pub trait Instance: crate::Sealed + Deref + Sized @@ -287,6 +358,16 @@ pub trait SpiExt, W: Word = u8> { PINS: Pins, CONFIG: Into; + fn spi_slave( + self, + _pins: PINS, + config: CONFIG, + rec: SPI::Rec, + ) -> SpiSlave + where + PINS: Pins, + CONFIG: Into; + fn spi_unchecked( self, config: CONFIG, @@ -296,6 +377,14 @@ pub trait SpiExt, W: Word = u8> { ) -> Spi where CONFIG: Into; + + fn spi_slave_unchecked( + self, + config: CONFIG, + rec: SPI::Rec, + ) -> SpiSlave + where + CONFIG: Into; } impl, W: Word> SpiExt for SPI { @@ -320,6 +409,25 @@ impl, W: Word> SpiExt for SPI { Spi::::new(self, config, freq, rec, clocks) } + fn spi_slave( + self, + _pins: PINS, + config: CONFIG, + rec: SPI::Rec, + ) -> SpiSlave + where + PINS: Pins, + CONFIG: Into, + { + let config = config.into(); + assert_eq!( + config.hardware_cs.enabled(), + PINS::HCS_PRESENT, + "If the hardware cs is enabled in the config, an HCS pin must be present in the given pins" + ); + SpiSlave::::new(self, config, rec) + } + fn spi_unchecked( self, config: CONFIG, @@ -332,6 +440,17 @@ impl, W: Word> SpiExt for SPI { { Spi::::new(self, config, freq, rec, clocks) } + + fn spi_slave_unchecked( + self, + config: CONFIG, + rec: SPI::Rec, + ) -> SpiSlave + where + CONFIG: Into, + { + SpiSlave::::new(self, config, rec) + } } fn calc_mbr(spi_ker_ck: u32, spi_freq: u32) -> MBR { @@ -370,6 +489,16 @@ impl Spi { } fn init(mut self, config: Config, freq: Hertz, clock: Hertz) -> Self { + if config.hardware_cs.enabled() { + assert!( + !matches!( + config.hardware_cs.mode, + HardwareCSMode::SlaveSelectInput + ), + "HardwareCSMode::SlaveSelectInput not valid for master operation" + ); + } + let spi_freq = freq.raw(); let spi_ker_ck = clock.raw(); let mbr = calc_mbr(spi_ker_ck, spi_freq); @@ -458,6 +587,79 @@ impl Spi { } } +impl SpiSlave { + fn new(spi: SPI, config: impl Into, rec: SPI::Rec) -> Self { + let config: Config = config.into(); + rec.enable(); + + let spi = SpiSlave { + inner: Inner::new(spi), + _word: PhantomData, + }; + spi.init(config) + } + + fn spi(&mut self) -> &mut SPI { + &mut self.inner.spi + } + + fn init(mut self, config: Config) -> Self { + if config.hardware_cs.enabled() { + assert!( + matches!( + config.hardware_cs.mode, + HardwareCSMode::SlaveSelectInput + ), + "Slave mode can only use hardware chip select as input" + ); + } + + self.inner.set_word_size(W::BITS as usize); + + // Deselect internal slave select + self.spi().cr1().write(|w| w.ssi().slave_not_selected()); + + let cs_polarity = match config.hardware_cs.polarity { + Polarity::IdleHigh => SSIOP::ActiveLow, + Polarity::IdleLow => SSIOP::ActiveHigh, + }; + + let endianness = match config.endianness { + Endianness::LsbFirst => LSBFRST::Lsbfirst, + Endianness::MsbFirst => LSBFRST::Msbfirst, + }; + + self.spi().cfg2().write(|w| { + w.cpha() + .bit(config.mode.phase == Phase::CaptureOnSecondTransition) + .cpol() + .bit(config.mode.polarity == Polarity::IdleHigh) + .master() + .slave() + .lsbfrst() + .variant(endianness) + .ssm() + .bit(!config.hardware_cs.enabled()) + .ssoe() + .disabled() + .ssiop() + .variant(cs_polarity) + .ioswp() + .bit(config.swap_miso_mosi) + .comm() + .variant(config.communication_mode.into()) + }); + + self + } + + fn hardware_cs_enabled(&self) -> bool { + // The SSM bit indicates software control of SS input is enabled if it is set. If software + // control is disabled, the SS input is determined by the assigned chip select pin. + self.inner.spi.cfg2().read().ssm().is_disabled() + } +} + macro_rules! check_status_error { ($sr:expr) => { if $sr.ovr().is_overrun() { @@ -1010,6 +1212,204 @@ impl Spi { } } +impl SpiSlave { + /// Set the word size for a transaction. Can only be changed when the peripheral is disabled + /// Must be less than or equal to the maximum size denoted by the const generic size parameter + /// (W) + pub fn set_word_size(&mut self, word_size: usize) { + self.inner.set_word_size(word_size); + } + + /// Start listening for transactions from a master + fn start_listening(&mut self) { + if !self.hardware_cs_enabled() { + self.spi().cr1().modify(|_, w| w.ssi().slave_selected()); + } + self.inner.enable(); + } + + /// Stop listening for transactions from a master. Abort any ongoing transaction. + fn stop_listening(&mut self) { + self.inner.disable(); + if !self.hardware_cs_enabled() { + self.spi().cr1().modify(|_, w| w.ssi().slave_not_selected()); + } + } + + /// This will return nb::Error::WouldBlock until all remaining bytes have been sent or received. + /// Upon completion of the transaction, it will disable the SPI peripheral and stop listening + /// for data. + fn end_transaction_if_done(&mut self) -> bool { + if self.inner.is_transmitter() + && self.spi().sr().read().txc().is_ongoing() + { + return false; + } + + self.stop_listening(); + + true + } + + /// Deconstructs the SPI peripheral and returns the component parts. + pub fn free(self) -> SPI { + self.inner.spi + } + + pub fn inner(&self) -> &SPI { + &self.inner.spi + } + + pub fn inner_mut(&mut self) -> &mut SPI { + &mut self.inner.spi + } +} + +// Non-blocking SPI Slave operations +impl SpiSlave { + fn start_write<'a>( + &mut self, + words: &'a [W], + ) -> Result, W>, Error> { + assert!( + !words.is_empty(), + "Write buffer should not be non-zero length" + ); + + let communication_mode = self.inner.communication_mode(); + if communication_mode == CommunicationMode::SimplexReceiver { + return Err(Error::InvalidOperation); + } + + if communication_mode == CommunicationMode::HalfDuplex { + self.inner.set_dir_transmitter(); + } + + self.start_listening(); + + Ok(Transaction::, W>::write(words)) + } + + fn start_read<'a>( + &mut self, + buf: &'a mut [W], + ) -> Result, W>, Error> { + let communication_mode = self.inner.communication_mode(); + if communication_mode == CommunicationMode::SimplexTransmitter { + return Err(Error::InvalidOperation); + } + + if communication_mode == CommunicationMode::HalfDuplex { + self.inner.set_dir_receiver(); + } + + self.start_listening(); + + Ok(Transaction::, W>::read(buf)) + } + + fn start_transfer<'a>( + &mut self, + read: &'a mut [W], + write: &'a [W], + ) -> Result, W>, Error> { + if self.inner.communication_mode() != CommunicationMode::FullDuplex { + return Err(Error::InvalidOperation); + } + + self.start_listening(); + + Ok(Transaction::, W>::transfer(write, read)) + } + + fn start_transfer_inplace<'a>( + &mut self, + words: &'a mut [W], + ) -> Result, W>, Error> { + if self.inner.communication_mode() != CommunicationMode::FullDuplex { + return Err(Error::InvalidOperation); + } + + self.start_listening(); + + Ok(Transaction::, W>::transfer_inplace( + words, + )) + } + + fn transfer_nonblocking_internal>( + &mut self, + mut transaction: Transaction, + ) -> Result>, Error> { + self.inner + .transfer_words_nonblocking(&mut transaction) + .inspect_err(|_| self.stop_listening())?; + + if transaction.is_complete() && self.end_transaction_if_done() { + Ok(None) + } else { + Ok(Some(transaction)) + } + } +} + +// Blocking SPI Slave operations +impl SpiSlave { + fn transfer_internal>( + &mut self, + mut transaction: Transaction, + ) -> Result<(), Error> { + while let Some(t) = self.transfer_nonblocking_internal(transaction)? { + transaction = t; + } + Ok(()) + } + + /// Blocking, write-only transfer + pub fn write(&mut self, words: &[W]) -> Result<(), Error> { + if words.is_empty() { + return Ok(()); + } + + let transaction = self.start_write(words)?; + self.transfer_internal(transaction) + } + + /// Blocking read-only transfer + pub fn read(&mut self, words: &mut [W]) -> Result<(), Error> { + if words.is_empty() { + return Ok(()); + } + + let transaction = self.start_read(words)?; + self.transfer_internal(transaction) + } + + /// Blocking full duplex transfer + pub fn transfer( + &mut self, + read: &mut [W], + write: &[W], + ) -> Result<(), Error> { + if read.is_empty() && write.is_empty() { + return Ok(()); + } + + let transaction = self.start_transfer(read, write)?; + self.transfer_internal(transaction) + } + + /// Blocking full duplex transfer with single buffer for transmit and receive + pub fn transfer_inplace(&mut self, words: &mut [W]) -> Result<(), Error> { + if words.is_empty() { + return Ok(()); + } + + let transaction = self.start_transfer_inplace(words)?; + self.transfer_internal(transaction) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/spi/config.rs b/src/spi/config.rs index 8a929f3..59d11f6 100644 --- a/src/spi/config.rs +++ b/src/spi/config.rs @@ -176,6 +176,10 @@ pub enum HardwareCSMode { /// you must setup the frame with [Spi::setup_transaction]. After everything has been sent, /// you must also clean it up with [Spi::end_transaction]. FrameTransaction, + /// This is the only supported active mode in slave mode. In master mode this can be used to + /// detect conflicts (generating a mode fault) in multi-master topologies. See RM0492 Rev 2 + /// 38.4.7. + SlaveSelectInput, } impl HardwareCS { diff --git a/src/spi/nonblocking.rs b/src/spi/nonblocking.rs index 613750d..b5f464d 100644 --- a/src/spi/nonblocking.rs +++ b/src/spi/nonblocking.rs @@ -1,6 +1,6 @@ use super::{ - Error, Instance, Op, Read, Spi, Transaction, Transfer, TransferInplace, - Word, Write, + Error, Instance, Op, Read, Spi, SpiSlave, Transaction, Transfer, + TransferInplace, Word, Write, }; /// Trait that provides non-blocking SPI operations. @@ -86,3 +86,42 @@ impl NonBlocking for Spi { self.transfer_nonblocking_internal(transaction) } } + +impl NonBlocking for SpiSlave { + fn start_nonblocking_read<'a>( + &mut self, + buf: &'a mut [W], + ) -> Result, W>, Error> { + self.start_read(buf) + } + + fn start_nonblocking_write<'a>( + &mut self, + words: &'a [W], + ) -> Result, W>, Error> { + self.start_write(words) + } + + fn start_nonblocking_duplex_transfer<'a>( + &mut self, + read: &'a mut [W], + write: &'a [W], + ) -> Result, W>, Error> { + self.start_transfer(read, write) + } + + fn start_nonblocking_duplex_transfer_inplace<'a>( + &mut self, + words: &'a mut [W], + ) -> Result, W>, Error> { + self.start_transfer_inplace(words) + } + + #[allow(private_bounds)] + fn transfer_nonblocking>( + &mut self, + transaction: Transaction, + ) -> Result>, Error> { + self.transfer_nonblocking_internal(transaction) + } +}