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}