Expand description
Use the SERCOM peripheral for UART communications
Configuring an UART peripheral occurs in three steps. First, you must create
a set of Pads
for use by the peripheral. Next, you assemble pieces into
a Config
struct. After configuring the peripheral, you then enable
it, yielding a functional Uart
struct. Transactions are performed using
the embedded_io::Write
, embedded_io::Read
,
embedded_hal_nb::serial::Write
, and embedded_hal_nb::serial::Read
traits.
§Pads
A Sercom
can use up to four Pin
s as peripheral Pad
s, but only
certain Pin
combinations are acceptable. All Pin
s must be mapped to
the same Sercom
(see the datasheet). SAMx5x chips also require that
Pin
s must be in the same IoSet
. This HAL makes it impossible to use
invalid Pin
/Pad
combinations, and the Pads
struct is responsible
for enforcing these constraints.
A Pads
type takes five type parameters, the first type specifies the
Sercom
, DI
, DO
, CK
and SS
, represent the Data In, Data Out, Sclk
and SS pads respectively. Each of the pad 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, uart};
use atsamd_hal::typelevel::NoneT;
type Rx = Pin<PA08, AlternateC>;
type Tx = Pin<PA09, AlternateC>;
type Pads = uart::Pads<Sercom0, Rx, Tx>;
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, uart};
type Pads = uart::PadsFromIds<Sercom0, PA08, 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.
On SAMD21 and SAMx5x chips, the builder methods automatically convert each
pin to the correct PinMode
. But for SAMD11 chips, users must manually
convert each pin before calling the builder methods. This is a consequence
of inherent ambiguities in the SAMD11 SERCOM pad definitions. Specifically,
the same PinId
can correspond to two different PadNum
s for the
same Sercom
.
use atsamd_hal::pac::Peripherals;
use atsamd_hal::gpio::Pins;
use atsamd_hal::sercom::{Sercom0, uart};
let mut peripherals = Peripherals::take().unwrap();
let pins = Pins::new(peripherals.PORT);
let pads = uart::Pads::<Sercom0>::default()
.rx(pins.pa09)
.tx(pins.pa08);
To be accepted as ValidPads
, a set of Pads
must do two things:
- Specify a type for at least one of
RX
orTX
- Satisfy the
RxpoTxpo
trait (SAMD11/SAMD21), or theRxpo
andTxpo
traits (SAMx5x)
§Config
Next, create a Config
struct, which represents the UART peripheral in
its disabled state. A Config
is specified with two type parameters: the
Pads
type; and a CharSize
, which defaults to EightBit
.
use atsamd_hal::gpio::{PA08, PA09};
use atsamd_hal::sercom::{Sercom0, uart};
use atsamd_hal::sercom::uart::{NineBit};
use atsamd_hal::typelevel::NoneT;
type Pads = uart::PadsFromIds<Sercom0, PA08, PA09>;
type Config = uart::Config<Pads, NineBit>;
Upon creation, the Config
takes ownership of both the Pads
struct
and the PAC SercomN
struct (eg Sercom0
). It takes a reference to the
PM, 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;
let pm = peripherals.PM;
let sercom = peripherals.Sercom0;
// Configure GCLK for 10 MHz
let freq = 10.mhz();
let config = uart::Config::new(&pm, sercom, pads, freq);
The Config
struct can configure the peripheral in one of two ways:
- A set of methods is provided to use in a builder pattern: for example
baud
,stop_bits
, etc. These methods takeself
and returnSelf
. - A set of methods is provided to use as setters: for example
set_baud
,set_stop_bits
, etc. These methods take&mut self
and return nothing.
In any case, the peripheral setup ends with a call to enable
, which
consumes the Config
and returns an enabled Uart
peripheral.
use atsamd_hal::sercom::uart::{StopBits, NineBit, BitOrder, BaudMode, Oversampling};
let uart = uart::Config::new(&mclk, sercom, pads, freq)
.baud(1.mhz(), BaudMode::Arithmetic(Oversampling::Bits16))
.char_size::<NineBit>()
.bit_order(BitOrder::LsbFirst)
.stop_bits(StopBits::TwoBits)
.enable();
Alternatively,
use atsamd_hal::sercom::uart::{StopBits, NineBit, BitOrder, BaudMode, Oversampling};
let uart = uart::Config::new(&mclk, sercom, pads, freq);
uart.set_baud(1.mhz(), BaudMode::Arithmetic(Oversampling::Bits16));
uart.set_char_size::<NineBit>();
uart.set_bit_order(BitOrder::LsbFirst);
uart.set_stop_bits(StopBits::TwoBits);
let uart = uart.enable();
To be accepted as a ValidConfig
, the Config
must have at least one
of Rx
or Tx
pads.
§CharSize
The UART peripheral can be configured to use different character sizes. By
default, a Config
is configured with an EightBit
character size.
This can be changed through the char_size
method.
Changing the character normally also changes the Config
’s type.
Alternatively, you can also use a DynCharSize
through the
dyn_char_size
method. This enables you to
dynamically change the character size on the fly through the
set_dyn_char_size
method when calling
reconfigure
.
§Reading the current configuration
It is possible to read the current configuration by using the getter methods
provided: for example get_baud
,
get_stop_bits
, etc.
§Uart
and capabilities
Uart
structs can only be created from a Config
. They have two type
parameters: the first one represents the underlying Config
, while the
second represents the Uart
’s capabilities. The second type parameter can
be one of:
Rx
orRxDuplex
: Can perform receive transactionsTx
orTxDuplex
: Can perform transmit transactionsDuplex
: UART configured as duplex that can perform receive and transmit transactions. Additionally, thesplit
method can be called to return aUart<C, RxDuplex>, Uart<C, TxDuplex>)
tuple. See the Splitting section for more information.
The nature of the underlying Pads
contained inside Config
determines
the type returned by a call to enable
. If the pads only have a TX
pin
specified, then enable
will return a Uart<C, Tx>
. Similarly, If the
pads only have a RX
pin specified, then enable
will return a Uart<C, Rx>
. Finally, if both RX
and TX
pins are specified, then enable
will return a Uart<C, Duplex>
, which can be further split into a Uart<C, RxDuplex>
and a Uart<C, TxDuplex>
.
use atsamd_hal::gpio::{PA08, PA09};
use atsamd_hal::sercom::{Sercom0, uart};
use atsamd_hal::sercom::uart::NineBit;
use atsamd_hal::typelevel::NoneT;
// Assuming SAMD21 or SAMx5x
type Pads = uart::PadsFromIds<Sercom0, PA08, NoneT, PA09>;
type Config = uart::Config<Pads, NineBit>;
type UartRx = uart::Uart<Config, RxDuplex>;
type UartTx = uart::UartTx<Config, RxDuples>;
Only the Uart
struct can actually perform transactions. To do so, use
the embedded HAL traits, like embedded_hal_nb::serial::Read
,
embedded_hal_nb::serial::Write
, embedded_io::Read
, and
embedded_io::Write
.
use nb::block;
use atsamd_hal::embedded_hal_nb::serial::Write;
block!(uart_tx.write(0x0fe));
§UART flow control (CTS/RTS)
This module supports CTS and RTS pins.
The RTS
pin is a fully hardware-controlled output pin that gets deasserted
when:
- The USART receiver is disabled;
- The USART’s RX buffer is full.
The CTS
pin is an input pin that provides an interrupt when a change
(rising or falling edge) is detected on the corresponding Pad. This
interrupt, CTSIC
, can be enabled with the
enable_ctsic
method only when the corresponding
Config
has a CTS
pad specified. The
disable_ctsic
and
clear_ctsic
methods are also available under the same
conditions. This application
note
provides more information about UART hardware flow control.
§Splitting
A Uart<C, Duplex>
can be split into its RxDuplex
and TxDuplex
constituents:
use atsamd_hal::sercom::uart::Uart;
// Assume uart is a Uart<C, Duplex>
let (rx, tx) = uart.split();
§Joining
When a Uart<C, Duplex>
has been split into its RxDuplex
and
TxDuplex
parts, these parts can be joined back into a Uart<C, Duplex>
by calling the join
function for Uart<C, Duplex>
. It takes a Uart<C, RxDuplex>
and a Uart<C, TxDuplex>
and moves them into a full Duplex
Uart
.
use atsamd_hal::sercom::uart::Uart;
// Assume rx is a Uart<C, RxDuplex> and tx is a Uart<C, TxDuplex>
let uart = Uart::join(rx, tx);
// uart is now a Uart<C, Duplex>
The AsMut<Uart<C, Duplex>>
trait is also implemented for (&mut Uart<C, RxDuplex>, &mut Uart<C, TxDuplex>)
. This is useful if you need an &mut Uart<C, Duplex>
but you only have a pair of &mut Uart<C, RxDuplex>
and
&mut Uart<C, TxDuplex>
. This can be leveraged to use the reconfigure
method when all you have is a pair of mutable references to the RxDuplex
and TxDuplex
halves.
use atsamd_hal::sercom::uart::Uart;
// Assume rx is a Uart<C, RxDuplex> and tx is a Uart<C, TxDuplex>
// Reconfigure peripheral from mutable references to RxDuplex
// and TxDuplex halves
(&mut rx, &mut tx).as_mut().reconfigure(|c| c.set_run_in_standby(false));
§Reading the current configuration
The AsRef<Config<P, C>>
trait is implemented for Uart<Config<P, C>, D>
.
This means you can use the get_
methods implemented for Config
, since
they take an &self
argument.
// Assume uart is a Uart<C, D>
let (baud, baud_mode) = uart.as_ref().get_baud();
§Disabling and reconfiguring
Some methods, such as disable
and reconfigure
, need to operate on
all parts of a UART at once. In practice, this means that these methods
operate on the type that was returned by enable
. This can be Uart<C, Rx>
, Uart<C, Tx>
, or Uart<C, Duplex>
, depending on how the peripheral
was configured.
The reconfigure
method gives out an &mut Config
reference, which can
then use the set_*
methods.
use atsamd_hal::sercom::uart::Uart;
use atsamd_hal::time::*;
// Assume config is a valid Duplex UART Config struct
let (rx, tx)= config.enable().split();
// Send/receive data with tx/rx halves...
// If the UART peripheral is configured in Duplex mode,
// the two constituting halves need to be joined back into
// a Uart<C, Duplex> before calling disable()
let uart = Uart::join(rx, tx);
// Reconfigure UART peripheral
uart.reconfigure(|c| c.set_run_in_standby(false));
// Disable UART peripheral
let config = uart.disable();
§Non-supported advanced features
- Synchronous mode (USART) is not supported
- LIN mode is not supported (SAMx5x)
- 32-bit extension mode is not supported (SAMx5x). If you need to transfer
slices, consider using the DMA methods instead. The
dma
Cargo feature must be enabled.
§Using UART with DMA dma
This HAL includes support for DMA-enabled UART transfers. Use
Uart::with_rx_channel
and Uart::with_tx_channel
to attach DMA
channels to the Uart
struct. A DMA-enabled Uart
implements the
blocking embedded_io::Write
and/or embedded_io::Read
traits, which
can be used to perform UART read/writes which are fast, continuous and low
jitter, even if they are preemped by a higher priority interrupt.
use atsamd_hal::dmac::channel::{AnyChannel, Ready};
use atsamd_hal::sercom::Uart::{I2c, ValidConfig, Error, TxDuplex};
use atsamd_hal::embedded_io::Write;
fn uart_send_with_dma<A: ValidConfig, C: AnyChannel<Status = Ready>>(uart: Uart<A, TxDuplex>, channel: C, bytes: &[u8]) -> Result<(), Error>{
// Attach a DMA channel
let uart = uart.with_tx_channel(channel);
uart.write(bytes)?;
}
§Non-blocking DMA transfers
Non-blocking DMA transfers are also supported.
The provided send_with_dma
and receive_with_dma
build and begin a
Transfer
, thus starting the UART in a non-blocking way. Note that these
methods require 'static
buffers in order to remain memory-safe.
Optionally, interrupts can be enabled on the provided Channel
. Please
refer to the dmac
module-level documentation for more
information.
// Assume channel0 and channel1 are configured `dmac::Channel`s,
// rx is a Uart<C, RxDuplex>, and tx is a Uart<C, TxDuplex>.
/// Create data to send
let tx_buffer: [u8; 50] = [0xff; 50];
let rx_buffer: [u8; 100] = [0xab; 100];
// Launch transmit transfer
let tx_dma = tx.send_with_dma(&mut tx_buffer, channel0, |_| {});
// Launch receive transfer
let rx_dma = rx.receive_with_dma(&mut rx_buffer, channel1, |_| {});
// Wait for transfers to complete and reclaim resources
let (chan0, tx_buffer, tx) = tx_dma.wait();
let (chan1, rx, rx_buffer) = rx_dma.wait();
§async
operation async
A Uart
can be used for async
operations. Configuring a Uart
in
async mode is relatively simple:
- Bind the corresponding
SERCOM
interrupt source to the UARTInterruptHandler
(refer to the module-levelasync_hal
documentation for more information). - Turn a previously configured
Uart
into aUartFuture
by callingUart::into_future
- Optionally, add DMA channels to RX, TX or both using
UartFuture::with_rx_dma_channel
andUartFuture::with_tx_dma_channel
. The API is exactly the same whether DMA channels are used or not. - Use the provided async methods for reading or writing to the UART peripheral.
UartFuture
implements AsRef<Uart>
and AsMut<Uart>
so that it can be
reconfigured using the regular Uart
methods. It also exposes a
split
method to split it into its RX and TX parts.
§Considerations when using async
Uart
with DMA async
dma
- An
Uart
struct must be turned into anUartFuture
by callingUart::into_future
before callingwith_rx_dma_channel
orwith_tx_dma_channel
. The DMA channel itself must also be configured in async mode by usingDmaController::into_future
. If a DMA channel is added to theUart
struct before it is turned into anUartFuture
, it will not be able to use DMA in async mode.
// This will work
let uart = uart.into_future().with_rx_dma_channel(rx_channel);
// This won't
let uart = uart.with_rx_dma_channel(rx_channel).into_future();
§Safety considerations
In async
mode, an SPI+DMA transfer does not require 'static
source and
destination buffers. This, in theory, makes its use unsafe
. However it is
marked as safe for better ergonomics.
This means that, as an user, you must ensure that the Future
s
returned by the read
and write
methods may never be forgotten through forget
or by wrapping them with a
ManuallyDrop
.
The returned futures implement Drop
and will automatically stop any
ongoing transfers; this guarantees that the memory occupied by the
now-dropped buffers may not be corrupted by running transfers.
This means that using functions like futures::select_biased
to implement
timeouts is safe; transfers will be safely cancelled if the timeout expires.
This also means that should you forget
this Future
after its first
poll
call, the transfer will keep running, ruining the now-reclaimed
memory, as well as the rest of your day.
await
ing is fine: theFuture
will run to completion.- Dropping an incomplete transfer is also fine. Dropping can happen, for example, if the transfer doesn’t complete before a timeout expires.
- Dropping an incomplete transfer without running its destructor is unsound and will trigger undefined behavior.
async fn always_ready() {}
let mut buffer = [0x00; 10];
// This is completely safe
uart.read(&mut buffer).await?;
// This is also safe: we launch a transfer, which is then immediately cancelled
futures::select_biased! {
_ = uart.read(&mut buffer)?,
_ = always_ready(),
}
// This, while contrived, is also safe.
{
use core::future::Future;
let future = uart.read(&mut buffer);
futures::pin_mut!(future);
// Assume ctx is a `core::task::Context` given out by the executor.
// The future is polled, therefore starting the transfer
future.as_mut().poll(ctx);
// Future is dropped here - transfer is cancelled.
}
// DANGER: This is an example of undefined behavior
{
use core::future::Future;
use core::ops::DerefMut;
let future = core::mem::ManuallyDrop::new(uart.read(&mut buffer));
futures::pin_mut!(future);
// To actually make this example compile, we would need to wrap the returned
// future from `i2c.read()` in a newtype that implements Future, because we
// can't actually call as_mut() without being able to name the type we want
// to deref to.
let future_ref: &mut SomeNewTypeFuture = &mut future.as_mut();
future.as_mut().poll(ctx);
// Future is NOT dropped here - transfer is not cancelled, resulting un UB.
}
As you can see, unsoundness is relatively hard to come by - however, caution should still be exercised.
Modules§
Structs§
- Config
- A configurable, disabled UART peripheral
- Flags
- Interrupt bit flags for UART Rx transactions
- Interrupt
Handler - Interrupt handler for async UART operarions
- Pads
- Container for a set of SERCOM
Pad
s - Status
- Status flags for UART Rx transactions
- Uart
- Abstraction over a UART peripheral, allowing to perform UART transactions.
The second type parameter,
D
, denotes what the struct’sCapability
is. - Uart
Future async
version of aUart
.
Enums§
- Baud
Mode - Baudrate calculation in asynchronous mode
- BitOrder
- Bit order of a UART frame
- Char
Size Enum enum
version ofCharSize
- Duplex
- Marker type representing a UART that has both transmit and receive capability
- DynChar
Size - Dynamic
CharSize
that can be changed on the fly - Eight
Bit CharSize
variant for 8-bit transactions- Error
- Errors available for UART transactions
- FiveBit
CharSize
variant for 5-bit transactions- NineBit
CharSize
variant for 9-bit transactions- Oversampling
- Baudrate oversampling values
- Parity
- Parity setting of a UART frame
- Rx
- Marker type representing a UART that can only receive
- RxDuplex
- Marker type representing the Rx half of a
Duplex
UART - Seven
Bit CharSize
variant for 7-bit transactions- SixBit
CharSize
variant for 6-bit transactions- Stop
Bits - Number of stop bits in a UART frame
- Tx
- Marker type representing a UART that can only transmit
- TxDuplex
- Marker type representing a the Tx half of a
Duplex
UART
Constants§
- DUPLEX_
FLAG_ MASK - Interrupt flags available for Duplex transactions
- DUPLEX_
STATUS_ MASK - Status flags available for Duplex transactions
- RX_
FLAG_ MASK - Interrupt flags available for RX transactions
- RX_
STATUS_ MASK - Status flags available for RX transactions
- TX_
FLAG_ MASK - Interrupt flags available for TX transactions
Traits§
- AnyConfig
- Type class for all possible
Config
types - Capability
- Type-level
enum
representing the capabilities of a UART peripheral - Char
Size - Type-level
enum
representing the UART character size - Fixed
Char Size - Type-level
enum
indicating aCharSize
that is not dynamic - PadSet
- Type-level function to recover the
OptionalPad
types from a generic set ofPads
- Receive
- Type-level enum representing a UART that can receive
- Rxpo
Txpo - Configure the
RXPO
andTXPO
fields based on a set ofPads
- Simplex
- Type-level enum representing a UART that has transmit or receive capability, but not both
- Single
Owner - Type-level enum representing a UART that is not half of a split
Duplex
- Transmit
- Type-level enum representing a UART that can transmit
- Valid
Config - Marker trait for valid UART
Config
urations - Valid
Pads - Marker trait for valid sets of
Pads
Type Aliases§
- Clock
- Clock type needed to create a new
Config
.Pm
for thumbv6m targets. - Config
Sercom - Type alias to recover the specific
Sercom
type from an implementation ofAnyConfig
- DataReg
- Size of the SERCOM’s
DATA
register - Specific
Config - Type alias to recover the specific
Config
type from an implementation ofAnyConfig
- Uart
Future Duplex - Convenience type for a
UartFuture
with RX and TX capabilities - Uart
Future Duplex Dma - Convenience type for a
UartFuture
with RX and TX capabilities in DMA mode. - Uart
Future Rx - Convenience type for a RX-only
UartFuture
. - Uart
Future RxDma - Convenience type for a RX-only
UartFuture
in DMA mode. - Uart
Future RxDuplex - Convenience type for the RX half of a
Duplex
UartFuture
. - Uart
Future RxDuplex Dma - Convenience type for the RX half of a
Duplex
UartFuture
in DMA mode. - Uart
Future Tx - Convenience type for a TX-only
UartFuture
. - Uart
Future TxDma - Convenience type for a TX-only
UartFuture
in DMA mode. - Uart
Future TxDuplex - Convenience type for the TX half of a
Duplex
UartFuture
. - Uart
Future TxDuplex Dma - Convenience type for the TX half of a
Duplex
UartFuture
in DMA mode. - Word
- Type alias to recover the
Word
type from an implementation ofCharSize