Pages: [1]   Go Down
Author Topic: Demonstration: Atomic access and Interrupt routines  (Read 1199 times)
0 Members and 1 Guest are viewing this topic.
New Hampshire
Offline Offline
God Member
*****
Karma: 17
Posts: 781
There are 10 kinds of people, those who know binary, and those who don't.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Had an idea this morning for what I thought would be an interesting little demonstration of why Atomic access is necessary when working with variables that are used in Interrupt routines.

Demonstration code:
Code:
#include "TimerOne.h"
#include <util/atomic.h>

volatile unsigned int test = 0xFF00;
void setup()
{
 
  Serial.begin(115200);
  Timer1.initialize(10000); //Call interrupt every 10ms
  Timer1.attachInterrupt(myInterrupt);
  Timer1.start();

}
 
void myInterrupt()
{
  //Interrupt just toggles the value of test.  Either 0x00FF or 0xFF00
  static bool alt = true;
  if(alt) test = 0x00FF;
  else    test = 0xFF00;
  alt = !alt;
}
 
void loop()
{
  unsigned int local;
  //ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
  {
     local = test;
  }
 
  //Test value of local.  Should only ever be 0x00FF or 0xFF00
  if(!(local == 0xFF00 || local == 0x00FF))
  {
    //local value incorrect due to nonatomic access of test
    Serial.println(local, HEX);
  }
}

If you download and run this code as is, unmodified, you will see intermittent outputs on the Serial port of 'FFFF' and '0'.

If the variable test is only ever assigned the values of 0x00FF or 0xFF00, then why is the value of our local variable occasionally 0x0000 or 0xFFFF?

The reason is because the assignment of the 16 bit value stored in our variable test to our variable local is not what we call atomic.  What do I mean by atomic?  Essentially it means that the assignment takes multiple cycles for the microcontroller is performed.  This is because the microcontroller is an 8-bit processor, so it only handles data in 8 bit chunks. When we are working with 16 bit data, the processor still only handles that data 8 bits at a time, so will always take multiple cycles to perform even the simplest operations on that data.

Because it takes multiple cycles even to copy data from one variable to another, there is the possibility that the interrupt will occur in the middle of that assignment.  When this happens, the processor stops what it is currently doing, switches to processing the ISR, and then returns back to where it left off.  The processor won't care if the ISR changed the value of it's variable while it was in the middle of assigning that value to another variable, so we get these situations where it assignments half of one value to our local variable, gets interrupted by the ISR, and then returns to copy the other half of the second value to our local variable.  And now our local variable contains invalid data.

The solution to this problem is to force the compiler to perform the operation without allowing any interruptions to occur.  There are several methods of doing this, but the basic requirement is that interrupts be disabled prior to performing the operation that requires atomicity, thus ensuring that the entire operation is completed without interruption.

If you uncomment the one line in my demonstration code: ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
the code will then run indefinitely without any output to the Serial port.  ATOMIC_BLOCK() is a macro provided by the AVR LibC library that provides the ability to designate a block of code to be performed atomically (ie, without interruption).

It is similar to using sei() and cli(), or Arduino's wrappers interrupts() nointerrupts() with one potentially key difference.  With the parameter ATOMIC_RESTORESTATE passed in, it doesn't simply re-enable interrupts upon exiting the atomic block.  It restores it to it's state previous to entering the atomic block.  In this simple demonstration code, the difference may not be apparent.

In more complicated code, with multiple paths of execution and potentially convoluted execution of various functions, the state of interrupts won't necessarily be known upon executing an ATOMIC_BLOCK(), so it may be necessary to restore the previous state as opposed to arbitrarily re-enabling interrupts.  Perhaps they were already disabled prior to entering the atomic block.
Logged


Left Coast, CA (USA)
Offline Offline
Brattain Member
*****
Karma: 361
Posts: 17259
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

 Very nice explanation for a often difficult to understand and troubleshoot problem.

Is there any reason the Arduino provided functions NoInterrups() and interrupts() could not be used to
provide the same protection?

http://arduino.cc/en/Reference/NoInterrupts shows an example of their usage.

Lefty



Logged

New Hampshire
Offline Offline
God Member
*****
Karma: 17
Posts: 781
There are 10 kinds of people, those who know binary, and those who don't.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Very nice explanation for a often difficult to understand and troubleshoot problem.

Is there any reason the Arduino provided functions NoInterrups() and interrupts() could not be used to
provide the same protection?

http://arduino.cc/en/Reference/NoInterrupts shows an example of their usage.

Lefty


interrupts() and nointerrupts() (or vice versa) can be used as well, just as sei() and cli() can be used.  A couple features I like about the ATOMIC_BLOCK macro though:

As already mentioned, it provides the capability of restoring previous state, as opposed to just re-enabling interrupts.

Also, it forces closure.  What I mean by this is with the alternatives, it's possible to unintentionally forget to include the counterpart call.  Perhaps your intent was:

Code:
nointerrupts();
atomic operations here
..
..
..
..
..
end of atomic operations
interrupts();

But you forgot to add the interrupts() call.  So interrupts didn't get re-enabled, and now you are debugging why that is the case.  If it is code that is called intermittently, or under certain conditions, it can be very hard to debug.

With ATOMIC_BLOCK() there's still the possibility that you've got too much code in your block, or not all the code that needs to be atomic, but it will always either re-enable interrupts, or restore them to the state they were in prior to the atomic block.  If you don't close the block, you get a compile error.
Logged


Pages: [1]   Go Up
Jump to: