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 the waiting loop.
Another issue arises when multiple data bytes have to be sent/received. In this case, we have to write complex code to deal with the data flow. Since the microcontroller’s specialty is interrupting, it is better to use them to handle communications, improving 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 a more effective USAR communication code with a guardian, which would wake the MCU if it has received a byte via USART.
On the other hand, Interrupt mode allows performing other tasks at total capacity while waiting for USART interrupt.
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 a 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 similarly. Two flags are 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 functional 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, a 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 have 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.