stm32_hal2/spi/
mod.rs

1//! Support for the Serial Peripheral Interface (SPI) bus peripheral.
2//! Provides APIs to configure, read, and write from
3//! SPI, with blocking, nonblocking, and DMA functionality.
4
5use core::ops::Deref;
6
7cfg_if::cfg_if! {
8    if #[cfg(any(feature = "h5", feature = "h7"))] {
9        mod h;
10        pub use h::*;
11    } else {
12        mod baseline;
13        pub use baseline::*;
14    }
15}
16
17use cfg_if::cfg_if;
18
19cfg_if! {
20    if #[cfg(feature = "c0")] { // pac bug?
21       use crate::{pac::{self, DMA as DMA1}};
22    } else {
23        use crate::pac::{self, DMA1};
24    }
25}
26#[cfg(any(feature = "f3", feature = "l4"))]
27use crate::dma::DmaInput;
28#[cfg(not(any(feature = "f4", feature = "l552")))]
29use crate::dma::{self, ChannelCfg, DmaChannel};
30use crate::{error::Result, util::RccPeriph}; // todo temp
31
32#[macro_export]
33macro_rules! check_errors {
34    ($sr:expr) => {
35        #[cfg(feature = "h7")]
36        let crc_error = $sr.crce().bit_is_set();
37        #[cfg(not(feature = "h7"))]
38        let crc_error = $sr.crcerr().bit_is_set();
39
40        if $sr.ovr().bit_is_set() {
41            return Err(Error::SpiError(SpiError::Overrun));
42        } else if $sr.modf().bit_is_set() {
43            return Err(Error::SpiError(SpiError::ModeFault));
44        } else if crc_error {
45            return Err(Error::SpiError(SpiError::Crc));
46        }
47    };
48}
49
50/// SPI error
51#[non_exhaustive]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53#[derive(Debug, Clone, Copy, Eq, PartialEq)]
54pub enum SpiError {
55    /// Overrun occurred
56    Overrun,
57    /// Mode fault occurred
58    ModeFault,
59    /// CRC error
60    Crc,
61    DuplexFailed, // todo temp?
62}
63
64/// Set the factor to divide the APB clock by to set baud rate. Sets `SPI_CR1` register, `BR` field.
65/// On H7, sets CFG1 register, `MBR` field.
66#[derive(Copy, Clone)]
67#[repr(u8)]
68pub enum BaudRate {
69    Div2 = 0b000,
70    Div4 = 0b001,
71    Div8 = 0b010,
72    Div16 = 0b011,
73    Div32 = 0b100,
74    Div64 = 0b101,
75    Div128 = 0b110,
76    Div256 = 0b111,
77}
78
79#[derive(Clone, Copy)]
80#[repr(u8)]
81/// FIFO reception threshold Sets `SPI_CR2` register, `FRXTH` field.
82pub enum ReceptionThresh {
83    /// RXNE event is generated if the FIFO level is greater than or equal to 1/2 (16-bit)
84    D16 = 0,
85    /// RXNE event is generated if the FIFO level is greater than or equal to 1/4 (8-bit)
86    D8 = 1,
87}
88
89#[derive(Clone, Copy, PartialEq)]
90/// Select the duplex communication mode between the 2 devices. Sets `CR1` register, `BIDIMODE`,
91/// and `RXONLY` fields.
92pub enum SpiCommMode {
93    FullDuplex,
94    HalfDuplex,
95    /// Simplex Transmit only. (Cfg same as Full Duplex, but ignores input)
96    TransmitOnly,
97    /// Simplex Receive only.
98    ReceiveOnly,
99}
100
101#[derive(Clone, Copy, PartialEq)]
102/// Used for managing NSS / CS pin. Sets CR1 register, SSM field.
103/// On H7, sets CFG2 register, `SSOE` field.
104pub enum SlaveSelect {
105    ///  In this configuration, slave select information
106    /// is driven internally by the SSI bit value in register SPIx_CR1. The external NSS pin is
107    /// free for other application uses.
108    Software,
109    /// This configuration is only used when the
110    /// MCU is set as master. The NSS pin is managed by the hardware. The NSS signal
111    /// is driven low as soon as the SPI is enabled in master mode (SPE=1), and is kept
112    /// low until the SPI is disabled (SPE =0). A pulse can be generated between
113    /// continuous communications if NSS pulse mode is activated (NSSP=1). The SPI
114    /// cannot work in multimaster configuration with this NSS setting.
115    HardwareOutEnable,
116    /// If the microcontroller is acting as the
117    /// master on the bus, this configuration allows multimaster capability. If the NSS pin
118    /// is pulled low in this mode, the SPI enters master mode fault state and the device is
119    /// automatically reconfigured in slave mode. In slave mode, the NSS pin works as a
120    /// standard “chip select” input and the slave is selected while NSS line is at low level.
121    HardwareOutDisable,
122}
123
124cfg_if! {
125    if #[cfg(feature = "embedded_hal")] {
126        type SpiModeType = embedded_hal::spi::Mode;
127    } else {
128        #[derive(Clone, Copy)]
129        #[repr(u8)]
130        /// Clock polarity. Sets CFGR2 register, CPOL field. Stored in the config as a field of `SpiMode`.
131        pub enum SpiPolarity {
132            /// Clock signal low when idle
133            IdleLow = 0,
134            /// Clock signal high when idle
135            IdleHigh = 1,
136        }
137
138        #[derive(Clone, Copy)]
139        #[repr(u8)]
140        /// Clock phase. Sets CFGR2 register, CPHA field. Stored in the config as a field of `SpiMode`.
141        pub enum SpiPhase {
142            /// Data in "captured" on the first clock transition
143            CaptureOnFirstTransition = 0,
144            /// Data in "captured" on the second clock transition
145            CaptureOnSecondTransition = 1,
146        }
147
148        #[derive(Clone, Copy)]
149        /// SPI mode. Sets CFGR2 reigster, CPOL and CPHA fields.
150        pub struct SpiMode {
151            /// Clock polarity
152            pub polarity: SpiPolarity,
153            /// Clock phase
154            pub phase: SpiPhase,
155        }
156
157        impl SpiMode {
158            /// Set Spi Mode 0: Idle low, capture on first transition.
159            /// Data sampled on rising edge and shifted out on the falling edge
160            pub fn mode0() -> Self {
161                Self {
162                    polarity: SpiPolarity::IdleLow,
163                    phase: SpiPhase::CaptureOnFirstTransition,
164                }
165            }
166
167            /// Set Spi Mode 1: Idle low, capture on second transition.
168            /// Data sampled on the falling edge and shifted out on the rising edge
169            pub fn mode1() -> Self {
170                Self {
171                    polarity: SpiPolarity::IdleLow,
172                    phase: SpiPhase::CaptureOnSecondTransition,
173                }
174            }
175
176            /// Set Spi Mode 2: Idle high, capture on first transition.
177            /// Data sampled on the rising edge and shifted out on the falling edge
178            pub fn mode2() -> Self {
179                Self {
180                    polarity: SpiPolarity::IdleHigh,
181                    phase: SpiPhase::CaptureOnFirstTransition,
182                }
183            }
184
185            /// Set Spi Mode 3: Idle high, capture on second transition.
186            /// Data sampled on the falling edge and shifted out on the rising edge
187            pub fn mode3() -> Self {
188                Self {
189                    polarity: SpiPolarity::IdleHigh,
190                    phase: SpiPhase::CaptureOnSecondTransition,
191                }
192            }
193        }
194
195        type SpiModeType = SpiMode;
196    }
197}
198
199#[derive(Clone)]
200/// Configuration data for SPI.
201pub struct SpiConfig {
202    /// SPI mode associated with Polarity and Phase. Defaults to Mode0: Idle low, capture on first transition.
203    pub mode: SpiModeType,
204    /// Sets the (duplex) communication mode between the devices. Defaults to full duplex.
205    pub comm_mode: SpiCommMode,
206    /// Controls use of hardware vs software CS/NSS pin. Defaults to software.
207    pub slave_select: SlaveSelect,
208    /// Data size. Defaults to 8 bits.
209    pub data_size: DataSize,
210    /// FIFO reception threshhold. Defaults to 8 bits.
211    pub fifo_reception_thresh: ReceptionThresh,
212    // pub cs_delay: f32,
213    // pub swap_miso_mosi: bool,
214    // pub suspend_when_inactive: bool,
215}
216
217impl Default for SpiConfig {
218    fn default() -> Self {
219        cfg_if! {
220            if #[cfg(feature = "embedded_hal")] {
221                let mode0 = embedded_hal::spi::MODE_0;
222            } else {
223                let mode0 = SpiModeType::mode0();
224            }
225        }
226
227        Self {
228            mode: mode0,
229            comm_mode: SpiCommMode::FullDuplex,
230            slave_select: SlaveSelect::Software,
231            data_size: DataSize::D8,
232            fifo_reception_thresh: ReceptionThresh::D8,
233        }
234    }
235}
236
237/// Represents a Serial Peripheral Interface (SPI) peripheral.
238pub struct Spi<R> {
239    pub regs: R,
240    pub cfg: SpiConfig,
241}
242
243impl<R> Spi<R>
244where
245    R: Deref<Target = pac::spi1::RegisterBlock> + RccPeriph,
246{
247    /// Stop a DMA transfer. Stops the channel, and disables the `txdmaen` and `rxdmaen` bits.
248    /// Run this after each transfer completes - you may wish to do this in an interrupt
249    /// (eg DMA transfer complete) instead of blocking. `channel2` is an optional second channel
250    /// to stop; eg if you have both a tx and rx channel.
251    #[cfg(not(any(feature = "f4", feature = "l552")))]
252    pub fn stop_dma(
253        &mut self,
254        channel: DmaChannel,
255        channel2: Option<DmaChannel>,
256        dma_periph: dma::DmaPeriph,
257    ) -> Result<()> {
258        // (RM:) To close communication it is mandatory to follow these steps in order:
259        // 1. Disable DMA streams for Tx and Rx in the DMA registers, if the streams are used.
260
261        dma::stop(dma_periph, channel)?;
262        if let Some(ch2) = channel2 {
263            dma::stop(dma_periph, ch2)?;
264        };
265
266        // 2. Disable the SPI by following the SPI disable procedure:
267        // self.disable();
268
269        // 3. Disable DMA Tx and Rx buffers by clearing the TXDMAEN and RXDMAEN bits in the
270        // SPI_CR2 register, if DMA Tx and/or DMA Rx are used.
271
272        #[cfg(not(feature = "h7"))]
273        self.regs.cr2().modify(|_, w| {
274            w.txdmaen().clear_bit();
275            w.rxdmaen().clear_bit()
276        });
277
278        #[cfg(feature = "h7")]
279        self.regs.cfg1().modify(|_, w| {
280            w.txdmaen().clear_bit();
281            w.rxdmaen().clear_bit()
282        });
283
284        Ok(())
285    }
286
287    /// Convenience function that clears the interrupt, and stops the transfer. For use with the TC
288    /// interrupt only.
289    #[cfg(not(any(feature = "f4", feature = "l552")))]
290    pub fn cleanup_dma(
291        &mut self,
292        dma_periph: dma::DmaPeriph,
293        channel_tx: DmaChannel,
294        channel_rx: Option<DmaChannel>,
295    ) -> Result<()> {
296        // The hardware seems to automatically enable Tx too; and we use it when transmitting.
297        dma::clear_interrupt(dma_periph, channel_tx, dma::DmaInterrupt::TransferComplete)?;
298
299        if let Some(ch_rx) = channel_rx {
300            dma::clear_interrupt(dma_periph, ch_rx, dma::DmaInterrupt::TransferComplete)?;
301        }
302
303        self.stop_dma(channel_tx, channel_rx, dma_periph)
304    }
305
306    /// Print the (raw) contents of the status register.
307    pub fn read_status(&self) -> u32 {
308        // todo july 2025? into
309        unsafe { self.regs.sr().read().bits().into() }
310    }
311}