nrf52_hal_common/spim.rs
1//! HAL interface to the SPIM peripheral
2//!
3//! See product specification, chapter 31.
4use core::ops::Deref;
5use core::sync::atomic::{compiler_fence, Ordering::SeqCst};
6
7pub use crate::target::spim0::frequency::FREQUENCYW as Frequency;
8pub use embedded_hal::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
9
10use crate::target::{spim0, SPIM0};
11use core::iter::repeat_with;
12
13#[cfg(any(feature = "52832", feature = "52840"))]
14use crate::target::{SPIM1, SPIM2};
15
16use crate::gpio::{Floating, Input, Output, Pin, PushPull};
17use crate::prelude::*;
18use crate::target_constants::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
19use crate::{slice_in_ram, DmaSlice};
20
21pub trait SpimExt: Deref<Target = spim0::RegisterBlock> + Sized {
22 fn constrain(self, pins: Pins, frequency: Frequency, mode: Mode, orc: u8) -> Spim<Self>;
23}
24
25macro_rules! impl_spim_ext {
26 ($($spim:ty,)*) => {
27 $(
28 impl SpimExt for $spim {
29 fn constrain(self, pins: Pins, frequency: Frequency, mode: Mode, orc: u8) -> Spim<Self> {
30 Spim::new(self, pins, frequency, mode, orc)
31 }
32 }
33 )*
34 }
35}
36
37impl_spim_ext!(SPIM0,);
38
39#[cfg(any(feature = "52832", feature = "52840"))]
40impl_spim_ext!(SPIM1, SPIM2,);
41
42/// Interface to a SPIM instance
43///
44/// This is a very basic interface that comes with the following limitations:
45/// - The SPIM instances share the same address space with instances of SPIS,
46/// SPI, TWIM, TWIS, and TWI. You need to make sure that conflicting instances
47/// are disabled before using `Spim`. See product specification, section 15.2.
48pub struct Spim<T>(T);
49
50impl<T> embedded_hal::blocking::spi::Transfer<u8> for Spim<T>
51where
52 T: SpimExt,
53{
54 type Error = Error;
55
56 fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Error> {
57 // If the slice isn't in RAM, we can't write back to it at all
58 ram_slice_check(words)?;
59
60 words.chunks(EASY_DMA_SIZE).try_for_each(|chunk| {
61 self.do_spi_dma_transfer(
62 DmaSlice::from_slice(chunk),
63 DmaSlice::from_slice(chunk),
64 )
65 })?;
66
67 Ok(words)
68 }
69}
70
71impl<T> embedded_hal::blocking::spi::Write<u8> for Spim<T>
72where
73 T: SpimExt,
74{
75 type Error = Error;
76
77 fn write<'w>(&mut self, words: &'w [u8]) -> Result<(), Error> {
78 // Mask on segment where Data RAM is located on nrf52840 and nrf52832
79 // Upper limit is choosen to entire area where DataRam can be placed
80 let needs_copy = !slice_in_ram(words);
81
82 let chunk_sz = if needs_copy {
83 FORCE_COPY_BUFFER_SIZE
84 } else {
85 EASY_DMA_SIZE
86 };
87
88 let step = if needs_copy {
89 Self::spi_dma_copy
90 } else {
91 Self::spi_dma_no_copy
92 };
93
94 words.chunks(chunk_sz).try_for_each(|c| step(self, c))
95 }
96}
97impl<T> Spim<T>
98where
99 T: SpimExt,
100{
101 fn spi_dma_no_copy(&mut self, chunk: &[u8]) -> Result<(), Error> {
102 self.do_spi_dma_transfer(DmaSlice::from_slice(chunk), DmaSlice::null())
103 }
104
105 fn spi_dma_copy(&mut self, chunk: &[u8]) -> Result<(), Error> {
106 let mut buf = [0u8; FORCE_COPY_BUFFER_SIZE];
107 buf[..chunk.len()].copy_from_slice(chunk);
108
109 self.do_spi_dma_transfer(
110 DmaSlice::from_slice(&buf[..chunk.len()]),
111 DmaSlice::null(),
112 )
113 }
114
115 pub fn new(spim: T, pins: Pins, frequency: Frequency, mode: Mode, orc: u8) -> Self {
116 // Select pins
117 spim.psel.sck.write(|w| {
118 let w = unsafe { w.pin().bits(pins.sck.pin) };
119 #[cfg(feature = "52840")]
120 let w = w.port().bit(pins.sck.port);
121 w.connect().connected()
122 });
123
124 match pins.mosi {
125 Some(mosi) => spim.psel.mosi.write(|w| {
126 let w = unsafe { w.pin().bits(mosi.pin) };
127 #[cfg(feature = "52840")]
128 let w = w.port().bit(mosi.port);
129 w.connect().connected()
130 }),
131 None => spim.psel.mosi.write(|w| w.connect().disconnected()),
132 }
133 match pins.miso {
134 Some(miso) => spim.psel.miso.write(|w| {
135 let w = unsafe { w.pin().bits(miso.pin) };
136 #[cfg(feature = "52840")]
137 let w = w.port().bit(miso.port);
138 w.connect().connected()
139 }),
140 None => spim.psel.miso.write(|w| w.connect().disconnected()),
141 }
142
143 // Enable SPIM instance
144 spim.enable.write(|w| w.enable().enabled());
145
146 // Configure mode
147 spim.config.write(|w| {
148 // Can't match on `mode` due to embedded-hal, see https://github.com/rust-embedded/embedded-hal/pull/126
149 if mode == MODE_0 {
150 w.order().msb_first().cpol().active_high().cpha().leading()
151 } else if mode == MODE_1 {
152 w.order().msb_first().cpol().active_high().cpha().trailing()
153 } else if mode == MODE_2 {
154 w.order().msb_first().cpol().active_low().cpha().leading()
155 } else {
156 w.order().msb_first().cpol().active_low().cpha().trailing()
157 }
158 });
159
160 // Configure frequency
161 spim.frequency.write(|w| w.frequency().variant(frequency));
162
163 // Set over-read character to `0`
164 spim.orc.write(|w|
165 // The ORC field is 8 bits long, so `0` is a valid value to write
166 // there.
167 unsafe { w.orc().bits(orc) });
168
169 Spim(spim)
170 }
171
172 /// Internal helper function to setup and execute SPIM DMA transfer
173 fn do_spi_dma_transfer(
174 &mut self,
175 tx: DmaSlice,
176 rx: DmaSlice,
177 ) -> Result<(), Error> {
178
179 // Conservative compiler fence to prevent optimizations that do not
180 // take in to account actions by DMA. The fence has been placed here,
181 // before any DMA action has started
182 compiler_fence(SeqCst);
183
184 // Set up the DMA write
185 self.0
186 .txd
187 .ptr
188 .write(|w| unsafe { w.ptr().bits(tx.ptr) });
189
190 self.0.txd.maxcnt.write(|w|
191 // Note that that nrf52840 maxcnt is a wider
192 // type than a u8, so we use a `_` cast rather than a `u8` cast.
193 // The MAXCNT field is thus at least 8 bits wide and accepts the full
194 // range of values that fit in a `u8`.
195 unsafe { w.maxcnt().bits(tx.len as _ ) });
196
197 // Set up the DMA read
198 self.0.rxd.ptr.write(|w|
199 // This is safe for the same reasons that writing to TXD.PTR is
200 // safe. Please refer to the explanation there.
201 unsafe { w.ptr().bits(rx.ptr) });
202 self.0.rxd.maxcnt.write(|w|
203 // This is safe for the same reasons that writing to TXD.MAXCNT is
204 // safe. Please refer to the explanation there.
205 unsafe { w.maxcnt().bits(rx.len as _) });
206
207 // Start SPI transaction
208 self.0.tasks_start.write(|w|
209 // `1` is a valid value to write to task registers.
210 unsafe { w.bits(1) });
211
212 // Conservative compiler fence to prevent optimizations that do not
213 // take in to account actions by DMA. The fence has been placed here,
214 // after all possible DMA actions have completed
215 compiler_fence(SeqCst);
216
217 // Wait for END event
218 //
219 // This event is triggered once both transmitting and receiving are
220 // done.
221 while self.0.events_end.read().bits() == 0 {}
222
223 // Reset the event, otherwise it will always read `1` from now on.
224 self.0.events_end.write(|w| w);
225
226 // Conservative compiler fence to prevent optimizations that do not
227 // take in to account actions by DMA. The fence has been placed here,
228 // after all possible DMA actions have completed
229 compiler_fence(SeqCst);
230
231 if self.0.txd.amount.read().bits() != tx.len {
232 return Err(Error::Transmit);
233 }
234 if self.0.rxd.amount.read().bits() != rx.len {
235 return Err(Error::Receive);
236 }
237 Ok(())
238 }
239
240 /// Read from an SPI slave
241 ///
242 /// This method is deprecated. Consider using `transfer` or `transfer_split`
243 #[inline(always)]
244 pub fn read(
245 &mut self,
246 chip_select: &mut Pin<Output<PushPull>>,
247 tx_buffer: &[u8],
248 rx_buffer: &mut [u8],
249 ) -> Result<(), Error> {
250 self.transfer_split_uneven(chip_select, tx_buffer, rx_buffer)
251 }
252
253 /// Read and write from a SPI slave, using a single buffer
254 ///
255 /// This method implements a complete read transaction, which consists of
256 /// the master transmitting what it wishes to read, and the slave responding
257 /// with the requested data.
258 ///
259 /// Uses the provided chip select pin to initiate the transaction. Transmits
260 /// all bytes in `buffer`, then receives an equal number of bytes.
261 pub fn transfer(
262 &mut self,
263 chip_select: &mut Pin<Output<PushPull>>,
264 buffer: &mut [u8],
265 ) -> Result<(), Error> {
266 ram_slice_check(buffer)?;
267
268 chip_select.set_low();
269
270 // Don't return early, as we must reset the CS pin
271 let res = buffer.chunks(EASY_DMA_SIZE).try_for_each(|chunk| {
272 self.do_spi_dma_transfer(
273 DmaSlice::from_slice(chunk),
274 DmaSlice::from_slice(chunk),
275 )
276 });
277
278 chip_select.set_high();
279
280 res
281 }
282
283 /// Read and write from a SPI slave, using separate read and write buffers
284 ///
285 /// This method implements a complete read transaction, which consists of
286 /// the master transmitting what it wishes to read, and the slave responding
287 /// with the requested data.
288 ///
289 /// Uses the provided chip select pin to initiate the transaction. Transmits
290 /// all bytes in `tx_buffer`, then receives bytes until `rx_buffer` is full.
291 ///
292 /// If `tx_buffer.len() != rx_buffer.len()`, the transaction will stop at the
293 /// smaller of either buffer.
294 pub fn transfer_split_even(
295 &mut self,
296 chip_select: &mut Pin<Output<PushPull>>,
297 tx_buffer: &[u8],
298 rx_buffer: &mut [u8],
299 ) -> Result<(), Error> {
300 ram_slice_check(tx_buffer)?;
301 ram_slice_check(rx_buffer)?;
302
303 let txi = tx_buffer.chunks(EASY_DMA_SIZE);
304 let rxi = rx_buffer.chunks_mut(EASY_DMA_SIZE);
305
306 chip_select.set_low();
307
308 // Don't return early, as we must reset the CS pin
309 let res = txi.zip(rxi).try_for_each(|(t, r)| {
310 self.do_spi_dma_transfer(DmaSlice::from_slice(t), DmaSlice::from_slice(r))
311 });
312
313 chip_select.set_high();
314
315 res
316 }
317
318 /// Read and write from a SPI slave, using separate read and write buffers
319 ///
320 /// This method implements a complete read transaction, which consists of
321 /// the master transmitting what it wishes to read, and the slave responding
322 /// with the requested data.
323 ///
324 /// Uses the provided chip select pin to initiate the transaction. Transmits
325 /// all bytes in `tx_buffer`, then receives bytes until `rx_buffer` is full.
326 ///
327 /// This method is more complicated than the other `transfer` methods because
328 /// it is allowed to perform transactions where `tx_buffer.len() != rx_buffer.len()`.
329 /// If this occurs, extra incoming bytes will be discarded, OR extra outgoing bytes
330 /// will be filled with the `orc` value.
331 pub fn transfer_split_uneven(
332 &mut self,
333 chip_select: &mut Pin<Output<PushPull>>,
334 tx_buffer: &[u8],
335 rx_buffer: &mut [u8],
336 ) -> Result<(), Error> {
337 ram_slice_check(tx_buffer)?;
338 ram_slice_check(rx_buffer)?;
339 // For the tx and rx, we want to return Some(chunk)
340 // as long as there is data to send. We then chain a repeat to
341 // the end so once all chunks have been exhausted, we will keep
342 // getting Nones out of the iterators
343 let txi = tx_buffer
344 .chunks(EASY_DMA_SIZE)
345 .map(|c| Some(c))
346 .chain(repeat_with(|| None));
347
348 let rxi = rx_buffer
349 .chunks_mut(EASY_DMA_SIZE)
350 .map(|c| Some(c))
351 .chain(repeat_with(|| None));
352
353 chip_select.set_low();
354
355 // We then chain the iterators together, and once BOTH are feeding
356 // back Nones, then we are done sending and receiving
357 //
358 // Don't return early, as we must reset the CS pin
359 let res = txi.zip(rxi)
360 .take_while(|(t, r)| t.is_some() && r.is_some())
361 // We also turn the slices into either a DmaSlice (if there was data), or a null
362 // DmaSlice (if there is no data)
363 .map(|(t, r)| {
364 (
365 t.map(|t| DmaSlice::from_slice(t))
366 .unwrap_or_else(|| DmaSlice::null()),
367 r.map(|r| DmaSlice::from_slice(r))
368 .unwrap_or_else(|| DmaSlice::null()),
369 )
370 })
371 .try_for_each(|(t, r)| {
372 self.do_spi_dma_transfer(t, r)
373 });
374
375 chip_select.set_high();
376
377 res
378 }
379
380 /// Write to an SPI slave
381 ///
382 /// This method uses the provided chip select pin to initiate the
383 /// transaction, then transmits all bytes in `tx_buffer`. All incoming
384 /// bytes are discarded.
385 pub fn write(
386 &mut self,
387 chip_select: &mut Pin<Output<PushPull>>,
388 tx_buffer: &[u8],
389 ) -> Result<(), Error> {
390 ram_slice_check(tx_buffer)?;
391 self.transfer_split_uneven(chip_select, tx_buffer, &mut [0u8; 0])
392 }
393
394 /// Return the raw interface to the underlying SPIM peripheral
395 pub fn free(self) -> T {
396 self.0
397 }
398}
399
400/// GPIO pins for SPIM interface
401pub struct Pins {
402 /// SPI clock
403 pub sck: Pin<Output<PushPull>>,
404
405 /// MOSI Master out, slave in
406 /// None if unused
407 pub mosi: Option<Pin<Output<PushPull>>>,
408
409 /// MISO Master in, slave out
410 /// None if unused
411 pub miso: Option<Pin<Input<Floating>>>,
412}
413
414#[derive(Debug)]
415pub enum Error {
416 TxBufferTooLong,
417 RxBufferTooLong,
418 /// EasyDMA can only read from data memory, read only buffers in flash will fail
419 DMABufferNotInDataMemory,
420 Transmit,
421 Receive,
422}
423
424fn ram_slice_check(slice: &[u8]) -> Result<(), Error> {
425 if slice_in_ram(slice) {
426 Ok(())
427 } else {
428 Err(Error::DMABufferNotInDataMemory)
429 }
430}