Module atsamd_hal::sercom::spi_future
source · [−]Expand description
A Future-like interface for SPI transactions
An SpiFuture takes ownership of an Spi struct and a [u8]-like
buffer. It then executes a full-duplex SPI transaction using iterrupts. On
each RXC or DRE interrupt, the SpiFuture reads or sends STEP bytes
of the buffer, where STEP is a value that depends on CharSize, for
SAMD11 & SAMD21 chips, or Length, for SAMD51 & SAME5x chips.
The provided buffer must implement AsRef and AsMut for [u8], it
must have an appropriate length (see below), and it must have a 'static
lifetime, i.e. it must be owned or a &'static mut reference.
SpiFuture has extra, optional capabilities as well. It can accept a
function or closure that will be called on completion of the transaction,
acting like a Waker. And it can take a GPIO Pin to use as the SS
line. If provided, the Pin will be set low at the beginnging of the
transfer and brought high at completion.
Calling start will enable the DRE and RXC interrupts and begin the
transaction.
use atsamd_hal::gpio::{Pin, PA10, PushPullOutput};
use atsamd_hal::sercom::spi::AnySpi;
use atsamd_hal::sercom::spi_future::SpiFuture;
fn wake_up() {
//...
}
fn use_future(spi: impl AnySpi, ss: Pin<PA10, PushPullOutput>) {
let buf = [0_u8; 12];
let future = SpiFuture::new(spi, buf)
.with_waker(wake_up)
.with_ss(ss);
future.start();
}When sending and receiving finish, the SpiFuture will automatically
disable the DRE and RXC interrupts. To test whether an SpiFuture is
complete, use the poll method. While the transaction is in progress, it
will return Poll::Pending. When the transaction is complete, it will
return Poll::Ready. Once complete, you can consume the SpiFuture and
free the constituent pieces. Doing so before the transfer has completed
is unsafe.
The actual transfer is performed by the send and recv methods, which
should be called from the DRE and RXC interrupt handlers, respectively.
STEP size and buffer length
For SAMD11 & SAMD21 chips, STEP is equal to the number of bytes in the
corresponding the CharSize::Word type, i.e. 1 for EightBit and 2 for
NineBit. For SAMD51 & SAME5x chips, STEP is equal to the Length or
4, whichever is less.
The provided buffer must have an appropriate length. For SAMD11 & SAMD21
chips, as well as SAMDx5x chips with Length <= 4, a single write of
STEP bytes represents an entire SPI transaction. In these cases, the
provided buffer must represent an integer number of transactions. For
example, a SAMD51 Spi struct with a Length of 3 could use buffers of
length 3, 6, 9, etc. For longer Length values, the provided buffer must
represent exactly one SPI transaction, so the buffer length must be equal to
Length. For example, a SAMD51 Spi struct with a Length of 17
could only use a buffer with exactly 17 bytes.
Keep in mind that SpiFuture works only with [u8]-like things, which
can introduce some limitations.
Suppose you plan to execute NineBit transactions with a SAMD21 chip.
Your data may come in the form of a [u16] slice. However, to use it with
SpiFuture, you will need reformulate it as a [u8] slice. The easiest
way to do so is probably to transmute the slice to [u8] or cast the
reference to &'static mut [u8]. Both of these operations are sound,
because u8 has no alignment requirements.
In another scenario, suppose you wanted to use a SAMx5x chip with a
transaction Length of 3 bytes. Your data might come in the form of a
[u32] slice. In this situation, it would not be appropriate to
transmute or cast the data to a [u8] slice. SpiFuture expects the data
to be a byte-packed [u8] slice, so the extra byte in each u32 makes it
incompatible.
RTIC example
The RTIC framework provides a convenient way to store a statically
allocated SpiFuture, so that it can be accessed by both the interrupt
handlers and user code. The following example shows how SpiFutures might
be used for a series of transactions. It was written for a SAMx5x chip, and
it uses features from the latest release of RTIC, v0.6-alpha.0.
use core::task::Poll;
use atsamd_hal::gpio::{PA08, PA09, PA10, PA11, Pin, PushPullOutput};
use atsamd_hal::sercom::Sercom0;
use atsamd_hal::sercom::pad::{IoSet1, Pad0, Pad1, Pad3};
use atsamd_hal::sercom::spi::{self, Master, lengths::U12};
use atsamd_hal::sercom::spi_future::SpiFuture;
type Pads = spi::Pads<Sercom0, IoSet1, (Pad0, PA08), (Pad3, PA11), (Pad1, PA09)>;
type SS = Pin<PA10, PushPullOutput>;
type Spi = spi::Spi<spi::Config<Pads, Master, U12>>;
type Future = SpiFuture<Spi, [u8; 12], SS, fn()>;
//...
#[resources]
struct Resources {
#[task_local]
#[init(None)]
opt_spi_ss: Option<(Spi, SS)>,
#[lock_free]
#[init(None)]
opt_future: Option<Future>,
}
#[task(resources = [opt_spi_ss, opt_future])]
fn task(ctx: task::Context) {
let task::Context { opt_spi_ss, opt_future } = ctx;
match opt_future {
Some(future) => {
if let Poll::Ready(_) = future.poll() {
let (spi, buf, ss) = opt_future.take().unwrap().free();
*opt_spi_ss = Some((spi, ss));
consume_data(buf);
}
}
None => {
if let Some((spi, ss)) = opt_spi_ss.take() {
let buf: [u8; 12] = produce_data();
let future = opt_future.get_or_insert(
SpiFuture::new(spi, buf)
.with_waker(|| { task::spawn().ok(); })
.with_ss(ss)
);
future.start();
}
}
}
}
#[task(binds = SERCOM0_0, resources = [opt_future])]
fn dre(ctx: dre::Context) {
ctx.resources.opt_future.as_mut().unwrap().send();
}
#[task(binds = SERCOM0_2, resources = [opt_future])]
fn rxc(ctx: rxc::Context) {
ctx.resources.opt_future.as_mut().unwrap().recv();
}
//...Structs
Traits
SpiFuture buffer length