Module atsamd_hal::sercom::spi
source · [−]Expand description
Use a SERCOM peripheral for SPI transactions
Using an SPI peripheral occurs in three steps. First, you must supply
gpio
Pin
s to create a set of Pads
. Next, you combine the
Pads
with other pieces to form a Config
struct. Finally, after
configuring the peripheral, you enable
it to yield a functional
Spi
struct. Transactions are performed using traits from the
embedded_hal
crate, specifically those from the
spi
, serial
, and
blocking
modules.
Crating a set of Pads
An SPI peripheral can use up to four Pin
s as Sercom
pads. However,
only certain Pin
combinations are acceptable. All Pin
s must be mapped to
the same Sercom
, and for SAMx5x chips, they must also belong to the same
IoSet
.
This HAL makes it impossible to use invalid Pin
combinations, and the
Pads
struct is responsible for enforcing these constraints.
A Pads
type takes five or six type parameters, depending on the chip. The
first type always specifies the Sercom
. On SAMx5x chips, the second type
specifies the IoSet
. The remaining four type parameters, DI
, DO
, CK
and SS
, represent the Data In, Data Out, Sclk and SS pads respectively.
Each of these type parameters is an OptionalPad
and defaults to
NoneT
. A Pad
is just a Pin
configured in the correct PinMode
that implements IsPad
. The bsp_pins!
macro can be
used to define convenient type aliases for Pad
types.
use atsamd_hal::gpio::{PA08, PA09, AlternateC};
use atsamd_hal::sercom::{Sercom0, spi};
use atsamd_hal::typelevel::NoneT;
// SAMx5x-specific imports
use atsamd_hal::sercom::pad::IoSet1;
type Miso = Pin<PA08, AlternateC>;
type Sclk = Pin<PA09, AlternateC>;
// SAMD11/SAMD21 version
type Pads = spi::Pads<Sercom0, Miso, NoneT, Sclk>;
// SAMx5x version
type Pads = spi::Pads<Sercom0, IoSet1, Miso, NoneT, Sclk>;
Alternatively, you can use the PadsFromIds
alias to define a set of
Pads
in terms of PinId
s instead of Pin
s. This is useful when you
don’t have Pin
aliases pre-defined.
use atsamd_hal::gpio::{PA08, PA09};
use atsamd_hal::sercom::{Sercom0, spi};
use atsamd_hal::typelevel::NoneT;
// SAMx5x-specific imports
use atsamd_hal::sercom::pad::IoSet1;
// SAMD21 version
type Pads = spi::PadsFromIds<Sercom0, PA08, NoneT, PA09>;
// SAMx5x version
type Pads = spi::PadsFromIds<Sercom0, IoSet1, PA08, NoneT, PA09>;
Instances of Pads
are created using the builder pattern. Start by creating
an empty set of Pads
using Default
. Then pass each respective Pin
using the corresponding methods. For SAMD21 and SAMx5x chips, the builder
methods automatically convert each pin to the correct PinMode
. However,
due to inherent ambiguities, users must manually configure PinMode
s for
SAMD11 chips.
use atsamd_hal::target_device::Peripherals;
use atsamd_hal::gpio::Pins;
use atsamd_hal::sercom::{Sercom0, spi};
// SAMx5x-specific imports
use atsamd_hal::sercom::pad::IoSet1;
let mut peripherals = Peripherals::take().unwrap();
let pins = Pins::new(peripherals.PORT);
// SAMD21 version
let pads = spi::Pads::<Sercom0>::default()
.sclk(pins.pa09)
.data_in(pins.pa08)
.data_out(pins.pa11);
// SAMx5x version
let pads = spi::Pads::<Sercom0, IoSet1>::default()
.sclk(pins.pa09)
.data_in(pins.pa08)
.data_out(pins.pa11);
To be accepted by the Config
struct as a set of ValidPads
, the
Pads
must do two things:
- Specify
SomePad
forCK
and at least one ofDI
orDO
- Use a valid combination of
PadNum
s, so that thePads
implementDipoDopo
Config
uring the peripheral
Next, create a Config
struct, which represents the SPI peripheral in its
disabled state. A Config
is specified with three type parameters: the
Pads
type; an OpMode
, which defaults to Master
; and a
Size
type that varies by chip. Size
essentially acts as a trait
alias. On SAMD11 and SAMD21 chips, it represents the
CharSize
, which can either be EightBit
or NineBit
.
While on SAMx5x chips, it represents the transaction
Length
in bytes, using type-level numbers provided by the typenum
crate. Valid
transaction lengths, from U1
to U255
, are re-exported in the
lengths
sub-module.
use atsamd_hal::gpio::{PA08, PA09};
use atsamd_hal::sercom::{Sercom0, spi};
use atsamd_hal::sercom::spi::Master;
use atsamd_hal::typelevel::NoneT;
// SAMD11/SAMD21-specific imports
use atsamd_hal::sercom::spi::NineBit;
// SAMx5x-specific imports
use atsamd_hal::sercom::spi::lengths::U2;
use atsamd_hal::sercom::pad::IoSet1;
// SAMD11/SAMD21 version
type Pads = spi::PadsFromIds<Sercom0, PA08, NoneT, PA09>;
type Config = spi::Config<Pads, Master, NineBit>;
// SAMx5x version
type Pads = spi::PadsFromIds<Sercom0, IoSet1, PA08, NoneT, PA09>;
type Config = spi::Config<Pads, Master, U2>;
For simplicity, this module ignores character size on SAMx5x chips. Instead,
the SPI peripheral is always configured to use 32-bit extension mode and the
hardware LENGTH
counter. Note that, due to a hardware bug, ICSPACE
must
be at least one when using the length counter. See the silicon errata for
more details.
Upon creation, the Config
takes ownership of both the Pads
and the
PAC Sercom
struct. It takes a reference to the PM
or MCLK
, so that
it can enable the APB clock, and it takes a frequency to indicate the GCLK
configuration. Users are responsible for correctly configuring the GCLK.
use atsamd_hal::time::U32Ext;
// Not shown: configure GCLK for 10 MHz
// SAMD11/SAMD21 version
let pm = peripherals.PM;
let sercom = peripherals.SERCOM0;
let freq = 10.mhz();
let config = spi::Config::new(&pm, sercom, pads, freq);
// SAMx5x version
let mclk = peripherals.MCLK;
let sercom = peripherals.SERCOM0;
let freq = 10.mhz();
let config = spi::Config::new(&mclk, sercom, pads, freq);
The Config
uses two different APIs for configuration. For most
parameters, it provides get_
and set_
methods that take &self
and
&mut self
respectively, e.g. get_bit_order
and
set_bit_order
. However, because Config
tracks
the OpMode
and Size
at compile-time, which requires changing the
corresponding type parameters, Config
also provides a builder-pattern API,
where methods take and return self
, e.g. bit_order
.
Once configured, the enable
method consumes the Config
and returns an
enabled Spi
struct that can be used for transactions. Because the
enable
function takes the Config
as self
, the builder-pattern API is
usually the more ergonomic option.
use embedded_hal::spi::MODE_1;
// SAMD11/SAMD21 version
let spi = spi::Config::new(&pm, sercom, pads, freq)
.baud(1.mhz())
.char_size::<NineBit>()
.bit_order(BitOrder::LsbFirst)
.spi_mode(MODE_1)
.enable();
// SAMx5x version
let spi = spi::Config::new(&mclk, sercom, pads, freq)
.baud(1.mhz())
.length::<U2>()
.bit_order(BitOrder::LsbFirst)
.spi_mode(MODE_1)
.enable();
To be accepted as a ValidConfig
, the Config
must have a set of
ValidPads
that matches its OpMode
. In particular, the SS
pad must
be NoneT
for Master
mode, where the user is expected to handle it
manaully. But it must be SomePad
in MasterHWSS
and Slave
modes,
where it is controlled by the hardware.
Using a functional Spi
peripheral
An Spi
struct has two type parameters. The first is the corresponding
Config
, while the second represents its Capability
, i.e. Rx
,
Tx
or Duplex
. The enable
function determines the Capability
automaically from the set of ValidPads
.
use atsamd_hal::gpio::{PA08, PA09};
use atsamd_hal::sercom::{Sercom0, spi};
use atsamd_hal::sercom::spi::{Master, Rx};
use atsamd_hal::typelevel::NoneT;
// SAMD11/SAMD21-specific imports
use atsamd_hal::sercom::spi::NineBit;
// SAMx5x-specific imports
use atsamd_hal::sercom::spi::lengths::U2;
use atsamd_hal::sercom::pad::IoSet1;
// SAMD11/SAMD21 version
type Pads = spi::PadsFromIds<Sercom0, PA08, NoneT, PA09>;
type Config = spi::Config<Pads, Master, NineBit>;
type Spi = spi::Spi<Config, Rx>;
// SAMx5x version
type Pads = spi::PadsFromIds<Sercom0, IoSet1, PA08, NoneT, PA09>;
type Config = spi::Config<Pads, Master, U2>;
type Spi = spi::Spi<Config, Rx>;
Only Spi
structs can actually perform transactions. To do so, use the
various embedded HAL traits, like
spi::FullDuplex
,
serial::Read
or
serial::Write
.
See the impl_ehal
module documentation for more details about the
specific trait implementations, which vary based on Size
and
Capability
.
use nb::block;
use embedded_hal::spi::FullDuplex;
block!(spi.send(0xAA55));
let rcvd: u16 = block!(spi.read());
Modules
embedded_hal
traits for Spi
structsStructs
Capability
enum for duplex transactionsCapability
enum for simplex, Receive
-only
transactionsCapability
enum for simplex, Transmit
-only
transactionsEnums
enum
for SPI transactionsOpMode
variant for Master mode with hardware-controlled slave selectConstants
Traits
Size
s that can be completed in a single
read or write of the DATA
registerOptionalPadNum
to its corresponding DIPO
valueOptionalPadNum
to its corresponding DOPO
valueLength
s greater than fourOptionalPad
types from a generic set
of Pads
Length
sConfig
urationsType Definitions
DATA
registerSize
type, which varies by chip