atsamd_hal/sercom/
spi_future.rs

1//! A [`Future`]-like interface for SPI transactions
2//!
3//! An [`SpiFuture`] takes ownership of an [`Spi`] `struct` and a `[u8]`-like
4//! buffer. It then executes a full-duplex SPI transaction using iterrupts. On
5//! each `RXC` or `DRE` interrupt, the [`SpiFuture`] reads or sends `STEP` bytes
6//! of the buffer, where `STEP` is a value that depends on [`CharSize`], for
7//! SAMD11 & SAMD21 chips, or [`Length`], for SAMD51 & SAME5x chips.
8//!
9//! The provided buffer must implement [`AsRef`] and [`AsMut`] for `[u8]`, it
10//! must have an appropriate length (see below), and it must have a `'static`
11//! lifetime, i.e. it must be owned or a `&'static mut` reference.
12//!
13//! [`SpiFuture`] has extra, optional capabilities as well. It can accept a
14//! function or closure that will be called on completion of the transaction,
15//! acting like a [`Waker`]. And it can take a GPIO [`Pin`] to use as the SS
16//! line. If provided, the [`Pin`] will be set low at the beginnging of the
17//! transfer and brought high at completion.
18//!
19//! Calling [`start`] will enable the `DRE` and `RXC` interrupts and begin the
20//! transaction.
21//!
22//! ```
23//! use atsamd_hal::gpio::{Pin, PA10, PushPullOutput};
24//! use atsamd_hal::sercom::spi::AnySpi;
25//! use atsamd_hal::sercom::spi_future::SpiFuture;
26//!
27//! fn wake_up() {
28//!     //...
29//! }
30//!
31//! fn use_future(spi: impl AnySpi, ss: Pin<PA10, PushPullOutput>) {
32//!     let buf = [0_u8; 12];
33//!     let future = SpiFuture::new(spi, buf)
34//!         .with_waker(wake_up)
35//!         .with_ss(ss);
36//!     future.start();
37//! }
38//! ```
39//!
40//! When sending and receiving finish, the [`SpiFuture`] will automatically
41//! disable the `DRE` and `RXC` interrupts. To test whether an [`SpiFuture`] is
42//! complete, use the [`poll`] method. While the transaction is in progress, it
43//! will return [`Poll::Pending`]. When the transaction is complete, it will
44//! return [`Poll::Ready`]. Once complete, you can consume the [`SpiFuture`] and
45//! [`free`] the constituent pieces. Doing so before the transfer has completed
46//! is `unsafe`.
47//!
48//! The actual transfer is performed by the [`send`] and [`recv`] methods, which
49//! should be called from the `DRE` and `RXC` interrupt handlers, respectively.
50//!
51//! ## `STEP` size and buffer length
52//!
53//! For SAMD11 & SAMD21 chips, `STEP` is equal to the number of bytes in the
54//! corresponding the [`CharSize::Word`] type, i.e. 1 for [`EightBit`] and 2 for
55//! [`NineBit`]. For SAMD51 & SAME5x chips, `STEP` is equal to the [`Length`] or
56//! 4, whichever is less.
57//!
58//! The provided buffer must have an appropriate length. For SAMD11 & SAMD21
59//! chips, as well as SAMDx5x chips with [`Length`]` <= 4`, a single write of
60//! `STEP` bytes represents an entire SPI transaction. In these cases, the
61//! provided buffer must represent an integer number of transactions. For
62//! example, a SAMD51 [`Spi`] struct with a [`Length`] of 3 could use buffers of
63//! length 3, 6, 9, etc. For longer [`Length`] values, the provided buffer must
64//! represent exactly one SPI transaction, so the buffer length must be equal to
65//! [`Length`]. For example, a SAMD51 [`Spi`] struct with a [`Length`] of 17
66//! could only use a buffer with exactly 17 bytes.
67//!
68//! Keep in mind that [`SpiFuture`] works only with `[u8]`-like things, which
69//! can introduce some limitations.
70//!
71//! Suppose you plan to execute [`NineBit`] transactions with a SAMD21 chip.
72//! Your data may come in the form of a `[u16]` slice. However, to use it with
73//! [`SpiFuture`], you will need reformulate it as a `[u8]` slice. The easiest
74//! way to do so is probably to transmute the slice to `[u8]` or cast the
75//! reference to `&'static mut [u8]`. Both of these operations are sound,
76//! because [`u8`] has no alignment requirements.
77//!
78//! In another scenario, suppose you wanted to use a SAMx5x chip with a
79//! transaction [`Length`] of 3 bytes. Your data might come in the form of a
80//! `[u32]` slice. In this situation, it would **not** be appropriate to
81//! transmute or cast the data to a `[u8]` slice. [`SpiFuture`] expects the data
82//! to be a *byte-packed* `[u8]` slice, so the extra byte in each `u32` makes it
83//! incompatible.
84//!
85//! ## [RTIC] example
86//!
87//! The [RTIC] framework provides a convenient way to store a `static`ally
88//! allocated [`SpiFuture`], so that it can be accessed by both the interrupt
89//! handlers and user code. The following example shows how [`SpiFuture`]s might
90//! be used for a series of transactions. It was written for a SAMx5x chip, and
91//! it uses features from the latest release of [RTIC], `v0.6-alpha.0`.
92//!
93//! ```
94//! use core::task::Poll;
95//! use atsamd_hal::gpio::{PA08, PA09, PA10, PA11, Pin, PushPullOutput};
96//! use atsamd_hal::sercom::Sercom0;
97//! use atsamd_hal::sercom::pad::{IoSet1, Pad0, Pad1, Pad3};
98//! use atsamd_hal::sercom::spi::{self, Master, lengths::U12};
99//! use atsamd_hal::sercom::spi_future::SpiFuture;
100//!
101//! type Pads = spi::Pads<Sercom0, IoSet1, (Pad0, PA08), (Pad3, PA11), (Pad1, PA09)>;
102//! type SS = Pin<PA10, PushPullOutput>;
103//! type Spi = spi::Spi<spi::Config<Pads, Master, U12>>;
104//! type Future = SpiFuture<Spi, [u8; 12], SS, fn()>;
105//!
106//! //...
107//!
108//! #[resources]
109//! struct Resources {
110//!     #[task_local]
111//!     #[init(None)]
112//!     opt_spi_ss: Option<(Spi, SS)>,
113//!
114//!     #[lock_free]
115//!     #[init(None)]
116//!     opt_future: Option<Future>,
117//! }
118//!
119//! #[task(resources = [opt_spi_ss, opt_future])]
120//! fn task(ctx: task::Context) {
121//!     let task::Context { opt_spi_ss, opt_future } = ctx;
122//!     match opt_future {
123//!         Some(future) => {
124//!             if let Poll::Ready(_) = future.poll() {
125//!                 let (spi, buf, ss) = opt_future.take().unwrap().free();
126//!                 *opt_spi_ss = Some((spi, ss));
127//!                 consume_data(buf);
128//!             }
129//!         }
130//!         None => {
131//!             if let Some((spi, ss)) = opt_spi_ss.take() {
132//!                 let buf: [u8; 12] = produce_data();
133//!                 let future = opt_future.get_or_insert(
134//!                     SpiFuture::new(spi, buf)
135//!                         .with_waker(|| { task::spawn().ok(); })
136//!                         .with_ss(ss)
137//!                 );
138//!                 future.start();
139//!             }
140//!         }
141//!     }
142//! }
143//!
144//! #[task(binds = SERCOM0_0, resources = [opt_future])]
145//! fn dre(ctx: dre::Context) {
146//!     ctx.resources.opt_future.as_mut().unwrap().send();
147//! }
148//!
149//! #[task(binds = SERCOM0_2, resources = [opt_future])]
150//! fn rxc(ctx: rxc::Context) {
151//!     ctx.resources.opt_future.as_mut().unwrap().recv();
152//! }
153//!
154//! //...
155//! ```
156//!
157//! [`start`]: SpiFuture::start
158//! [`poll`]: SpiFuture::poll
159//! [`free`]: SpiFuture::free
160//! [`send`]: SpiFuture::send
161//! [`recv`]: SpiFuture::recv
162//! [`Spi`]: super::spi::Spi
163//! [`CharSize`]: super::spi::CharSize
164//! [`CharSize::Word`]: super::spi::CharSize::Word
165//! [`EightBit`]: super::spi::EightBit
166//! [`NineBit`]: super::spi::NineBit
167//! [`Length`]: super::spi::Length
168//! [`Pin`]: crate::gpio::pin::Pin
169//! [`Future`]: core::future::Future
170//! [`Waker`]: core::task::Waker
171//! [`Poll`]: core::task::Poll
172//! [RTIC]: https://rtic.rs/
173
174use atsamd_hal_macros::hal_cfg;
175
176use core::convert::Infallible;
177use core::task::Poll;
178
179use crate::ehal_02::digital::v2::OutputPin;
180
181use crate::gpio::pin::{OptionalPin, SomePin};
182use crate::typelevel::NoneT;
183
184use super::spi::{AnySpi, Error, Flags};
185
186#[allow(unused_imports)]
187// This isn't used in the `d11` or `d21` builds, but is for `doc` and `d5x`
188use super::spi::Spi;
189
190#[hal_cfg("sercom0-d5x")]
191use {
192    super::spi::{Capability, Config, DynLength, OpMode, StaticLength, ValidConfig, ValidPads},
193    typenum::Unsigned,
194};
195
196#[hal_cfg(any("sercom0-d11", "sercom0-d21"))]
197use core::mem::size_of;
198
199#[hal_cfg(any("sercom0-d11", "sercom0-d21"))]
200type Data = u16;
201
202#[hal_cfg("sercom0-d5x")]
203type Data = u32;
204
205#[cfg(doc)]
206#[hal_cfg(not("sercom0-d5x"))]
207/// This type is not present with the selected feature set, defined for
208/// documentation only
209pub enum DynLength {}
210
211//=============================================================================
212// CheckBufLen
213//=============================================================================
214
215/// Trait used to verify the [`SpiFuture`] buffer length
216#[allow(clippy::len_without_is_empty)]
217pub trait CheckBufLen: AnySpi {
218    #[hal_cfg("sercom0-d5x")]
219    /// [`Spi`] transaction length
220    ///
221    /// This value is zero for an [`Spi`] with [`DynLength`]
222    const LEN: usize = <Self::Size as Unsigned>::USIZE;
223
224    #[hal_cfg(any("sercom0-d11", "sercom0-d21"))]
225    /// [`Spi`] transaction length
226    ///
227    /// [`Spi`]: super::spi::Spi
228    const LEN: usize = size_of::<Self::Word>();
229
230    /// Return the [`Spi`] transaction length
231    ///
232    /// In most cases, this returns the corresponding constant. For SAMx5x chips
233    /// with [`DynLength`], this returns the result of [`Spi::get_dyn_length`].
234    ///
235    /// [`Spi`]: super::spi::Spi
236    #[inline]
237    fn len(&self) -> usize {
238        Self::LEN
239    }
240
241    /// Step size through the [`SpiFuture`] buffer
242    ///
243    /// This is equal to the number of bytes sent in each write to the SPI DATA
244    /// register. It is zero for an [`Spi`] with [`DynLength`].
245    ///
246    /// [`Spi`]: super::spi::Spi
247    const STEP: usize = if Self::LEN > 4 { 4 } else { Self::LEN };
248
249    /// Return the step size through the [`SpiFuture`] buffer
250    ///
251    /// In most cases, this returns the corresponding constant. For SAMx5x chips
252    /// with [`DynLength`], this returns a result calculated from [`Self::len`].
253    #[inline]
254    fn step(&self) -> usize {
255        Self::STEP
256    }
257
258    /// Check that the buffer has a valid length
259    ///
260    /// If the transaction length is greater than four, then the size of the
261    /// buffer must equal the transaction length. If the transaction length is
262    /// less than or equal to four, then the size of the buffer must be an
263    /// integer multiple of the transaction length.
264    ///
265    /// Both of these statements apply regardless of whether the [`Spi`] has a
266    /// [`DynLength`].
267    ///
268    /// [`Spi`]: super::spi::Spi
269    #[inline]
270    fn check_buf_len(&self, buf: &impl AsRef<[u8]>) {
271        let buf_len = buf.as_ref().len();
272        let self_len = self.len();
273        if (self_len > 4 && buf_len != self_len) || (self_len <= 4 && buf_len % self_len != 0) {
274            panic!("Invalid SpiFuture buffer length");
275        }
276    }
277}
278
279#[hal_cfg(any("sercom0-d11", "sercom0-d21"))]
280impl<S: AnySpi> CheckBufLen for S {}
281
282#[hal_cfg("sercom0-d5x")]
283impl<P, M, L, A> CheckBufLen for Spi<Config<P, M, L>, A>
284where
285    Config<P, M, L>: ValidConfig,
286    P: ValidPads,
287    M: OpMode,
288    L: StaticLength,
289    A: Capability,
290{
291}
292
293#[hal_cfg("sercom0-d5x")]
294impl<P, M, A> CheckBufLen for Spi<Config<P, M, DynLength>, A>
295where
296    Config<P, M, DynLength>: ValidConfig,
297    P: ValidPads,
298    M: OpMode,
299    A: Capability,
300{
301    #[inline]
302    fn len(&self) -> usize {
303        self.get_dyn_length() as usize
304    }
305
306    #[inline]
307    fn step(&self) -> usize {
308        let len = self.len();
309        if len > 4 {
310            4
311        } else {
312            len
313        }
314    }
315}
316
317//=============================================================================
318// ControlSS
319//=============================================================================
320
321/// Trait used to control the SS line during an [`SpiFuture`] transaction
322pub trait ControlSS: OptionalPin {
323    /// If an SS pin is present, assert it by bringing it low
324    fn assert(&mut self);
325
326    /// If an SS pin is present, deassert it by bringing it high
327    fn deassert(&mut self);
328}
329
330impl<P> ControlSS for P
331where
332    P: SomePin + OutputPin<Error = Infallible>,
333{
334    #[inline]
335    fn assert(&mut self) {
336        self.set_low().unwrap();
337    }
338
339    #[inline]
340    fn deassert(&mut self) {
341        self.set_high().unwrap();
342    }
343}
344
345impl ControlSS for NoneT {
346    fn assert(&mut self) {}
347    fn deassert(&mut self) {}
348}
349
350//=============================================================================
351// SpiFuture
352//=============================================================================
353
354/// A [`Future`]-like interface for SPI transactions
355///
356/// See the [module-level](self) documentation for more details.
357///
358/// [`Future`]: core::future::Future
359pub struct SpiFuture<S, B, SS = NoneT, W = fn()>
360where
361    S: CheckBufLen,
362    B: AsRef<[u8]> + AsMut<[u8]> + 'static,
363    SS: ControlSS,
364    W: FnOnce() + 'static,
365{
366    spi: S,
367    buf: B,
368    sent: usize,
369    rcvd: usize,
370    ss: SS,
371    waker: Option<W>,
372}
373
374impl<S, B> SpiFuture<S, B>
375where
376    S: CheckBufLen,
377    B: AsRef<[u8]> + AsMut<[u8]> + 'static,
378{
379    /// Create a new [`SpiFuture`] with no SS pin or waker
380    #[inline]
381    pub fn new(spi: S, buf: B) -> Self {
382        spi.check_buf_len(&buf);
383        SpiFuture {
384            spi,
385            buf,
386            sent: 0,
387            rcvd: 0,
388            ss: NoneT,
389            waker: None,
390        }
391    }
392}
393
394impl<S, B, W> SpiFuture<S, B, NoneT, W>
395where
396    S: CheckBufLen,
397    B: AsRef<[u8]> + AsMut<[u8]> + 'static,
398    W: FnOnce() + 'static,
399{
400    /// Add an SS pin to the [`SpiFuture`]
401    ///
402    /// This function changes the `SS` type, so it must take an owned `self`.
403    #[inline]
404    pub fn with_ss<SS>(self, pin: SS) -> SpiFuture<S, B, SS, W>
405    where
406        SS: SomePin + OutputPin<Error = Infallible>,
407    {
408        SpiFuture {
409            spi: self.spi,
410            buf: self.buf,
411            sent: self.sent,
412            rcvd: self.rcvd,
413            ss: pin,
414            waker: self.waker,
415        }
416    }
417}
418
419impl<S, B, SS> SpiFuture<S, B, SS>
420where
421    S: CheckBufLen,
422    B: AsRef<[u8]> + AsMut<[u8]> + 'static,
423    SS: ControlSS,
424{
425    /// Add a waker to the [`SpiFuture`]
426    ///
427    /// This function changes the waker type, so it must take an owned `self`.
428    #[inline]
429    pub fn with_waker<W>(self, waker: W) -> SpiFuture<S, B, SS, W>
430    where
431        W: FnOnce() + 'static,
432    {
433        SpiFuture {
434            spi: self.spi,
435            buf: self.buf,
436            sent: self.sent,
437            rcvd: self.rcvd,
438            ss: self.ss,
439            waker: Some(waker),
440        }
441    }
442}
443
444impl<S, B, SS, W> SpiFuture<S, B, SS, W>
445where
446    S: CheckBufLen,
447    B: AsRef<[u8]> + AsMut<[u8]> + 'static,
448    SS: ControlSS,
449    W: FnOnce() + 'static,
450{
451    /// Start the [`SpiFuture`] transaction
452    ///
453    /// This will assert the SS pin, if present, and enable the `DRE` and `RXC`
454    /// interrupts.
455    #[inline]
456    pub fn start(&mut self) {
457        self.ss.assert();
458        self.spi.as_mut().enable_interrupts(Flags::DRE | Flags::RXC);
459    }
460
461    /// Send the next set of bytes from the buffer
462    ///
463    /// This method should be called from the `DRE` interrupt handler. Once all
464    /// bytes of the transaction have been sent, this function will
465    /// automatically disable the `DRE` interrupt.
466    pub fn send(&mut self) -> Result<(), Error> {
467        let _ = self.spi.as_ref().read_flags_errors()?;
468        let buf = self.buf.as_ref();
469        if let Some(buf) = buf.get(self.sent..) {
470            let mut data = buf.iter();
471            let mut bytes = [0; 4];
472            let mut iter = bytes.iter_mut();
473            for _ in 0..self.spi.step() {
474                match (iter.next(), data.next()) {
475                    (Some(b), Some(d)) => *b = *d,
476                    _ => break,
477                }
478            }
479            let word = u32::from_le_bytes(bytes);
480            unsafe { self.spi.as_mut().write_data(word as Data) };
481            self.sent += self.spi.step();
482        }
483        if self.sent >= buf.len() {
484            self.spi.as_mut().disable_interrupts(Flags::DRE);
485        }
486        Ok(())
487    }
488
489    /// Received the next set of bytes and write them to the buffer
490    ///
491    /// This method should be called from the `RXC` interrupt handler. Once all
492    /// bytes of the transaction have been received, this function will
493    /// automatically disable the `RXC` interrupt, deassert the SS pin (if
494    /// present), and call the waker (if present).
495    pub fn recv(&mut self) -> Result<(), Error> {
496        let _ = self.spi.as_ref().read_flags_errors()?;
497        let buf = self.buf.as_mut();
498        if self.rcvd < self.sent {
499            let buf = unsafe { buf.get_unchecked_mut(self.rcvd..) };
500            let mut data = buf.iter_mut();
501            // Allow this lint as it will put out a warning on thumbv7em but not thumbv6m
502            #[allow(clippy::unnecessary_cast)]
503            let word = unsafe { self.spi.as_mut().read_data() as u32 };
504            let bytes = word.to_le_bytes();
505            let mut iter = bytes.iter();
506            for _ in 0..self.spi.step() {
507                match (data.next(), iter.next()) {
508                    (Some(d), Some(b)) => *d = *b,
509                    _ => break,
510                }
511            }
512            self.rcvd += self.spi.step();
513        }
514        if self.rcvd >= buf.len() {
515            self.spi.as_mut().disable_interrupts(Flags::RXC);
516            self.ss.deassert();
517            if let Some(waker) = self.waker.take() {
518                waker();
519            }
520        }
521        Ok(())
522    }
523
524    /// Poll the [`SpiFuture`]
525    ///
526    /// This function will return [`Poll::Pending`] until all bytes have been
527    /// received. When the transaction is complete, it will return
528    /// [`Poll::Ready`] with a reference to the completed buffer.
529    #[inline]
530    pub fn poll(&self) -> Poll<&[u8]> {
531        let buf = self.buf.as_ref();
532        if self.rcvd >= buf.len() {
533            Poll::Ready(buf)
534        } else {
535            Poll::Pending
536        }
537    }
538
539    /// Consume the [`SpiFuture`] and free its components
540    ///
541    /// If the transaction is complete, this function will consume the
542    /// [`SpiFuture`] and return the [`Spi`] object, the
543    /// buffer, and the SS pin, if present.
544    ///
545    /// If the transaction is not complete, it will return `Err(self)`.
546    #[inline]
547    pub fn free(self) -> Result<(S, B, SS), Self> {
548        if self.rcvd >= self.buf.as_ref().len() {
549            Ok((self.spi, self.buf, self.ss))
550        } else {
551            Err(self)
552        }
553    }
554
555    /// Consume the [`SpiFuture`] and free its components without checking for
556    /// completion
557    ///
558    /// # Safety
559    ///
560    /// Ending the transaction prematurely could leave the [`Spi`] in an
561    /// inconsistent state. It is not safe to call this function unless the
562    /// transaction is complete.
563    ///
564    /// [`Spi`]: super::spi::Spi
565    #[inline]
566    pub unsafe fn free_unchecked(self) -> (S, B, SS) {
567        (self.spi, self.buf, self.ss)
568    }
569}