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