Programming AVR USART with AVR-GCC. Part 1

AVR USART tutorial will be a multi-part tutorial as this peripheral is a sophisticated device and needs special attention.

USART Overview

USART is an acronym for Universal Synchronous and Asynchronous serial Receiver and Transmitter. Instead of using this long expression, let’s stick to USART. So, at least one USART is found in most of AVR microcontrollers (except few of Tiny ones). Atmega328 microcontroller has one USART module that is highly configurable and flexible.

Datasheet provides a list of supported features, including Full Duplex, Asynchronous and Synchronous operation, Master or Slave operation mode, variable frame size, even or odd parity bits, one or two stop bits, several interrupt sources, and even more. We won’t be able to cover all of them in the tutorial – we will take common cases and probably something that might look interesting.

Setting USART hardware

USART is usually referred to as RS232 interface what is wrong. USART stands for the communication protocol, while RS232 stands for signal logic levels and control signals. RS232 now is a thing of the past, but there are still lots of boards that support RS232. RS232 communication standard needs different signal levels than AVR microcontroller can provide. AVR usually gives 5V (or 3V) for logical “1” and 0V for logical “0”. RS232 standard uses +3V to 25V for logical “0” and -3V to -25V for logical “1”. For this, there is a memorable TTL to RS232 converter chip used like MAX232. But if you look for development boards, you will see that majority boards now uses USB communication standard, and instead of using MAX232, there is a USB-to TTL converters like FT232.

USB-to TTL converters with FT232

After installing proper drivers on PC there is a virtual COM port created that acts like standard serial interface. Whatever converter chip we are gonna use, microcontroller communication is same – USART, and we are going to stick to it.

Importance of system clock frequency

Once we use USART communications in our project there comes another dilemma – choosing a frequency for the system clock. The problem is that the USART module uses a system clock to generate BAUD rates and sample incoming RX and outgoing Tx lines at some specific frequencies. Simply speaking, to make perfect USART communication, your AVR has to be clocked at the frequency that can be divided by 1.8432MHz. This is why you can frequently find crystals like 7.3728MHz. The datasheet says that frequency can differ, but baud rate error cannot exceed 0.5% to maintain reliable communication. In the datasheet, you will find a formula and pre-calculated tables of recommended baud rate settings for commonly used oscillator frequencies. In our tutorial, we are using 16MHz crystal, so according to datasheet table (19-6), most of the baud rates don’t exceed 0.5% mark. Of course, you can use baud rates with higher errors, but this may lead to sampling errors on the receiver side.

Understanding USART data frames

USART communication uses a particular protocol to transmit data reliably. It consists of several parts:

  • 1 start bit;
  • 5,6,7,8 or 9 data bits;
  • no, even, or odd parity bit;
  • 1 or 2 stop bits.

No matter what format is chosen, transmitter and receiver sides must be configured same. Usually, for terminal programs, we use one start bit, 8 data bits, no parity, and one stop bit.

Initializing USART communication

OK, leave theory behind and do some real examples. To do this and following experiments, you can use Arduino Duemilanove board as a general development board. USART is transmitted through TTL-USB converter, so once drivers are installed on PC you should see a virtual RS232 port like COM3 or similar. Download any terminal program for receiving and sending serial data. Bray’s terminal works perfectly for this.

Starting USART communication isn’t as hard as it may seem. First of all, we need to decide our baud rate. Our development board is clocked with 16MHz crystal, and let’s say we want our baud rate to be 9600. The datasheet says that such a combination will give an error value of 0.2%, which is less than 0.5% and is safe to use. So in our program, we define baudrate value:

#define USART_BAUDRATE 9600

The next step is to calculate UBRR value which is a baud rate prescaller calculated out of system clock and desired baud rate. Usually, it is calculated by the following formula that is also defined as follows:

#define UBRR_VALUE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

UBRR0 register is a 16-bit register, so it should be treated as any other 16-bit register in AVR – first, write 8 bits to UBRR0H and then to UBRR0L.

// Set baud rate
UBRR0H = (uint8_t)(UBBR_VALUE>>8);
UBRR0L = (uint8_t)UBBR_VALUE;

Luckily in AVR GCC, we can simply assign UBRR_VALUE value to UBRR0 and leave to the compiler to take care of writing sequence.

UBRR0= UBRR_VALUE;

Next thing we have to take care of is the data frame format. Let’s use a standard form:

// Set frame format to 8 data bits, no parity, 1 stop bit
UCSR0C |= (1<<UCSZ1)|(1<<UCSZ0);

Now we can enable transmission by turning reception and transmission bits in UCSR0B register:

Sending and receiving USART data

In the previous chapter, we learned how to configure and enable USART0 transfer; now, we are ready to perform simple data transmission. Let us write two functions – one for transmitting data and another for receiving. There is a dedicated register UDR0 in USART0 module, which is used to send and receive data. Once you write data to this register, data transfer is started automatically. To send the following data byte program has to wait for transmission completion. It is done by checking transmission buffer empty flag UDRE0 in UCSR0A register. So always we have to make sure if data is already sent before writing another data byte. This looks as follows:

//wait while previous byte is completed
while(!(UCSR0A&(1<<UDRE0))){};
// Transmit data
UDR0 = u8Data;

The same situation is when receiving data. We need to check RXC0 flag to be raised when receive is complete and there is data present in the receive buffer.

// Wait for byte to be received
while(!(UCSR0A&(1<<RXC0)) ){};
// Return received data
return UDR0;

Working USART0 example

Once we have our bits of the program, we can put everything into one working program. Let us do simple test routine where we will send a byte number from a terminal program; our program receives it, increments by one, and send it back to the terminal.

#include <avr/io.h>
#define USART_BAUDRATE 9600
#define UBRR_VALUE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
void USART0Init(void)
{
// Set baud rate
UBRR0H = (uint8_t)(UBRR_VALUE>>8);
UBRR0L = (uint8_t)UBRR_VALUE;
// Set frame format to 8 data bits, no parity, 1 stop bit
UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
//enable transmission and reception
UCSR0B |= (1<<RXEN0)|(1<<TXEN0);
}
void USART0SendByte(uint8_t u8Data)
{
//wait while previous byte is completed
while(!(UCSR0A&(1<<UDRE0))){};
// Transmit data
UDR0 = u8Data;
}
uint8_t USART0ReceiveByte()
{
// Wait for byte to be received
while(!(UCSR0A&(1<<RXC0))){};
// Return received data
return UDR0;
}
int main (void)
{
uint8_t u8TempData;
//Initialize USART0
USART0Init();
    while(1)
    {
        // Receive data
        u8TempData = USART0ReceiveByte();
        // Increment received data
        u8TempData++;
        //Send back to terminal
        USART0SendByte(u8TempData);
    }
}

and here are results on the terminal screen

As you can see, transmitted data bytes are incremented by one and sent back to the terminal program—simple cryptography.

In this tutorial part, we learned to do simple USART communications. Don’t get relaxed too much because this method is least efficient because we need to pool down the flags to perform transmission and receive. These loops occupy microcontrollers resources and wastes energy. If you are looking for more robust and efficient USART transmission then we need to use interrupt-driven USART communications.

Download example program here.

3 Comments:

  1. Hi, dear Admin.
    I’m newbie in avrs so I ask:
    1) How to switch to external oscillator? Is there any action need to be done for it?
    2) I could not find where is F_CPU defined. I suspect it should be #define F_CPU 16000000L //16 MHz. Is it correct?

    Thanks, Vladimir.

Comments are closed