Arduino Stack Exchange is a question and answer site for developers of open-source hardware and software that is compatible with Arduino. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I am familiar with the keyword Volatile being used to declare variables that are shared among multiple threads on a software application (basically on a multithreaded application).

But why do I need to declare a variable as Volatile when running the code on an an Arduino interrupt? Is the code running in the interrupt executing on different thread? Is there something else going on?

share|improve this question

migrated from electronics.stackexchange.com Feb 22 at 21:35

This question came from our site for electronics and electrical engineering professionals, students, and enthusiasts.

    
You have to tell the compiler that the variable exists so that optimization algorithms don't remove it entirely from your executable. – Scott Seidman Feb 22 at 21:29
    
"I am familiar with the keyword Volatile". Then forget about it, and read again. – Eugene Sh. Feb 22 at 21:29
2  
Effectively the interrupt handler is a separate thread from main. Without the volatile declaration, compiler does not know that main must re-read the global (which may have been changed by the interrupt handler). – MarkU Feb 22 at 21:33
    
Is the code running in the interrupt executing on different thread - well sort of, only the opposite. Interrupts are the mechanism, the technique used by OS programmers, to implement threads. So we need to use volatile in multithreaded code because their context has been switched by the OS in an interrupt. We need to use volatile in interrupts simply because interrupts run in a different context. – slebetman Feb 23 at 2:18

volatile has nothing to do with multithreading per se, and it seems to be among the most miused keywords in that area. It is never a substitute for proper synchronization.

As per standard C, volatile invokes mostly implementation defined behaviour of telling the compiler to not optimize away accesses to the memory a variable resides in, even if the compiler thinks there can't be any other.

This can be useful in the following situations where the compiler is not aware that outside means can change a variable:

  • memory mapped hardware registers
  • variables accessed from other threads but checked in a tight loop
  • variables accessed from interrupt or signal handlers but checked in a tight loop
share|improve this answer

First, it's volatile not Volatile. I cover these concepts in my page about Interrupts however to avoid giving a link-only answer I'll repeat the relevant bits.


What are "volatile" variables?

Variables shared between ISR functions and normal functions should be declared volatile. This tells the compiler that such variables might change at any time, and thus the compiler must reload the variable whenever you reference it, rather than relying upon a copy it might have in a processor register.

For example:

volatile boolean flag;

// Interrupt Service Routine (ISR)
void isr ()
{
 flag = true;
}  // end of isr

void setup ()
{
  attachInterrupt (0, isr, CHANGE);  // attach interrupt handler
}  // end of setup

void loop ()
{
  if (flag)
    {
    // interrupt has occurred
    }
}  // end of loop

Critical sections ... accessing volatile variables

There are some subtle issues regarding variables which are shared between interrupt service routines (ISRs) and the main code (that is, the code not in an ISR).

Since an ISR can fire at any time when interrupts are enabled, you need to be cautious about accessing such shared variables, as they may be being updated at the very moment you access them.


First ... when do you use "volatile" variables?

A variable should only be marked volatile if it is used both inside an ISR, and outside one.

  • Variables only used outside an ISR should not be volatile.
  • Variables only used inside an ISR should not be volatile.
  • Variables used both inside and outside an ISR should be volatile.

eg.

volatile int counter;

Marking a variable as volatile tells the compiler to not "cache" the variable's contents into a processor register, but always read it from memory, when needed. This may slow down processing, which is why you don't just make every variable volatile, when not needed.


Atomic access

Consider this code:

volatile byte count;

ISR (TIMER1_OVF_vect)
  {
  count = 10;
  }

void setup ()
  {
  }

void loop () 
  {
  count++;
  }

The code generated for count++ (add 1 to count) is this:

 14c:   80 91 00 02     lds r24, 0x0200
 150:   8f 5f           subi    r24, 0xFF     <<---- problem if interrupt occurs before this is executed
 152:   80 93 00 02     sts 0x0200, r24   <<---- problem if interrupt occurs before this is executed

(Note that it adds 1 by subtracting -1)

There are two danger points here, as marked. If the interrupt fires after the lds (load register 24 with the variable count), but before the sts (store back into the variable count) then the variable might be changed by the ISR (TIMER1_OVF_vect), however this change is now lost, because the variable in the register was used instead.

We need to protect the shared variable by turning off interrupts briefly, like this:

volatile byte count;

ISR (TIMER1_OVF_vect)
  {
  count = 10;
  }  // end of TIMER1_OVF_vect

void setup ()
  {
  }  // end of setup

void loop () 
  {
  noInterrupts ();
  count++;
  interrupts ();
  } // end of loop

Now the update of count is done "atomically" ... that is, it cannot be interrupted.


Multi-byte variables

Let's make count a 2-byte variable, and see the other problem:

volatile unsigned int count;

ISR (TIMER1_OVF_vect)
  {
  count++;
  } // end of TIMER1_OVF_vect

void setup ()
  {
  pinMode (13, OUTPUT);
  }  // end of setup

void loop () 
  {
  if (count > 20)
     digitalWrite (13, HIGH);
  } // end of loop

OK, we are not changing count any more, so is there still a problem? Sadly, yes. Let's look at the generated code for the "if" statement:

 172:   80 91 10 02     lds r24, 0x0210
 176:   90 91 11 02     lds r25, 0x0211  <<---- problem if interrupt occurs before this is executed
 17a:   45 97           sbiw    r24, 0x15   
 17c:   50 f0           brcs    .+20        

Imagine that count was 0xFFFF and was about to "wrap around" back to zero. We load 0xFF into one register, but before we load the second 0xFF the variable changes to 0x00. Now we think count is 0x00FF which is neither the value it had before (0xFFFF) or now (0x0000).

So again we have to "protect" the access to the shared variable, like this:

void loop () 
  {
  noInterrupts ();
  if (count > 20)
     digitalWrite (13, HIGH);
  interrupts ();
  }  // end of loop

What if you are not sure interrupts are on or off?

There is a final "gotcha" here. What if interrupts might be off already? Then turning them back on afterwards is a Bad Idea.

In that case you need to save the processor status register like this:

unsigned int getCount ()
  {
  unsigned int c;
  byte oldSREG = SREG;   // remember if interrupts are on or off

  noInterrupts ();   // turn interrupts off
  c = count;         // access the shared variable
  SREG = oldSREG;    // turn interrupts back on, if they were on before

  return c;          // return copy of shared variable
  }  // end of getCount

This "safe" code saves the current status of interrupts, turns them off (they may already be off), gets the shared variable into a temporary variable, turns interrupts back on - if they were on when we entered the function - and then returns the copy of the shared variable.


Summary

Code may "appear to work" even if you don't take the above precautions. That is because the chances of the interrupt occurring at the exact wrong moment is fairly low (maybe 1 in 100, depending on the code size). But sooner or later it will happen at the wrong moment, and your code will either crash, or occasionally return the wrong results.

So for reliable code, pay attention to protecting access to shared variables.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.