13 Getting Started with USART

13.1 Introduction

Author: Alexandru Niculae, Microchip Technology Inc.

The purpose of this document is to describe step-by-step how to configure the USART peripheral on megaAVR® 0-series, tinyAVR® 0- and 1-series, and AVR® DA devices. While this is a complex peripheral and can work in various modes, this document will use it in Asynchronous mode for the following use cases:

Additionally, this document provides information on how to configure the USART in Synchronous mode and One Wire mode.

Note: For each of the use cases described in this document, there are two code examples: One bare metal developed on ATmega4809 and one generated with MPLAB® Code Configurator (MCC) developed on AVR128DA48.

13.2 Overview

The USART module has four pins, named RX (receive), TX (transmit), XCK (clock) and XDIR (direction). In One-Wire mode only, the TX pin is used for both transmitting and receiving. The downside of this mode is that it only provides half-duplex communication. In Asynchronous mode, both RX and TX pins are used, thus achieving full-duplex communication. The XCK pin is used for clock signal in Synchronous mode, and the XDIR pin is used for RS485 mode.

Figure 13-155. USART Block Diagram

The most common USART configuration is referred to as “9600 8N1”, meaning 9600 baud rate, eight data bits, no parity, and one Stop bit. Therefore, a typical USART frame will have 10 bits (one Start bit, eight data bits, and one Stop bit) and will be able to represent one ASCII character, which means an “8N1” configuration will transmit BAUD_RATE/10 ASCII characters per second.

Note: All examples described in this document will use a 9600 baud rate and “8N1” frame format. The serial terminal must be set for this configuration.

Moreover, the USART is a complex peripheral and can be used to achieve a handful of other protocols such as:

  • Host SPI
  • Client LIN
  • IR Communication
  • Addressable USART (also called Multi Processor Communication)
  • RS485

13.3 Send “Hello World”

This use case demonstrates how to send a string to the PC and show it in the terminal. There are plenty of options available for UART to USB convertors (such as MCP2200) and PC serial terminal software (such as Data Visualizer in Atmel Studio). The USART will be configured for Asynchronous mode and only the TX pin will be used.

Note: The TX pin of the microcontroller must be connected to the RX pin of a UART to USB convertor. If RX were also used, it has to be connected to the TX pin of a UART to USB convertor. Sometimes the devices have to share a common ground line also.

This use case follows the steps:

  • Set the baud rate
  • Enable the Transmitter (TX)
  • Configure the pins

How to Configure the Baud Rate

The baud rate shows how many bits are sent per second. The higher the baud rate, the faster the communication. Common baud rates are: 1200, 2400, 4800, 9600, 19200, 38400, 57600 and 115200, with 9600 being the most commonly used one.

On the megaAVR 0-series, the maximum baud will be limited to 1/8 * (Maximum USART clock) in Async mode and 1/2 * (Maximum USART clock) in Sync mode. To set the baud rate, write to USARTn.BAUD register:

USART0.BAUD = (uint16_t)USART0_BAUD_RATE(9600);

Notice the use of the USART0_BAUD_RATE macro to compute the register’s value from the baud value. This macro must be defined based on the formula in the image below. This formula depends on the settings of the USART, so it might not be the same in other modes.

Figure 13-156. Equations for Calculating Baud Rate Register Setting

S is the number of samples per bit. In Asynchronous operating mode, it is 16 (NORMAL mode) or 8 (CLK2X mode). For Synchronous operating mode, S equals 2.

Since the USART peripheral frequency is 3.33 MHz, the macro will be:

#define USART0_BAUD_RATE(BAUD_RATE) ((float)(3333333 * 64 / (16 * (float)BAUD_RATE)) + 0.5)

How to Enable the Transmitter and Send Data

Depending on the application needs, the user may choose to only enable the receiver or the transmitter of the USART module. Since in this use case only the microcontroller sends messages, only the transmitter needs to be enabled.

USART0.CTRLB |= USART_TXEN_bm;

Before sending data, the user needs to check if the previous transmission is completed by checking the USARTn.STATUS register. The following code example waits until the transmit DATA register is empty and then writes a character to the USARTn.TXDATA register:

void USART0_sendChar(char c) 
{
    while (!(USART0.STATUS & USART_DREIF_bm))
    {
        ;
    }        
    USART0.TXDATAL = c;
}

The send register is nine bits long. Therefore, it was split into two parts: the lower part that holds the first eight bits, called TXDATAL, and the higher part that holds the remaining one bit, called TXDATAH. TXDATAH is used only when the USART is configured to use nine data bits. When used, this ninth bit must be written before writing to USARTn.TXDATAL, except if CHSIZE in USARTn.CTRLC is set to "9-bit - Low byte first", where USARTn.TXDATAL should be written first.

How to Configure Pins

The TX pin must be configured as output. By default, each peripheral has some associated pin positions. The pins can be found in the device specific data sheet, in the Multiplexed Signals section. Each USART has two sets of pin positions. The default and alternate pin positions for USART0 are shown below.

Figure 13-157. Multiplexed Signals

For this use case, the default USART0 pin position is used; this is PA0 to PA3. The following code sets the TX pin direction to output.

PORTA.DIR |= PIN0_bm;

To use the alternate pin positions, write to the PORTMUX.USARTROUTEA register.

PORTMUX.USARTROUTEA |= PORTMUX_USART00_bm;
Note: In this example, the default pin position is used, not the alternate one.

Demo Code

The following code continually sends the string “Hello World!”. A string is sent character by character. The ‘USART0_sendString’ function calls the ‘USART0_sendCharacter’ function for each character in “Hello Word!” string. Before sending each character, the ‘USART0_sendChar’ function waits for the previous character transmission to be completed. This is done by polling the status register, until the data register empty flag, STATUS.DREIF, is set.

#define F_CPU 3333333
#define USART0_BAUD_RATE(BAUD_RATE) ((float)(3333333 * 64 / (16 * (float)BAUD_RATE)) + 0.5)

#include <avr/io.h>
#include <util/delay.h>
#include <string.h>

void USART0_init(void);
void USART0_sendChar(char c);
void USART0_sendString(char *str);

void USART0_init(void)
{
    PORTA.DIR &= ~PIN1_bm;
    PORTA.DIR |= PIN0_bm;
    
    USART0.BAUD = (uint16_t)USART0_BAUD_RATE(9600);
    USART0.CTRLB |= USART_TXEN_bm; 
}

void USART0_sendChar(char c)
{
    while (!(USART0.STATUS & USART_DREIF_bm))
    {
        ;
    }        
    USART0.TXDATAL = c;
}

void USART0_sendString(char *str)
{
    for(size_t i = 0; i < strlen(str); i++)
    {
        USART0_sendChar(str[i]);
    }
}

int main(void)
{
    USART0_init();
    
    while (1) 
    {
        USART0_sendString("Hello World!\r\n");
        _delay_ms(500);
    }
}
Note: For the delay function to work properly, the CPU frequency must be defined before including the <avr/delay.h> header.
Note: The frame structure is not configured, the default is “8N1” (eight data bits, no parity bit and one Stop bit). The CPU and peripheral clock frequency is also default, 3.33 MHz.

13.4 Send Formatted Strings/Send String Templates Using printf

It is a common use case for an application to send a string with variable fields, for example, when the application reports its state or a counter value. Using formatted strings is a very flexible approach and reduces the number of code lines. This can be accomplished by changing the output stream of the ‘printf’ function.

This use case follows these steps:

  • Configure the USART peripheral the same as for the first use case
  • Create a used defined stream
  • Replace the standard output stream with the user-defined stream

Usually, when using ‘printf’, the characters are sent to a stream of data, called standard output stream. On a PC, the standard output stream is handled by the function to display characters on the screen. But streams can be created so that another function handles their data.

The following code creates a user-defined stream that will be handled by the USART1_printChar function. This function is a wrapper of the USART1_sendChar function but has a slightly different signature to match what FDEV_SETUP_STREAM expects as a parameter.

static void USART1_sendChar(char c)
{
    while (!(USART1.STATUS & USART_DREIF_bm))
    {
        ;
    }
    USART1.TXDATAL = c;
}

static int USART1_printChar(char c, FILE *stream)
{ 
    USART1_sendChar(c);
    return 0; 
}

static FILE USART_stream = FDEV_SETUP_STREAM(USART1_printChar, NULL, _FDEV_SETUP_WRITE);

Then replace the standard output stream with the user-defined stream, handled by the USART send function.

stdout = &USART_stream;

The application can now use ‘printf’ instead of writing to USART registers directly.

uint8_t count = 0;
while (1)
{
    printf("Counter value is: %d\r\n", count++);
    _delay_ms(500);
}
Note: The ‘printf’ function uses specifiers to mark where to insert variables in the string template. Some of the available specifiers are in the table below:
Table 13-4. printf Specifiers
Specifier Description
%d Insert a signed integer
%s Insert a sequence of characters
%c Insert a character
%x Insert integer unsigned in hex format

Other settings do not change and are, therefore, skipped in the code snippets above. See the full code example on GitHub.

An MPLAB MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here:

13.5 Receive Control Commands

One important usage of the USART represents the implementation of a command-line interface. This way, the microcontroller can receive control commands via USART. It is convenient to use the line terminator as a command delimiter, so, for this use case, the USART will read full lines.

This use case follows the steps:

  • Configure the USART peripheral same as for the first use case
  • Enable the receiver
  • Read and store the incoming data until the end of line
  • Check if the received data are a valid command; if so, execute it

How to Enable the Receiver and Receive Data

For USART1, the default pin position for RX is Port C pin 1 (PC1). The following line sets the PC1 direction to input.

PORTC.DIR &= ~PIN1_bm;

Same as the transmitter, the receiver is enabled by witting to the USARTn.CTRLB register.

USART1.CTRLB |= USART_RXEN_bm;

Before reading the data, the user must wait for the data to be available by polling the Receive Complete Interrupt Flag, RXCIF.

uint8_t USART1_read()
{
    while (!(USART1.STATUS & USART_RXCIF_bm))
    {
        ;
    }
	return USART1.RXDATAL;
}

How to Read a Line

The following code snippet reads one line of data and stores it in an array. It assumes that a valid line is shorter than the array length.

The array index is reset to zero when reaching the array end to avoid a buffer overflow error in case of longer lines received. The characters ‘\n’ (line feed) and ‘\r’ (carriage return) are ignored because they are part of the line terminator. When ‘\n’ is found, the string end (NULL) is added to the command, and the function ‘executeCommand’ will call a function based on the value of the command string.

char command[MAX_COMMAND_LEN];
uint8_t index = 0;
char c;
    
/* This delay invalidates the initial noise on the TX line, after device reset. */
    _delay_ms(10);

while (1) 
{
    c = USART1_readChar();
    if(c != ‘\n’ && c != ‘\r’)
    {
       command[index++] = c;
       if(index > MAX_COMMAND_LEN)
       {
           index = 0;
       }
    }
    
    if(c == ‘\n’)
    {
       command[index] = ‘\0’;
       index = 0;
       executeCommand(command);
    }
}

In the following code example on GitHub, the USART receives ‘ON’ and ‘OFF’ commands, and the microcontroller controls a GPIO output, which can, for example, toggle an LED.

An MPLAB MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here:

13.6 Other Implementation Modes

The applications described above demonstrate the basic USART functionalities. This section describes the USART configured in Synchronous mode and One-Wire mode.

13.6.1 Synchronous Mode

Figure 13-158. USART Communication Mode (CMODE) Bit Field in Control C Register

The CMODE bit field in the CTRLC register controls the communication modes.

The disadvantage of the Asynchronous mode is that the receiver chip and the transmitter chip need to use the same baud rate, and exact timing is required. The asynchronous protocols use a separate line for the clock signal, so the chip that generates the clock dictates the communication speed, which is much more flexible in terms of exact timings and creates two roles in the communication: The server that generates the clock and the client that receives the clock.

In the Synchronous USART mode, an additional clock pin, XCK, is used. Same as the RX and TX pins, XCK has a default pin, and changing the PORTMUX register will also change XCK. Configuring the XCK direction decides if the device is a server (generates clock) or a client (receives clock).

To activate the Synchronous mode:

  • Configure the XCK pin (PC2) direction as output;
    PORTC.DIR &= ~PIN2_bm;
  • Write 0x01 to the CMODE bit field in the USARTn.CTRLC register.
    Figure 13-159. USART Communication Mode
    USART1.CTRLC = USART_CMODE_SYNCHRONOUS_gc;

An MPLAB MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here:

13.6.2 One-Wire Mode

Using only one wire effectively reduces the number of pins used for USART communication to one. RX and TX are internally connected, and only TX is used, which means that both incoming and outgoing data will share the same wire, so transmission and reception cannot happen simultaneously. This is called half-duplex communication.

Figure 13-160. Loop-back Mode Enable (LBME) Bit in Control A Register

Use the LBME bit in the CTRLA register to enable an internal loopback connection between RX and TX. An internal connection between RX and TX can be created by writing to USARTn.CTRLA.

USART1.CTRLA |= USART_LBME_bm;

This will internally connect the RX and TX pins, but only the TX pin is used. As the TX pin is used for both transmit and receive, the pin direction needs to be configured as an output before each transmission and switched back to input when the transmission ends.

Since RX is connected internally to TX during transmission, it will receive the data sent, which can be used as a collision detection mechanism. If there is another transmission occurring, the received data will not match the transmitted data. An advanced one-wire driver could take advantage of this strategy.

An MPLAB MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here:

13.7 References