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