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:
- Send ‘Hello
World’ to a Terminal
Demonstrates how to send a string to the PC and show it in the terminal.
- Send
Formatted Strings/Send String Templates Using
‘
printf
’Enhances the first use case with the ability to use the ‘
printf
’ function to send strings over USART. - Receive
Control Commands
Many times, the USART is used to implement a command-line interface. This way, the microcontroller can receive control commands via the USART.
Additionally, this document provides information on how to configure the USART in Synchronous mode and One Wire mode.
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.
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.
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.
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.
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.
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;
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); } }
<avr/delay.h>
header.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); }
printf
’ function
uses specifiers to mark where to insert variables in the string template. Some of the
available specifiers are in the table below: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
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.
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.
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
- ATmega4809 web page: https://www.microchip.com/wwwproducts/en/ATMEGA4809
- megaAVR® 0-series Manual (DS40002015)
- ATmega3209/4809 – 48-pin Data Sheet megaAVR® 0-series (DS40002016)
- ATmega4809 Xplained Pro web page: https://www.microchip.com/developmenttools/ProductDetails/atmega4809-xpro
- AVR128DA48 Product Page: www.microchip.com/wwwproducts/en/AVR128DA28
- AVR128DA48 Curiosity Nano Evaluation Kit web page: www.microchip.com/Developmenttools/ProductDetails/DM164151
- AVR128DA28/32/48/64 (DS4000218)
- Getting Started with the AVR® DA Family (DS00003429)