stm32f7xx-hal 0.7.0

HAL for the STM32F7xx family of microcontrollers
Documentation
//! Interface to the SPI peripheral
//!
//! See chapter 32 in the STM32F746 Reference Manual.

pub use embedded_hal::spi::{Mode, Phase, Polarity};

use core::{fmt, marker::PhantomData, ops::DerefMut, pin::Pin, ptr};

use as_slice::{AsMutSlice, AsSlice as _};
use embedded_hal::{
    blocking::spi::{transfer, write, write_iter},
    spi::FullDuplex,
};

use crate::{
    gpio::{self, Alternate},
    pac::{self, spi1::cr2},
    rcc::{BusClock, Clocks, Enable, RccBus},
    state,
};

use crate::dma;
use fugit::HertzU32 as Hertz;

/// Entry point to the SPI API
pub struct Spi<I, P, State> {
    spi: I,
    pins: P,
    _state: State,
}

impl<I, P> Spi<I, P, state::Disabled>
where
    I: Instance + Enable + BusClock,
    P: Pins<I>,
{
    /// Create a new instance of the SPI API
    pub fn new(instance: I, pins: P) -> Self {
        Self {
            spi: instance,
            pins,
            _state: state::Disabled,
        }
    }

    /// Initialize the SPI peripheral
    pub fn enable<Word>(
        self,
        mode: Mode,
        freq: Hertz,
        clocks: &Clocks,
        apb: &mut <I as RccBus>::Bus,
    ) -> Spi<I, P, Enabled<Word>>
    where
        Word: SupportedWordSize,
    {
        I::enable(apb);
        let cpol = mode.polarity == Polarity::IdleHigh;
        let cpha = mode.phase == Phase::CaptureOnSecondTransition;

        let br = match I::clock(clocks) / freq {
            0 => unreachable!(),
            1..=2 => 0b000,
            3..=5 => 0b001,
            6..=11 => 0b010,
            12..=23 => 0b011,
            24..=47 => 0b100,
            48..=95 => 0b101,
            96..=191 => 0b110,
            _ => 0b111,
        };
        self.spi.configure::<Word>(br, cpol, cpha);

        Spi {
            spi: self.spi,
            pins: self.pins,
            _state: Enabled(PhantomData),
        }
    }
}

impl<I, P, Word> Spi<I, P, Enabled<Word>>
where
    I: Instance,
    P: Pins<I>,
    Word: SupportedWordSize,
{
    /// Start an SPI transfer using DMA
    ///
    /// Sends the data in `buffer` and writes the received data into buffer
    /// right after. Returns a [`Transfer`], to represent the ongoing SPI
    /// transfer.
    ///
    /// Please note that the word "transfer" is used with two different meanings
    /// here:
    /// - An SPI transfer, as in an SPI transaction that involves both sending
    ///   and receiving data. The method name refers to this kind of transfer.
    /// - A DMA transfer, as in an ongoing DMA operation. The name of the return
    ///   type refers to this kind of transfer.
    ///
    /// This method, as well as all other DMA-related methods in this module,
    /// requires references to two DMA handles, one each for the RX and TX
    /// streams. This will actually always be the same handle, as each SPI
    /// instance uses the same DMA instance for both sending and receiving. It
    /// would be nice to simplify that, but I believe that requires an equality
    /// constraint in the where clause, which is not supported yet by the
    /// compiler.
    pub fn transfer_all<B>(
        self,
        buffer: Pin<B>,
        dma_rx: &dma::Handle<<Rx<I> as dma::Target>::Instance, state::Enabled>,
        dma_tx: &dma::Handle<<Tx<I> as dma::Target>::Instance, state::Enabled>,
        rx: <Rx<I> as dma::Target>::Stream,
        tx: <Tx<I> as dma::Target>::Stream,
    ) -> Transfer<Word, I, P, B, Rx<I>, Tx<I>, dma::Ready>
    where
        Rx<I>: dma::Target,
        Tx<I>: dma::Target,
        B: DerefMut + 'static,
        B::Target: AsMutSlice<Element = Word>,
    {
        // Create the RX/TX tokens for the transfer. Those must only exist once,
        // otherwise it would be possible to create multiple transfers trying to
        // use the same hardware resources.
        //
        // We guarantee that they only exist once by only creating them where we
        // have access to `self`, moving `self` into the `Transfer` while they
        // are in use, and dropping them when returning `self` from the
        // transfer.
        let rx_token = Rx(PhantomData);
        let tx_token = Tx(PhantomData);

        // We need to move a buffer into each of the `dma::Transfer` instances,
        // while keeping the original buffer around to return to the caller
        // later, when the transfer is finished.
        //
        // Here we create two `Buffer` from raw pointers acquired from `buffer`.
        let rx_buffer = dma::PtrBuffer {
            ptr: buffer.as_slice().as_ptr(),
            len: buffer.as_slice().len(),
        };
        let tx_buffer = dma::PtrBuffer {
            ptr: buffer.as_slice().as_ptr(),
            len: buffer.as_slice().len(),
        };

        // Create the two DMA transfers. This is safe, for the following
        // reasons:
        // 1. The trait bounds on this method guarantee that `buffer`, which we
        //    created the two buffer instances from, can be safely read from and
        //    written to.
        // 2. The semantics of the SPI peripheral guarantee that the buffer
        //    reads/writes are synchronized, preventing race conditions.
        let rx_transfer = unsafe {
            dma::Transfer::new(
                dma_rx,
                rx,
                Pin::new(rx_buffer),
                rx_token,
                self.spi.dr_address(),
                dma::Direction::PeripheralToMemory,
            )
        };
        let tx_transfer = unsafe {
            dma::Transfer::new(
                dma_tx,
                tx,
                Pin::new(tx_buffer),
                tx_token,
                self.spi.dr_address(),
                dma::Direction::MemoryToPeripheral,
            )
        };

        Transfer {
            buffer,
            target: self,

            rx: rx_transfer,
            tx: tx_transfer,

            _state: dma::Ready,
        }
    }
}

impl<I, P, Word> FullDuplex<Word> for Spi<I, P, Enabled<Word>>
where
    I: Instance,
    P: Pins<I>,
    Word: SupportedWordSize,
{
    type Error = Error;

    fn read(&mut self) -> nb::Result<Word, Self::Error> {
        self.spi.read()
    }

    fn send(&mut self, word: Word) -> nb::Result<(), Self::Error> {
        self.spi.send(word)
    }
}

impl<I, P, Word> transfer::Default<Word> for Spi<I, P, Enabled<Word>>
where
    I: Instance,
    P: Pins<I>,
    Word: SupportedWordSize,
{
}

impl<I, P, Word> write::Default<Word> for Spi<I, P, Enabled<Word>>
where
    I: Instance,
    P: Pins<I>,
    Word: SupportedWordSize,
{
}

impl<I, P, Word> write_iter::Default<Word> for Spi<I, P, Enabled<Word>>
where
    I: Instance,
    P: Pins<I>,
    Word: SupportedWordSize,
{
}

impl<I, P, State> Spi<I, P, State>
where
    I: Instance,
    P: Pins<I>,
{
    /// Destroy the peripheral API and return a raw SPI peripheral instance
    pub fn free(self) -> (I, P) {
        (self.spi, self.pins)
    }
}

/// Implemented for all instances of the SPI peripheral
///
/// Users of this crate should not implement this trait.
pub trait Instance {
    fn configure<Word>(&self, br: u8, cpol: bool, cpha: bool)
    where
        Word: SupportedWordSize;
    fn read<Word>(&self) -> nb::Result<Word, Error>
    where
        Word: SupportedWordSize;
    fn send<Word>(&self, word: Word) -> nb::Result<(), Error>
    where
        Word: SupportedWordSize;
    fn dr_address(&self) -> u32;
}

/// Implemented for all tuples that contain a full set of valid SPI pins
pub trait Pins<I> {}

impl<I, SCK, MISO, MOSI> Pins<I> for (SCK, MISO, MOSI)
where
    SCK: Sck<I>,
    MISO: Miso<I>,
    MOSI: Mosi<I>,
{
}

/// Implemented for all pins that can function as the SCK pin
///
/// Users of this crate should not implement this trait.
pub trait Sck<I> {}

/// Implemented for all pins that can function as the MISO pin
///
/// Users of this crate should not implement this trait.
pub trait Miso<I> {}

/// Implemented for all pins that can function as the MOSI pin
///
/// Users of this crate should not implement this trait.
pub trait Mosi<I> {}

macro_rules! impl_instance {
    (
        $(
            $name:ty {
                regs: ($bus:ident, $reset:ident, $enable:ident),
                pins: {
                    SCK: [$($sck:ty,)*],
                    MISO: [$($miso:ty,)*],
                    MOSI: [$($mosi:ty,)*],
                }
            }
        )*
    ) => {
        $(
            impl Instance for $name {

                // I don't like putting this much code into the macro, but I
                // have to: There are two different SPI variants in the PAC, and
                // while I haven't found any actual differences between them,
                // they're still using different sets of types, and I need to
                // generate different methods to interface with them, even
                // though these methods end up looking identical.
                //
                // Maybe this is a problem in the SVD file that can be fixed
                // there.

                fn configure<Word>(&self, br: u8, cpol: bool, cpha: bool)
                    where Word: SupportedWordSize
                {
                    self.cr2.write(|w| {
                        // Data size
                        //
                        // This is safe, as `Word::ds` returns an enum which can
                        // only encode valid variants for this field.
                        let w = unsafe { w.ds().bits(Word::ds().into()) };

                        w
                            // FIFO reception threshold.
                            .frxth().bit(Word::frxth().into())
                            // Disable TX buffer empty interrupt
                            .txeie().masked()
                            // Disable RX buffer not empty interrupt
                            .rxneie().masked()
                            // Disable error interrupt
                            .errie().masked()
                            // Frame format
                            .frf().motorola()
                            // NSS pulse management
                            .nssp().no_pulse()
                            // SS output
                            .ssoe().disabled()
                            // Enable DMA support
                            .txdmaen().enabled()
                            .rxdmaen().enabled()
                    });

                    self.cr1.write(|w|
                        w
                            // Use two lines for MISO/MOSI
                            .bidimode().unidirectional()
                            // Disable hardware CRC calculation
                            .crcen().disabled()
                            // Enable full-duplex mode
                            .rxonly().full_duplex()
                            // Manage slave select pin manually
                            .ssm().enabled()
                            .ssi().set_bit()
                            // Transmit most significant bit first
                            .lsbfirst().msbfirst()
                            // Set baud rate value
                            .br().bits(br)
                            // Select master mode
                            .mstr().master()
                            // Select clock polarity
                            .cpol().bit(cpol)
                            // Select clock phase
                            .cpha().bit(cpha)
                            // Enable SPI
                            .spe().enabled()
                    );
                }

                fn read<Word>(&self) -> nb::Result<Word, Error> {
                    let sr = self.sr.read();

                    // Check for errors
                    //
                    // This whole code should live in a method in `Error`, but
                    // this wouldn't be straight-forward, due to the different
                    // SPI types in the PAC, explained in more detail in
                    // another comment.
                    if sr.fre().is_error() {
                        return Err(nb::Error::Other(Error::FrameFormat));
                    }
                    if sr.ovr().is_overrun() {
                        return Err(nb::Error::Other(Error::Overrun));
                    }
                    if sr.modf().is_fault() {
                        return Err(nb::Error::Other(Error::ModeFault));
                    }

                    // Did we receive something?
                    if sr.rxne().is_not_empty() {
                        // It makes a difference whether we read this register
                        // as a `u8` or `u16`, so we can't use the standard way
                        // to access it. This is safe, as `&self.dr` is a
                        // memory-mapped register.
                        let value = unsafe {
                            ptr::read_volatile(
                                &self.dr as *const _ as *const _,
                            )
                        };

                        return Ok(value);
                    }

                    Err(nb::Error::WouldBlock)
                }

                fn send<Word>(&self, word: Word) -> nb::Result<(), Error> {
                    let sr = self.sr.read();

                    // Check for errors
                    //
                    // This whole code should live in a method in `Error`, but
                    // this wouldn't be straight-forward, due to the different
                    // SPI types in the PAC, explained in more detail in
                    // another comment.
                    if sr.fre().is_error() {
                        return Err(nb::Error::Other(Error::FrameFormat));
                    }
                    if sr.ovr().is_overrun() {
                        return Err(nb::Error::Other(Error::Overrun));
                    }
                    if sr.modf().is_fault() {
                        return Err(nb::Error::Other(Error::ModeFault));
                    }

                    // Can we write to the transmit buffer?
                    if sr.txe().is_empty() {
                        // It makes a difference whether we write a `u8` or
                        // `u16` to this register, so we can't use the standard
                        // way to access it. This is safe, as `&self.dr` is a
                        // memory-mapped register.
                        unsafe {
                            ptr::write_volatile(
                                &self.dr as *const _ as *mut _,
                                word,
                            );
                        }

                        return Ok(())
                    }

                    Err(nb::Error::WouldBlock)
                }

                fn dr_address(&self) -> u32 {
                    &self.dr as *const _ as _
                }
            }

            $(
                impl Sck<$name> for $sck {}
            )*

            $(
                impl Miso<$name> for $miso {}
            )*

            $(
                impl Mosi<$name> for $mosi {}
            )*
        )*
    }
}

impl_instance!(
    pac::SPI1 {
        regs: (apb2, spi1rst, spi1en),
        pins: {
            SCK: [
                gpio::PA5<Alternate<5>>,
                gpio::PB3<Alternate<5>>,
                gpio::PG11<Alternate<5>>,
            ],
            MISO: [
                gpio::PA6<Alternate<5>>,
                gpio::PB4<Alternate<5>>,
                gpio::PG9<Alternate<5>>,
            ],
            MOSI: [
                gpio::PA7<Alternate<5>>,
                gpio::PB5<Alternate<5>>,
                gpio::PD7<Alternate<5>>,
            ],
        }
    }
    pac::SPI2 {
        regs: (apb1, spi2rst, spi2en),
        pins: {
            SCK: [
                gpio::PA9<Alternate<5>>,
                gpio::PB10<Alternate<5>>,
                gpio::PB13<Alternate<5>>,
                gpio::PD3<Alternate<5>>,
                gpio::PI1<Alternate<5>>,
            ],
            MISO: [
                gpio::PB14<Alternate<5>>,
                gpio::PC2<Alternate<5>>,
                gpio::PI2<Alternate<5>>,
            ],
            MOSI: [
                gpio::PB15<Alternate<5>>,
                gpio::PC1<Alternate<5>>,
                gpio::PC3<Alternate<5>>,
                gpio::PI3<Alternate<5>>,
            ],
        }
    }
    pac::SPI3 {
        regs: (apb1, spi3rst, spi3en),
        pins: {
            SCK: [
                gpio::PB3<Alternate<6>>,
                gpio::PC10<Alternate<6>>,
            ],
            MISO: [
                gpio::PB4<Alternate<6>>,
                gpio::PC11<Alternate<6>>,
            ],
            MOSI: [
                gpio::PB2<Alternate<7>>,
                gpio::PB5<Alternate<6>>,
                gpio::PC12<Alternate<6>>,
                gpio::PD6<Alternate<5>>,
            ],
        }
    }
    pac::SPI4 {
        regs: (apb2, spi4rst, spi4en),
        pins: {
            SCK: [
                gpio::PE2<Alternate<5>>,
                gpio::PE12<Alternate<5>>,
            ],
            MISO: [
                gpio::PE5<Alternate<5>>,
                gpio::PE13<Alternate<5>>,
            ],
            MOSI: [
                gpio::PE6<Alternate<5>>,
                gpio::PE14<Alternate<5>>,
            ],
        }
    }
    pac::SPI5 {
        regs: (apb2, spi5rst, spi5en),
        pins: {
            SCK: [
                gpio::PF7<Alternate<5>>,
                gpio::PH6<Alternate<5>>,
            ],
            MISO: [
                gpio::PF8<Alternate<5>>,
                gpio::PH7<Alternate<5>>,
            ],
            MOSI: [
                gpio::PF9<Alternate<5>>,
                gpio::PF11<Alternate<5>>,
            ],
        }
    }
);

#[cfg(any(
    feature = "stm32f745",
    feature = "stm32f746",
    feature = "stm32f756",
    feature = "stm32f765",
    feature = "stm32f767",
    feature = "stm32f769",
    feature = "stm32f777",
    feature = "stm32f778",
    feature = "stm32f779",
))]
impl_instance!(
    pac::SPI6 {
        regs: (apb2, spi6rst, spi6en),
        pins: {
            SCK: [
                gpio::PG13<Alternate<5>>,
            ],
            MISO: [
                gpio::PG12<Alternate<5>>,
            ],
            MOSI: [
                gpio::PG14<Alternate<5>>,
            ],
        }
    }
);

/// Placeholder for a pin when no SCK pin is required
pub struct NoSck;
impl<I> Sck<I> for NoSck {}

/// Placeholder for a pin when no MISO pin is required
pub struct NoMiso;
impl<I> Miso<I> for NoMiso {}

/// Placeholder for a pin when no MOSI pin is required
pub struct NoMosi;
impl<I> Mosi<I> for NoMosi {}

#[derive(Debug)]
pub enum Error {
    FrameFormat,
    Overrun,
    ModeFault,
}

/// RX token used for DMA transfers
pub struct Rx<I>(PhantomData<I>);

/// TX token used for DMA transfers
pub struct Tx<I>(PhantomData<I>);

/// A DMA transfer of the SPI peripheral
///
/// Since DMA can send and receive at the same time, using two DMA transfers and
/// two DMA streams, we need this type to represent this operation and wrap the
/// underlying [`dma::Transfer`] instances.
pub struct Transfer<Word: SupportedWordSize, I, P, Buffer, Rx: dma::Target, Tx: dma::Target, State>
{
    buffer: Pin<Buffer>,
    target: Spi<I, P, Enabled<Word>>,
    rx: dma::Transfer<Rx, dma::PtrBuffer<Word>, State>,
    tx: dma::Transfer<Tx, dma::PtrBuffer<Word>, State>,
    _state: State,
}

impl<Word, I, P, Buffer, Rx, Tx> Transfer<Word, I, P, Buffer, Rx, Tx, dma::Ready>
where
    Rx: dma::Target,
    Tx: dma::Target,
    Word: SupportedWordSize,
{
    /// Enables the given interrupts for this DMA transfer
    ///
    /// These interrupts are only enabled for this transfer. The settings
    /// doesn't affect other transfers, nor subsequent transfers using the same
    /// DMA streams.
    pub fn enable_interrupts(
        &mut self,
        rx_handle: &dma::Handle<Rx::Instance, state::Enabled>,
        tx_handle: &dma::Handle<Tx::Instance, state::Enabled>,
        interrupts: dma::Interrupts,
    ) {
        self.rx.enable_interrupts(rx_handle, interrupts);
        self.tx.enable_interrupts(tx_handle, interrupts);
    }

    /// Start the DMA transfer
    ///
    /// Consumes this instance of `Transfer` and returns another instance with
    /// its type state set to indicate the transfer has been started.
    pub fn start(
        self,
        rx_handle: &dma::Handle<Rx::Instance, state::Enabled>,
        tx_handle: &dma::Handle<Tx::Instance, state::Enabled>,
    ) -> Transfer<Word, I, P, Buffer, Rx, Tx, dma::Started> {
        Transfer {
            buffer: self.buffer,
            target: self.target,
            rx: self.rx.start(rx_handle),
            tx: self.tx.start(tx_handle),
            _state: dma::Started,
        }
    }
}

impl<Word, I, P, Buffer, Rx, Tx> Transfer<Word, I, P, Buffer, Rx, Tx, dma::Started>
where
    Rx: dma::Target,
    Tx: dma::Target,
    Word: SupportedWordSize,
{
    /// Checks whether the transfer is still ongoing
    pub fn is_active(
        &self,
        rx_handle: &dma::Handle<Rx::Instance, state::Enabled>,
        tx_handle: &dma::Handle<Tx::Instance, state::Enabled>,
    ) -> bool {
        self.rx.is_active(rx_handle) || self.tx.is_active(tx_handle)
    }

    /// Waits for the transfer to end
    ///
    /// This method will block if the transfer is still ongoing. If you want
    /// this method to return immediately, first check whether the transfer is
    /// still ongoing by calling `is_active`.
    ///
    /// An ongoing transfer needs exlusive access to some resources, namely the
    /// data buffer, the DMA stream, and the peripheral. Those have been moved
    /// into the `Transfer` instance to prevent concurrent access to them. This
    /// method returns those resources, so they can be used again.
    pub fn wait(
        self,
        rx_handle: &dma::Handle<Rx::Instance, state::Enabled>,
        tx_handle: &dma::Handle<Tx::Instance, state::Enabled>,
    ) -> WaitResult<Word, I, P, Rx, Tx, Buffer> {
        let (rx_res, rx_err) = match self.rx.wait(rx_handle) {
            Ok(res) => (res, None),
            Err((res, err)) => (res, Some(err)),
        };
        let (tx_res, tx_err) = match self.tx.wait(tx_handle) {
            Ok(res) => (res, None),
            Err((res, err)) => (res, Some(err)),
        };

        let res = TransferResources {
            rx_stream: rx_res.stream,
            tx_stream: tx_res.stream,
            target: self.target,
            buffer: self.buffer,
        };

        if let Some(err) = rx_err {
            return Err((res, err));
        }
        if let Some(err) = tx_err {
            return Err((res, err));
        }

        Ok(res)
    }
}

/// Returned by [`Transfer::wait`]
pub type WaitResult<Word, I, P, Rx, Tx, Buffer> = Result<
    TransferResources<Word, I, P, Rx, Tx, Buffer>,
    (TransferResources<Word, I, P, Rx, Tx, Buffer>, dma::Error),
>;

/// The resources that an ongoing transfer needs exclusive access to
pub struct TransferResources<Word, I, P, Rx: dma::Target, Tx: dma::Target, Buffer> {
    pub rx_stream: Rx::Stream,
    pub tx_stream: Tx::Stream,
    pub target: Spi<I, P, Enabled<Word>>,
    pub buffer: Pin<Buffer>,
}

// As `TransferResources` is used in the error variant of `Result`, it needs a
// `Debug` implementation to enable stuff like `unwrap` and `expect`. This can't
// be derived without putting requirements on the type arguments.
impl<Word, I, P, Rx, Tx, Buffer> fmt::Debug for TransferResources<Word, I, P, Rx, Tx, Buffer>
where
    Rx: dma::Target,
    Tx: dma::Target,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "TransferResources {{ .. }}")
    }
}

/// Indicates that the SPI peripheral is enabled
///
/// The `Word` type parameter indicates which word size the peripheral is
/// configured for.
pub struct Enabled<Word>(PhantomData<Word>);

pub trait SupportedWordSize: dma::SupportedWordSize + private::Sealed {
    fn frxth() -> cr2::FRXTH_A;
    fn ds() -> cr2::DS_A;
}

impl private::Sealed for u8 {}
impl SupportedWordSize for u8 {
    fn frxth() -> cr2::FRXTH_A {
        cr2::FRXTH_A::QUARTER
    }

    fn ds() -> cr2::DS_A {
        cr2::DS_A::EIGHTBIT
    }
}

impl private::Sealed for u16 {}
impl SupportedWordSize for u16 {
    fn frxth() -> cr2::FRXTH_A {
        cr2::FRXTH_A::HALF
    }

    fn ds() -> cr2::DS_A {
        cr2::DS_A::SIXTEENBIT
    }
}

mod private {
    /// Prevents code outside of the parent module from implementing traits
    ///
    /// This trait is located in a module that is not accessible outside of the
    /// parent module. This means that any trait that requires `Sealed` cannot
    /// be implemented only in the parent module.
    pub trait Sealed {}
}