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