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 static
ally
allocated SpiFuture
, so that it can be accessed by both the interrupt
handlers and user code. The following example shows how SpiFuture
s 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