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 Pins as peripheral Pads, but only
certain Pin combinations are acceptable. All Pins must be mapped to
the same Sercom (see the datasheet). SAMx5x chips also require that
Pins 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 PinIds instead of Pins. 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 PadNums 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
RXorTX - Satisfy the
RxpoTxpotrait (SAMD11/SAMD21), or theRxpoandTxpotraits (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 takeselfand returnSelf. - A set of methods is provided to use as setters: for example
set_baud,set_stop_bits, etc. These methods take&mut selfand 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:
RxorRxDuplex: Can perform receive transactionsTxorTxDuplex: Can perform transmit transactionsDuplex: UART configured as duplex that can perform receive and transmit transactions. Additionally, thesplitmethod 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
dmaCargo 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
SERCOMinterrupt source to the UARTInterruptHandler(refer to the module-levelasync_haldocumentation for more information). - Turn a previously configured
Uartinto aUartFutureby callingUart::into_future - Optionally, add DMA channels to RX, TX or both using
UartFuture::with_rx_dma_channelandUartFuture::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
Uartstruct must be turned into anUartFutureby callingUart::into_futurebefore callingwith_rx_dma_channelorwith_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 theUartstruct 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 Futures
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.
awaiting is fine: theFuturewill 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
Pads - 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’sCapabilityis. - Uart
Future asyncversion of aUart.
Enums§
- Baud
Mode - Baudrate calculation in asynchronous mode
- BitOrder
- Bit order of a UART frame
- Char
Size Enum enumversion ofCharSize- Duplex
- Marker type representing a UART that has both transmit and receive capability
- DynChar
Size - Dynamic
CharSizethat can be changed on the fly - Eight
Bit CharSizevariant for 8-bit transactions- Error
- Errors available for UART transactions
- FiveBit
CharSizevariant for 5-bit transactions- NineBit
CharSizevariant 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
DuplexUART - Seven
Bit CharSizevariant for 7-bit transactions- SixBit
CharSizevariant 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
DuplexUART
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
Configtypes - Capability
- Type-level
enumrepresenting the capabilities of a UART peripheral - Char
Size - Type-level
enumrepresenting the UART character size - Fixed
Char Size - Type-level
enumindicating aCharSizethat is not dynamic - PadSet
- Type-level function to recover the
OptionalPadtypes from a generic set ofPads - Receive
- Type-level enum representing a UART that can receive
- Rxpo
Txpo - Configure the
RXPOandTXPOfields 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
Configurations - Valid
Pads - Marker trait for valid sets of
Pads
Type Aliases§
- Clock
- Clock type needed to create a new
Config.Mclkfor thumbv7em targets. - Config
Sercom - Type alias to recover the specific
Sercomtype from an implementation ofAnyConfig - DataReg
- Size of the SERCOM’s
DATAregister - Pads
From Ids - Define a set of
PadsusingPinIds instead ofPins - Specific
Config - Type alias to recover the specific
Configtype from an implementation ofAnyConfig - Uart
Future Duplex - Convenience type for a
UartFuturewith RX and TX capabilities - Uart
Future Duplex Dma - Convenience type for a
UartFuturewith 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
UartFuturein DMA mode. - Uart
Future RxDuplex - Convenience type for the RX half of a
DuplexUartFuture. - Uart
Future RxDuplex Dma - Convenience type for the RX half of a
DuplexUartFuturein DMA mode. - Uart
Future Tx - Convenience type for a TX-only
UartFuture. - Uart
Future TxDma - Convenience type for a TX-only
UartFuturein DMA mode. - Uart
Future TxDuplex - Convenience type for the TX half of a
DuplexUartFuture. - Uart
Future TxDuplex Dma - Convenience type for the TX half of a
DuplexUartFuturein DMA mode. - Word
- Type alias to recover the
Wordtype from an implementation ofCharSize