Interrupt driven AVR USART communication

Simple USART routines are not always practical because waiting transmitting buffer to be ready in a simple loop occupies processor resources. If we do not know when data will be received, we are just wasting resources inside waiting loop.

Another issue arises when multiple data bytes has to be sent/received. In this case, we have to write complex code to deal with data flow. Since microcontroller speciality are interrupts, it is better to use them to handle communications and this way improve overall performance and energy saving.

Instead, of continuously checking if there new data received in the UDR register or checking if send buffer is free, lets us write more effective USAR communication code with a guardian which would wake the MCU if it have received a byte via USART.

In other hand, Interrupt mode allows to perform other tasks at full capacity while waiting for USART interrupt.

avr usart

Let us implement interrupt driven AVR USART communication example.

For this we will need to include the interrupt library:

#include <avr/interrupt.h>

Then we will have to enable USART Receive Complete Interrupt (USART_RXC_vect) and enable global interrupts. Then write interrupt service routine ISR(USART_RXC_vect). Let us write simple bounce program – USART sends back incremented received value by one:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h> 
#ifndef F_CPU
//define cpu clock speed if not defined
#define F_CPU 3686400
#endif
//set desired baud rate
#define BAUDRATE 19200
//calculate UBRR value
#define UBRRVAL ((F_CPU/(BAUDRATE*16UL))-1)
void USART_Init()
{
	//Set baud rate
	UBRRL=UBRRVAL;		//low byte
	UBRRH=(UBRRVAL>>8);	//high byte
	//Set data frame format: asynchronous mode,no parity, 1 stop bit, 8 bit size
	UCSRC=(1<<URSEL)|(0<<UMSEL)|(0<<UPM1)|(0<<UPM0)|
		(0<<USBS)|(0<<UCSZ2)|(1<<UCSZ1)|(1<<UCSZ0);	
	//Enable Transmitter and Receiver and Interrupt on receive complete
	UCSRB=(1<<RXEN)|(1<<TXEN)|(1<<RXCIE);
	//enable global interrupts
	set_sleep_mode(SLEEP_MODE_IDLE);
	sei();
}
ISR(USART_RXC_vect)
{
	//defien temp value for storing received byte
	uint8_t Temp;
	//Store data to temp
	Temp=UDR;
	Temp++;//increment
	//send received data back
	// no need to wait for empty send buffer
	UDR=Temp;
}
int main(void)
{
	USART_Init();
	while(1)
	sleep_mode();
	//nothing here interrupts are working
	return 0;
}

Transmitter interrupt mode works in similar manner. There are two flags indicating and generating interrupts: USART Data Register Empty(UDRE), which generates interrupt when UDR register is empty, and ready to receive new data; and Transmit Complete (TXC) – generates interrupt when transmit is complete and no new data is in UDR. These interrupts are useful in half-duplex mode, where transmitting operation must enter receive mode.

Also, receive and transmit interrupt modes are often used in buffered mode when, for example, after all multi-byte data buffer is sent interrupt may request to load new data or set USART for receive mode.

As I mentioned Interrupt mode frees MCU resources and allows running other background tasks like sending information to LCD, Reading button states, flashing LED and so on. Interrupts has to be short enough so they would not block other interrupts in more complex programs. The more interrupts we use the more care should be taken while setting them properly.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.