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