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}