In the last part of the USART tutorial, we 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. First 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 can’t 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 the 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? The explanation is simple. Let’s take TX Complete interrupt. It will occur when Transmit Shift Register has been shifted out and is empty. We have empty transmit buffer UDR0 and Transmit Shift Register in this case. 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. Suppose we need to transfer data in one direction only, for instance. In that case, 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 a 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 a simple solution for our example.
As usual, let us write Interrupt based USART test program. In this case, we will use both – receive and transmit routines. First of all, collected data will be stored in a simple buffer, and then it will be sent back in backward order.
First 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 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 space is left in buffer and writes following data. If the buffer is full – error code is returned. Same situation is with BufferRead – it checks if there is data in a buffer and pops data to variable. These buffer operations don’t pretend to be a 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 the 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 checks if the buffer is full after writing and if there is a period ‘.’ char present, which means the 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 the 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 complete 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 the example, our program receives a string of data and transmits it back in backward 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 the real world, you would probably go with more generic buffer algorithms – may be 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.
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?
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.
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;
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.
Very interesting article, thank you. Until then, I rabotal with USART without interruption, were often lost data transmission / reception. Thanks for the explanation.
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?