Interrupt USART mikrokontroller AVR Basic

Posted on

What is Interrupt

What is Interrupt on a microcontroller? – This tutorial will teach you the basics for creating interrupt-driven USART communications. This assumes that the reader has read and fully understands my previous tutorial on basic serial communication.

AVR and almost all microcontrollers – contain a feature is called interrupt.

An interrupt, as the name implies, allows an external event (such as input from a user or AVR peripheral) to temporarily pause the main microcontroller program and run the “Interrupt Service Routine” (often shortened to “ISR”) before resuming the main program it left off.

Interrupts are especially useful for handling erratic external inputs (such as pin changes or serial byte arrivals), as well as for processing “background tasks” such as flashing an LED whenever a timer overflows.

In this tutorial, we will use the AVR USART peripheral interrupt.

Interrupt USART mikrokontroller AVR Basic

Simple echo program

We’ve created a simple program from scratch that will echo the bytes received in the AVR’s USART interface.

The complete program list is as follows:

# include < avr / io .h >
# define USART_BAUDRATE 9600
# define BAUD_PRESCALE ((( F_CPU / ( USART_BAUDRATE * 16 UL ))) - 1)
int main ( void )
{
char ReceivedByte ;
UCSRB |= (1 << RXEN ) | (1 << TXEN ); // Turn on the transmission and reception circuitry
UCSRC |= (1 << URSEL ) | (1 << UCSZ0 ) | (1 << UCSZ1 ); // Use 8- bit character sizes
UBRRH = ( BAUD_PRESCALE >> 8) ; // Load upper 8- bits of the baud rate value into the high byte
of the UBRR register
UBRRL = BAUD_PRESCALE ; // Load lower 8 - bits of the baud rate value into the low byte of the
UBRR register
for (;;) // Loop forever
{
while (( UCSRA & (1 << RXC )) == 0) {}; // Do nothing until data have been received and is
ready to be read from UDR
ReceivedByte = UDR ; // Fetch the received byte value into the variable " ByteReceived "
while (( UCSRA & (1 << UDRE )) == 0) {}; // Do nothing until UDR is ready for more data to
be written to it
UDR = ReceivedByte ; // Echo back the received byte back to the computer
}
}

Readers should be able to fully understand this code. If not, you can reread the tutorial on basic serial communication.

Now, we want to extend this code so that serial data is echoed back when received in an interrupt, not our main program loop.

To do this, we first need to include the avr-libc standard library header, .

This file contains library functions and macros related to the AVR interrupt functionality.

We’ll be adding this to the top of our code, under the standard device header, including:

# include < avr / io .h >
# include < avr / interrupt .h >

Setelah disertakan, sekarang kami memiliki cara untuk membuat ISR untuk menangani penerimaan serial.

Untuk membuat ISR, kami menggunakan sintaks:

ISR ({ Vector Name }, { Options })
{
// Code to be executed when ISR fires
}

And put it in our program as if it were a normal function. To add one to handle receiving bytes via USART, we need to find the appropriate name in our AVR datasheet.

In the datasheet for our example AVR, ATMEGA16, we see that the name of the interrupt when a byte is received is “USART RXC”.

The standard avr-libc device header file – which is included in our program as well as other AVR-GCC programs involving the AVR I/O functionality defines vector names for us.

The avr-libc symbolic name for each interrupt vector is identical to that of the datasheet, with the addition of the “vect” suffix to indicate that it is a vector name.

So, since our datasheet lists “USART RXC” as the vector name, the syntax for our program is:

ISR ( USART_RXC_vect , ISR_BLOCK )
{
// Code to be executed when the USART receives a byte here
}

Which we will place at the end of our program, after our main function. Note that here I’ve used the ISR option of ISR BLOCK, which should be used by most interrupt service routines except in special situations.

That’s beyond the scope of this tutorial, but please read the avr-libc manual if you want to know more about it. The new program code looks like this:

# include < avr / io .h >
# include < avr / interrupt .h >
# define USART_BAUDRATE 9600
# define BAUD_PRESCALE ((( F_CPU / ( USART_BAUDRATE * 16 UL ))) - 1)
int main ( void )
{
char ReceivedByte ;
UCSRB |= (1 << RXEN ) | (1 << TXEN ); // Turn on the transmission and reception circuitry
UCSRC |= (1 << URSEL ) | (1 << UCSZ0 ) | (1 << UCSZ1 ); // Use 8- bit character sizes
UBRRH = ( BAUD_PRESCALE >> 8) ; // Load upper 8- bits of the baud rate value into the high byte
of the UBRR register
UBRRL = BAUD_PRESCALE ; // Load lower 8 - bits of the baud rate value into the low byte of the
UBRR register
for (;;) // Loop forever
{
while (( UCSRA & (1 << RXC )) == 0) {}; // Do nothing until data have been received and is
ready to be read from UDR
ReceivedByte = UDR ; // Fetch the received byte value into the variable " ByteReceived "
while (( UCSRA & (1 << UDRE )) == 0) {}; // Do nothing until UDR is ready for more data to
be written to it
UDR = ReceivedByte ; // Echo back the received byte back to the computer
}
}
ISR ( USART_RXC_vect )
{
// Code to be executed when the USART receives a byte here
}

Filling ISR

Right now our acceptance of our new USART ISR doesn’t really do anything – we’ve just defined it.

We want it to echo back the sent bytes, so we’ll move our main loop code:

while (( UCSRA & (1 << RXC )) == 0) {}; // Do nothing until data have been received and is ready
to be read from UDR
ReceivedByte = UDR ; // Fetch the received byte value into the variable " ByteReceived "
while (( UCSRA & (1 << UDRE )) == 0) {}; // Do nothing until UDR is ready for more data to be
written to it
UDR = ReceivedByte ; // Echo back the received byte back to the computer

There. However, we can now remove the two while loops – since the ISR is only active when a byte is received, and only one byte is sent after each reception, we can guarantee that both checks are now redundant.

When ISR is enabled, we know that there are bytes received in the USART input buffer, and also not in the output buffer. Using this knowledge, we can simplify our ISR code to the following:

ISR ( USART_RXC_vect )
{
char ReceivedByte ;
ReceivedByte = UDR ; // Fetch the received byte value into the variable " ByteReceived "
UDR = ReceivedByte ; // Echo back the received byte back to the computer
}

Note that I’ve also moved the variable declaration from the ReceivedByte variable to the ISR, because that’s where it’s actually used. It is worth mentioning at this point a small section in the datasheet about RXC interrupts:

When interrupt-driven data reception is used, the complete receiving routine must read the received data from UDR to clear the RXC Flag, otherwise a new interrupt will occur after the interrupt routine ends.

That’s important for us to remember: if you use the USART RXC interrupt, you must read a byte from the UDR register to clear the interrupt flag. We did it in the code above, but keep that in mind for your future projects!

Enabling USART Receive Interrupt

Let’s take a look at the latest from our test code:

# include < avr / io .h >
# include < avr / interrupt .h >
# define USART_BAUDRATE 9600
# define BAUD_PRESCALE ((( F_CPU / ( USART_BAUDRATE * 16 UL ))) - 1)
int main ( void )
{
UCSRB |= (1 << RXEN ) | (1 << TXEN ); // Turn on the transmission and reception circuitry
UCSRC |= (1 << URSEL ) | (1 << UCSZ0 ) | (1 << UCSZ1 ); // Use 8- bit character sizes
UBRRH = ( BAUD_PRESCALE >> 8) ; // Load upper 8- bits of the baud rate value into the high byte
of the UBRR register
UBRRL = BAUD_PRESCALE ; // Load lower 8 - bits of the baud rate value into the low byte of the
UBRR register
for (;;) // Loop forever
{
// Do nothing - echoing is handled by the ISR instead of in the main loop
}
}
ISR ( USART_RXC_vect )
{
char ReceivedByte ;
ReceivedByte = UDR ; // Fetch the received byte value into the variable " ByteReceived "
UDR = ReceivedByte ; // Echo back the received byte back to the computer
}

If you compile and run this, you’ll see that nothing happens – no characters are echoed back to the connected computer.

This is because even though we have defined and populated the ISR, we haven’t enabled it. To do so, we need to do two things:
Enable global interrupt
Enable USART Byte Received interrupt
The first item is simple, so we’ll do that first. The AVR microcontroller contains global flags that can be set or cleared to enable or disable interrupt handling.

Note that setting this flag does not enable all interrupts, it only allows the possibility of running them. If the Global Interrupt Enable flag is disabled, all interrupts will be ignored, even if enabled (more on that later).

To enable the Global Interrupt Enable flag, we can use the sei() macro which is defined by the avr-libc header file helpfully.

It is so named because it generates the “SEI” assembly instructions in the final code list, which the AVR interprets as a command to set the Enable Global Interrupt flag.

The credit of sei() is cli() (to turn off interrupt handling) but we won’t be using that macro in this tutorial.

We will add our sei() instruction to our main routine, after configuring the USART register:

// ...
UBRRH = ( BAUD_PRESCALE >> 8) ; // Load upper 8- bits of the baud rate value into the high byte
of the UBRR register
UBRRL = BAUD_PRESCALE ; // Load lower 8 - bits of the baud rate value into the low byte of the
UBRR register
sei () ; // Enable the Global Interrupt Enable flag so that interrupts can be processed
for (;;) // Loop forever
// ...

Sekarang untuk item kedua di daftar kami, yang perlu dilakukan sebelum interupsi diaktifkan. Kita perlu secara khusus mengaktifkan USART Receive Complete interrupt, yang dapat kita lakukan dengan menyetel flag yang sesuai di register kontrol USART.

Di ATMEGA16, bit ini disebut RXCIE (kependekan dari “Recieve Complete Interrupt Enable”) dan merupakan bagian dari register UCSRB. Menyetel bit ini memungkinkan penanganan vektor peristiwa USART RXC:

UCSRB |= (1 << RXCIE );

We will add this to our main routine, before “sei();” we just ordered.

Bringing it all together

Now we have an interrupt-driven serial example:

#include <avr/io.h>
#include <avr/interrupt.h>
#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL ))) - 1)
int main ( void )
{
UCSRB |= (1 << RXEN ) | (1 << TXEN ); // Turn on the transmission and reception circuitry
UCSRC |= (1 << URSEL ) | (1 << UCSZ0 ) | (1 << UCSZ1 ); // Use 8- bit character sizes
UBRRH = ( BAUD_PRESCALE >> 8) ; // Load upper 8- bits of the baud rate value into the high byte of the UBRR register
UBRRL = BAUD_PRESCALE ; // Load lower 8 - bits of the baud rate value into the low byte of the
UBRR register
UCSRB |= (1 << RXCIE ); // Enable the USART Recieve Complete interrupt ( USART_RXC )
sei () ; // Enable the Global Interrupt Enable flag so that interrupts can be processed
for (;;) // Loop forever
{
// Do nothing - echoing is handled by the ISR instead of in the main loop
}
}
ISR ( USART_RXC_vect )
{
char ReceivedByte ;
ReceivedByte = UDR ; // Fetch the received byte value into the variable " ByteReceived "
UDR = ReceivedByte ; // Echo back the received byte back to the computer
}

Which, like the original program, will echo the characters received via USART. However, since our program is now interrupt-driven, we can add code into the main loop that will run when data isn’t received – like flashing an LED.

Interrupts allow infrequent “background” tasks to run when they occur, without incurring the runtime penalty of having to check the hardware until an even number occurs.

This frees up our main loop to handle critical code, with the interrupt code pausing the main code to execute when an event of interest occurs.

Interrupts should be kept as short as possible in execution time. This is because when one ISR is executing, another is blocked and thus if another ISR condition occurs while one ISR is executing, the events of that ISR will be missed or delayed.

Because of this, communication ISRs are generally kept short by receiving characters through the ISR, and placing them in a buffer that the master code can read at any time.

This ensures that received data will not be missed, while giving the main program time to finish what it is doing before it has to process input.

Similarly, long transmissions can be accomplished by placing the data to be sent into a buffer, and requesting an interrupt to send data when the hardware is ready while the main program performs other tasks.

Original title : Interrupt Driven USART in AVR-GCC
Author: Dean Camera
Publish Date : September 2, 2012
Download Original File