Configuring megaAVR® Interrupts

Last modified by Microchip on 2023/11/10 11:09

AVR® Interrupt operation must be carefully initialized by the application developer. This page summarizes the key initialization and usage steps required for using interrupts in an application.

Further information regarding interrupt usage is provided in the Interrupt Module section of the AVR-LIBC Library.

#include Files
#include Standard Headers

The application must include header files avr/io.h and avr/interrupt.h as shown in the following code:

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

The avr/interrupt.h header file provides several macros intended to simplify the application of interrupts in an application, such as macros for globally enabling/disabling interrupts (I-bit in the Status Register), as well as a macro for assigning an interrupt function to a specific interrupt vector:

  • sei( )
  • cli( )
  • ISR(vector_id, attributes)

The vector_id macros are defined in the processor-specific header file (included via avr/io.h), as well as in the device datasheet. Its construction is defined below.

Back to top

Provide Interrupt Service Routine

An interrupt handler function is different to an ordinary function in that it handles the context save and restore to ensure that upon return from interrupt, the program context is maintained. A different code sequence is used to return from these functions as well.

There are several actions that the compiler needs to take to generate an interrupt service routine:

  • The compiler has to be told to use an alternate form of return instruction (RETI vs. RET)
  • The compiler has to be told about any specific additional options
    • Enable nesting of interrupts
    • Options for generation of prologue/epilogue code
  • The function needs to be linked to a specific interrupt vector.

Several handler function attributes are provided to the application developer, enabling these options.

  • The ISR( ) macro is provided to ease the definition of interrupt handler functions with attributes

For all interrupt vectors without specific handlers, a default interrupt handler will be installed. The default interrupt handler will reset the device.

An application may override the default handler and provide an application-specific default interrupt handler by using the BADISR_vect vector_id inside the ISR( ) macro.

Back to top

Review the Following Examples
 ISR( ) Macro

The following code example shows how to use the ISR() macro to define an interrupt function:

ISR(vector_id, ISR_[BLOCK|NOBLOCK|NAKED|ALIASOF])
{
   /* Hardware auto-clears the interrupt flag (most interrupt sources) */
   /* Clear the cause of the interrupt (required by some interrupt sources) */
   /* ISR-specific processing */
}        

The various parameters will now be more fully described.

vector_id

This identifier is a concatenation of a Vector Source ID and _vect. Vector Source IDs are found in the device datasheet, as shown (partially) in the following example for ATmega328PB:

Interrupt Vectors

Misspelt vector_ids will still generate a function, however, it will not be wired into the interrupt vector table. The compiler will generate a warning if it detects a suspicious-looking name.

Attributes

ISR( ) attributes provide further instruction to the compiler regarding how to set up the interrupt function.

ISR_BLOCK

Global interrupts are initially disabled by the AVR hardware when entering the ISR. This setting does not modify this state.

This attribute is identical to an ISR( ) macro with no attribute specified.

ISR_NOBLOCK

ISR runs with global interrupts initially enabled. The interrupt enable flag is activated by the compiler as early as possible within the ISR to ensure minimal processing delay for nested interrupts.

This may be used to create nested ISRs, however, care should be taken to avoid stack overflows, or to avoid infinitely entering the ISR for those cases where the AVR hardware does not clear the respective interrupt flag before entering the ISR.

ISR_NAKED

ISR is created with no prologue or epilogue code. The user code is responsible for the preservation of the machine state including the SREG register, as well as placing a reti() at the end of the interrupt routine.

ISR_ALIASOF(vector_id)

This may be used to define additional vectors that share the same handler. The following example aliases the PCINT1 vector to the PCINT0 handler:

ISR(PCINT0_vect)
{
  ...
 // Code to handle the event.
}
ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));

ISR( ) Example

In this code example, we highlight the required header files and correct ISR definition of a handler function for the Timer/Counter1 Clear-Timer-On-Compare (CTC) mode interrupt source. The handler toggles LED0 on the ATmega328PB Xplained Mini every 100 mS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <avr/io.h>
#include
<avr/interrupt.h>

ISR(TIMER1_COMPA_vect, ISR_BLOCK)
{
    PORTB ^= (1 << PORTB5);   // Toggle LED0
}

int main(void)
{
   // Initialization

   // Set LED as output
   DDRB |= (1 << PORTB5);     // Configure PB5 as digital output        
   PORTB &= ~(1 << PORTB5);   // Set initial level for PB5

   // Set up Timer/Counter1
   TCCR1B |= (1 << WGM12 );   // Configure timer 1 for CTC mode
   OCR1A = 25000;             // Set CTC compare value to 10Hz (100mS)
                              // at 16MHz AVR clock, with a prescaler of 64
   TIMSK1 |= (1 << OCIE1A );  // Enable CTC interrupt
   TCCR1B |= ((1 << CS10 ) | (1 << CS11 )); // Start Timer/Counter1 at F_CPU/64

   // Enable all interrupts
   sei();

   while(1);
}

Back to top

Configure the Peripheral

Next, you must configure the peripheral to generate interrupt request events.

For example, the ATmega328PB contains several Timer/Counter peripherals modules. Each module has a mode called Clear Timer on Compare (CTC) that, when properly initialized, will periodically trigger a Timer1 Output Compare Match Flag signal in the TC1 interrupt flag register (TIFR1) register as shown in the accompanying image

Timer1

In this example, we will initialize Timer/Counter1 in CTC mode to generate interrupt requests every 100 mS, given a prescaled input of 250 kHz (16 MHz/64):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <avr/io.h>
#include
<avr/interrupt.h>

ISR(TIMER1_COMPA_vect, ISR_BLOCK)
{
    PORTB ^= (1 << PORTB5);   // Toggle LED0
}

int main(void)
{
   // Initialization

   // Set LED as output
   DDRB |= (1 << PORTB5);     // Configure PB5 as digital output        
   PORTB &= ~(1 << PORTB5);   // Set initial level for PB5

   // Set up Timer/Counter1
   TCCR1B |= (1 << WGM12 );   // Configure timer 1 for CTC mode
   OCR1A = 25000;             // Set CTC compare value to 10Hz (100mS)
                              // at 16MHz AVR clock, with a prescaler of 64
   TIMSK1 |= (1 << OCIE1A );  // Enable CTC interrupt
   TCCR1B |= ((1 << CS10 ) | (1 << CS11 )); // Start Timer/Counter1 at F_CPU/64

   // Enable all interrupts
   sei();

   while(1);
}

​This is an example of a non-persistent interrupt. The TIFR1.OCFA flag is automatically cleared by hardware on entry to the handler.

The TIFR1.OCFA flag can also be cleared manually by writing a logic 1 to the bit location.

Back to top

Enable All Interrupts

Finally, we need to globally enable all enabled peripheral interrupts by setting the Global Interrupt Enable I-bit in the Status Register (SREG).

The AVR-LIBC interrupt library provides two handy macro functions for this:

  • sei( ) to Enable interrupts globally
  • cli( ) to Disable interrupts globally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <avr/io.h>
#include
<avr/interrupt.h>

ISR(TIMER1_COMPA_vect, ISR_BLOCK)
{
    PORTB ^= (1 << PORTB5);   // Toggle LED0
}

int main(void)
{
   // Initialization

   // Set LED as output
   DDRB |= (1 << PORTB5);     // Configure PB5 as digital output        
   PORTB &= ~(1 << PORTB5);   // Set initial level for PB5

   // Set up Timer/Counter1
   TCCR1B |= (1 << WGM12 );   // Configure timer 1 for CTC mode
   OCR1A = 25000;             // Set CTC compare value to 10Hz (100mS)
                              // at 16MHz AVR clock, with a prescaler of 64
   TIMSK1 |= (1 << OCIE1A );  // Enable CTC interrupt
   TCCR1B |= ((1 << CS10 ) | (1 << CS11 )); // Start Timer/Counter1 at F_CPU/64

   // Enable all interrupts
   sei();

   while(1);
}

Learn More

Back to top