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}