Turn-key PCB assembly services in prototype quantities or low-volume to mid-volume production runs

Programming AVR USART with AVR-GCC. Part 2

In the previous part of the USART tutorial, we have discussed the most straightforward way of implementing USART transmitting and receiving routines. These are OK to use, but in more intense and power critical applications they are not practical and efficient. Firs of all using loops to poll for transmitting buffer to be ready or wait for received byte consumes lots of processing power what also leads to more power consumption. In reception mode we cant predict when actual data will be received, so the program has to check for received data indicating flag constantly and don’t miss it as next upcoming byte may clear it. So there is a better way of using USART – so-called Interrupt Driven USART.

USART Interrupt sources

If you look into datasheet you will find that USART0 in Atmega328 has three interrupt sources:

  • TX Complete;

  • TX Data Register Empty;

  • RX Complete.

Probably a natural question comes out: Why there are two interrupts for transmission? Explanation is simple. Lets take TX Complete interrupt. It will occur when Transmit Shift Register has been shifted out and is empty. In this case we have empty transmit buffer UDR0 and Transmit Shift Register. After this interrupt there is no physical transmission on pins. This feature is usually used in half-duplex transmission mode when transmitter and receiver share same line like in RS-485 standard.

So if we need to use a single line and be able to transmit and receive data – use Half Duplex mode and TX complete interrupts. If we need to transfer data in one direction only, for instance, serial LCD using TX Complete interrupt is impractical because we get gaps between transmitted frames and loose transmission rate. In such a case, it is more practical to use Tx Data Register Empty interrupt. This interrupt occurs just after data from transmit buffer is sent to transmit shift register. In this case, UDR0 is ready to receive data while the previous is being transmitted. In this case, we get “chain” like transmission without gaps between frames.

RX complete interrupt occurs when receive is complete, and there is data present in the receive buffer.

Programming interrupt based USART routines

The real power of interrupt driven USART transmissions begins when using buffers. This means, we don’t have to take care each time new data is received. Interrupt routines can simply fill the buffer with data that can be processed later. For instance it could be a some sort of command that consists of sequence of bytes or some numerical matrix of data. Using of buffers is going to another tutorial part, so we won’t focus on them just write simple solution for our example.

As usually let us write Interrupt based USART test program. In this case, we are going to use both – receive and transmit routines. Firs of all collected data will be stored in a simple buffer, and then it will be sent back in backward order.

Firs of all we define structure data type that will be used to create buffer:

#define BUF_SIZE 20

//type definition of buffer structure

typedef struct{

//Array of chars

uint8_t buffer[BUF_SIZE];

//array element index

uint8_t index;

}u8buf;

Here we will be storing our received array of bytes. There is only one index element that that keeps track of buffer. So our buffer is gonna be stack type. We need to have several buffer control routines in order to use it. So here are three routines that will help:

//initialize buffer

void BufferInit(u8buf *buf)

{

//set index to start of buffer

buf->index=0;

}

//write to buffer routine

uint8_t BufferWrite(u8buf *buf, uint8_t u8data)

{

if (buf->index<BUF_SIZE)

{

buf->buffer[buf->index] = u8data;

//increment buffer index

buf->index++;

return 0;

}

else return 1;

}

uint8_t BufferRead(u8buf *buf, volatile uint8_t *u8data)

{

if(buf->index>0)

{

buf->index--;

*u8data=buf->buffer[buf->index];

return 0;

}

else return 1;

}

So BufferInit routine simply resets array index to 0, what makes it ready to accept new data. BufferWrite routine checks if there is space left in buffer and writes following data. If buffer is full – error code is returned. Same situation is with BufferRead – it checks if there is data in buffer and pops data to variable. These buffer operations don’t pretend to be good example, but for our Interrupt driven USART communications will work.

After we set up the buffer, we can initialize USART0 communications.

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 reception and RC complete interrupt
    UCSR0B |= (1<<RXEN0)|(1<<RXCIE0);
}

Baud rate settings remain the same as in simple USART example. As we are receiving data first, we need only to enable receiver, and RX Complete interrupts. Now is left to write interrupt service routines for the receiver:

//RX Complete interrupt service routine
ISR(USART_RX_vect)
{
    uint8_t u8temp;
    u8temp=UDR0;
    //check if period char or end of buffer
    if ((BufferWrite(&buf, u8temp)==1)||(u8temp=='.'))
    {
        //disable reception and RX Complete interrupt
        UCSR0B &= ~((1<<RXEN0)|(1<<RXCIE0));
        //enable transmission and UDR0 empty interrupt
        UCSR0B |= (1<<TXEN0)|(1<<UDRIE0);
    }
}

Every time RX Complete interrupt occurs it check if the buffer is full after write and if there is a period ‘.’ char present which means end of receiving. If one of conditions is met, RX complete interrupt is disabled and transmitter is enabled along with Data Register Empty interrupt. So it gives a turn to transmitter.

//UDR0 Empty interrupt service routine
ISR(USART_UDRE_vect)
{
    //if index is not at start of buffer
    if (BufferRead(&buf, &UDR0)==1)
    {
        //start over
        //reset buffer
        BufferInit(&buf);
        //disable transmission and UDR0 empty interrupt
        UCSR0B &= ~((1<<TXEN0)|(1<<UDRIE0));
        //enable reception and RC complete interrupt
        UCSR0B |= (1<<RXEN0)|(1<<RXCIE0);
    }
}

It sends buffer contents until the buffer index reaches 0. Once the buffer is empty, it resets buffer and enables receiver interrupts.

And here is a full working code that can be tested with any Atmega328 development board:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#define USART_BAUDRATE 9600
#define UBRR_VALUE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
//define max buffer size
#define BUF_SIZE 20
//type definition of buffer structure
typedef struct{
    //Array of chars
    uint8_t buffer[BUF_SIZE];
    //array element index
    uint8_t index;
}u8buf;
//declare buffer
u8buf buf;
//initialize buffer
void BufferInit(u8buf *buf)
{
    //set index to start of buffer
    buf->index=0;
}
//write to buffer routine
uint8_t BufferWrite(u8buf *buf, uint8_t u8data)
{
    if (buf->index<BUF_SIZE)
    {
        buf->buffer[buf->index] = u8data;
        //increment buffer index
        buf->index++;
        return 0;
    }
        else return 1;
}
uint8_t BufferRead(u8buf *buf, volatile uint8_t *u8data)
{
    if(buf->index>0)
    {
        buf->index--;
        *u8data=buf->buffer[buf->index];
        return 0;
    }
    else return 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 reception and RC complete interrupt
    UCSR0B |= (1<<RXEN0)|(1<<RXCIE0);
}
//RX Complete interrupt service routine
ISR(USART_RX_vect)
{
    uint8_t u8temp;
    u8temp=UDR0;
    //check if period char or end of buffer
    if ((BufferWrite(&buf, u8temp)==1)||(u8temp=='.'))
    {
        //disable reception and RX Complete interrupt
        UCSR0B &= ~((1<<RXEN0)|(1<<RXCIE0));
        //enable transmission and UDR0 empty interrupt
        UCSR0B |= (1<<TXEN0)|(1<<UDRIE0);
    }
}
//UDR0 Empty interrupt service routine
ISR(USART_UDRE_vect)
{
    //if index is not at start of buffer
    if (BufferRead(&buf, &UDR0)==1)
    {
        //start over
        //reset buffer
        BufferInit(&buf);
        //disable transmission and UDR0 empty interrupt
        UCSR0B &= ~((1<<TXEN0)|(1<<UDRIE0));
        //enable reception and RC complete interrupt
        UCSR0B |= (1<<RXEN0)|(1<<RXCIE0);
    }
}
int main (void)
{
    //Init buffer
    BufferInit(&buf);
    //set sleep mode
    set_sleep_mode(SLEEP_MODE_IDLE);
    //Initialize USART0
    USART0Init();
    //enable global interrupts
    sei();
    while(1)
        {
            //put MCU to sleep
            sleep_mode();
        }
}

As we can see from example, our program receives a string of data and transmits it back in backwards order. Each byte of received and transmitted by interrupt routines. Once microcontroller finishes serving interrupt it enters an idle sleep mode. This way we aren’t occupying much of processing power and draining less supply current. Keep in mind that this example is used only to demonstrate usage of USART interrupts. In real world you would probably go with more generic buffer algorithms – maybe circular buffers. Code also should be modular as using single source file gets filled up very quickly. Feel free to comment and correct if needed.

Bookmark the permalink.

6 Comments

  1. These IF statements should use || not | since you are performing a logical-or not a bitwise-or:
    if ((BufferWrite(&buf, u8temp)==1)|(u8temp==’.’))

    Also, why all the volatiles?

  2. Fixed IF statement.
    Removed volatiles from buffer variables.
    One volatile is needed anyway:
    uint8_t BufferRead(u8buf *buf, volatile uint8_t *u8data)
    Because second argument of BufferRead function, when called, is UDR0 register which is SFR and is defined as volatile.
    Thank you for corrects.

  3. I’d probably not pass register addresses around in this case. I’d use the return argument to pass back the value to be written, and let the calling code perform the ‘save’ (or load UDR in this case). I’d have a separate function to determine whether data was ready, thus avoiding the need to return a success/fail flag. Anyway, that’s just personal preference 😉

    Incidentally, although you can’t do it on all AVRs, the UBRR0 register can be loaded in one 16-bit write, eg. UBRR0 = (uint16_t)UBRR_VALUE;

  4. I had this in mind, just decided to make code more compact. I agree that using return argument would be better and fail safe. Probably I’ll fix this area in a time.
    About UBRR0 value. I mentioned about loading 16-bit register in article. Anyway in program I used safe way where high and low bytes are written separately.
    P.S. I’m wandering why Akismet is marking your comments as SPAM. Need to find different solution to avoid this.

  5. Very interesting article, thank you. Until then, I rabotal with USART without interruption, were often lost data transmission / reception. Thanks for the explanation.

  6. uint8_t BufferReadLIFO(u8buf *buf, volatile uint8_t *u8data)
    {
    if(buf->index<sizeof(buf))
    {
    *u8data = buf->buffer[buf->index];
    //increment buffer index
    buf->index++;
    return 0;
    }
    else return 1;
    }

    it can be like that for LIFO?

Leave a Reply