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 serial traits from embedded HAL.

Pads

A Sercom can use up to four Pins as peripheral Pads, but only certain Pin combinations are acceptable. In particular, all Pins must be mapped to the same Sercom (see the datasheet). 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 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, DI, DO, CK and SS, represent the Data In, Data Out, Sclk and SS pads respectively. Each of the remaining 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 Sercom struct. 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 serial::Read and serial::Write.

use nb::block;
use embedded_hal::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;
use atsamd_hal::time::*;

// 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.

Modules

embedded-hal trait implementations for Uarts

Structs

A configurable, disabled UART peripheral
Interrupt bit flags for UART Rx transactions
Container for a set of SERCOM Pads
Status flags for UART Rx transactions
Abstraction over a UART peripheral, allowing to perform UART transactions. The second type parameter, D, denotes what the struct’s Capability is.

Enums

Baudrate calculation in asynchronous mode
Bit order of a UART frame
enum version of CharSize
Marker type representing a UART that has both transmit and receive capability
Dynamic CharSize that can be changed on the fly
CharSize variant for 8-bit transactions
Errors available for UART transactions
CharSize variant for 5-bit transactions
CharSize variant for 9-bit transactions
Baudrate oversampling values
Parity setting of a UART frame
Marker type representing a UART that can only receive
Marker type representing the Rx half of a Duplex UART
CharSize variant for 7-bit transactions
CharSize variant for 6-bit transactions
Number of stop bits in a UART frame
Marker type representing a UART that can only transmit
Marker type representing a the Tx half of a Duplex UART

Constants

Interrupt flags available for Duplex transactions
Status flags available for Duplex transactions
Interrupt flags available for RX transactions
Status flags available for RX transactions
Interrupt flags available for TX transactions

Traits

Type class for all possible Config types
Type-level enum representing the capabilities of a UART peripheral
Type-level enum representing the UART character size
Type-level enum indicating a CharSize that is not dynamic
Type-level function to recover the OptionalPad types from a generic set of Pads
Type-level enum representing a UART that can receive
Configure the RXPO and TXPO fields based on a set of Pads
Type-level enum representing a UART that has transmit or receive capability, but not both
Type-level enum representing a UART that can transmit
Marker trait for valid UART Configurations
Marker trait for valid sets of Pads

Type Definitions

Clock type needed to create a new Config. MCLK for thumbv7em targets.
Type alias to recover the specific Sercom type from an implementation of AnyConfig
Size of the SERCOM’s DATA register
Define a set of Pads using PinIds instead of Pins
Type alias to recover the specific Config type from an implementation of AnyConfig
Type alias to recover the Word type from an implementation of CharSize