Module uart

Source
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 RX or TX
  • Satisfy the RxpoTxpo trait (SAMD11/SAMD21), or the Rxpo and Txpo 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 take self and return Self.
  • 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 or RxDuplex: Can perform receive transactions
  • Tx or TxDuplex: Can perform transmit transactions
  • Duplex: UART configured as duplex that can perform receive and transmit transactions. Additionally, the split method can be called to return a Uart<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:

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 an UartFuture by calling Uart::into_future before calling with_rx_dma_channel or with_tx_dma_channel . The DMA channel itself must also be configured in async mode by using DmaController::into_future. If a DMA channel is added to the Uart struct before it is turned into an UartFuture, 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: the Future 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§

impl_ehal
embedded-hal trait implementations for Uarts

Structs§

Config
A configurable, disabled UART peripheral
Flags
Interrupt bit flags for UART Rx transactions
InterruptHandler
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’s Capability is.
UartFuture
async version of a Uart.

Enums§

BaudMode
Baudrate calculation in asynchronous mode
BitOrder
Bit order of a UART frame
CharSizeEnum
enum version of CharSize
Duplex
Marker type representing a UART that has both transmit and receive capability
DynCharSize
Dynamic CharSize that can be changed on the fly
EightBit
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
SevenBit
CharSize variant for 7-bit transactions
SixBit
CharSize variant for 6-bit transactions
StopBits
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
CharSize
Type-level enum representing the UART character size
FixedCharSize
Type-level enum indicating a CharSize that is not dynamic
PadSet
Type-level function to recover the OptionalPad types from a generic set of Pads
Receive
Type-level enum representing a UART that can receive
RxpoTxpo
Configure the RXPO and TXPO fields based on a set of Pads
Simplex
Type-level enum representing a UART that has transmit or receive capability, but not both
SingleOwner
Type-level enum representing a UART that is not half of a split Duplex
Transmit
Type-level enum representing a UART that can transmit
ValidConfig
Marker trait for valid UART Configurations
ValidPads
Marker trait for valid sets of Pads

Type Aliases§

Clock
Clock type needed to create a new Config. Pm for thumbv6m targets.
ConfigSercom
Type alias to recover the specific Sercom type from an implementation of AnyConfig
DataReg
Size of the SERCOM’s DATA register
SpecificConfig
Type alias to recover the specific Config type from an implementation of AnyConfig
UartFutureDuplex
Convenience type for a UartFuture with RX and TX capabilities
UartFutureDuplexDma
Convenience type for a UartFuture with RX and TX capabilities in DMA mode.
UartFutureRx
Convenience type for a RX-only UartFuture.
UartFutureRxDma
Convenience type for a RX-only UartFuture in DMA mode.
UartFutureRxDuplex
Convenience type for the RX half of a Duplex UartFuture.
UartFutureRxDuplexDma
Convenience type for the RX half of a Duplex UartFuture in DMA mode.
UartFutureTx
Convenience type for a TX-only UartFuture.
UartFutureTxDma
Convenience type for a TX-only UartFuture in DMA mode.
UartFutureTxDuplex
Convenience type for the TX half of a Duplex UartFuture.
UartFutureTxDuplexDma
Convenience type for the TX half of a Duplex UartFuture in DMA mode.
Word
Type alias to recover the Word type from an implementation of CharSize