8-bit AVR® Interrupts Special Considerations

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

This page covers a few special considerations to keep in mind when working with interrupts on AVR® MCUs.

Sharing Data with the ISR

Variables shared between the ISR and the main program must be declared as volatile and be global in scope.

When compiling using the optimizer, in a loop like the following one:

uint8_t flag;
...
ISR(SOME_vect) {
    flag = 1;
}
...
while(flag == 0) {
...
}

the compiler will typically access "flag" only once, and optimize further accesses completely away since its code path analysis shows that nothing inside the loop could change the value of "flag" anyway.

To tell the compiler that this variable could be changed outside the scope of its code path analysis (e.g., within an interrupt service routine), the variable needs to be declared like this:

volatile uint8_t flag;
...
ISR(SOME_vect) {
    flag = 1;
}
...
while(flag == 0) {
...
}

When the variable is declared volatile as above, the compiler makes certain that wherever the variable is updated or read it will always write changes back to SRAM memory and read the variable from SRAM.

Back to top

Atomic Data Operations

For an operation to be considered atomic, it must guarantee uninterrupted access of a given variable. Many assembly languages provide this at certain levels, i.e., bit test and set, however, there is no provision to automatically provide atomicity of all variable types in the ANSI C language.

ANSI-C expressions and statements are non-atomic.

This issue can be problematic (in certain situations) when*multi-byte variables are shared with an ISR.

While declaring such a variable as volatile ensures that the compiler will not optimize accesses to it away, it does not guarantee atomic access to it. Consider the following code example:

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

volatile uint16_t ctr;

ISR(TIMER1_OVF_vect)
{
  ctr--;
}
...
int
main(void)
{
   ...
   ctr = 0x0200;
   start_timer();
  while(ctr != 0)
    // wait
      ;
   ...
}

There is a chance that the main context will exit its while( ) loop when the variable ctr just reaches the value 0x00FF. This happens because the compiler cannot natively access a 16-bit variable atomically in an 8-bit CPU. So, when ctr is for example at 0x0100, the compiler then tests the low byte for 0, which succeeds. It then proceeds to test the high byte, but that moment the ISR triggers, and the main context is interrupted. The ISR will decrement the variable from 0x0100 to 0x00FF, then the main context proceeds. It now tests the high byte of the variable which is (now) also 0, so it concludes the variable has reached 0, and terminates the loop.

Back to top

Atomic Access Macros

The AVR-LIBC Atomic library provides the ATOMIC_BLOCK macros which insert the appropriate interrupt protection when atomic access is desired. These macros operate via automatic manipulation of the Global Interrupt Status (I) bit of the SREG register. Exit paths from both block types are all managed automatically without the need for special considerations, i. e. the interrupt status will be restored to the same value it has been when entering the respective block.

Using the macros from this header file, the above code can be rewritten like:

#include <stdint.h>
#include
<avr/io.h>
#include
<avr/interrupt.h>
#include
<util/atomic.h>

volatile uint16_t ctr;

ISR(TIMER1_OVF_vect)
{
  ctr--;
}
...
int main(void)
{
   uint_16 ctr_copy;
   ...
   ctr = 0x0200;
   start_timer();
  do
   {
     ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
     {
       ctr_copy = ctr;
     }
   } while(ctr != 0);
    // wait
      ;
   ...
}

The ATOMIC_BLOCK macro will install the appropriate interrupt protection before accessing variable ctr, so it is guaranteed to be consistently tested.

In this case, the parameter ATOMIC_RESTORESTATE causes the ATOMIC_BLOCK to restore the previous state of the SREG register, saved before the Global Interrupt Status flag bit was disabled. The net effect of this is to make the ATOMIC_BLOCK's contents guaranteed atomic, without changing the state of the Global Interrupt Status flag when execution of the block completes.

Back to top

Learn More

Back to top