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}