Scott Craig and Justin Tanner

The volatile Type Qualifier

During development of the RTOS we ran into a nasty bug. When we chose an optimization option in the gcc compiler such as -O2, our OS behaved differently than when compiled without optimization. Upon closer inspection we found that certain C statements were ignored.

The Problem

To illustrate this problem imagine we have a sensor that takes 50ms to read. Code to read this sensor might look like this:

int finished_reading = 0;

void main(void)
{
    init_sensor();

    for(;;)
    {
        finished_reading = 0;
        read_sensor();

        while(finished_reading == 0) {};
    }
}


ISR()
{
    /* read the sensor value */
    finished_reading = 1;
}

The expected behavior of this code inside the for(;;) loop is as follows,

  1. call read_sensor() and enter while() loop
  2. ISR fires, gets the sensor value, and sets finished_reading to 1
  3. the while() loop exits
  4. go back to step one

But the optimization of this code causes a problem: step 3 never occurs. The ISR executes as expected, but the value of finished_reading never gets set to 1.

Upon Closer Inspection

To see what is going on we must go down to the assembly level.

C code

while(finished_reading == 0) {};

AVR RISC assembler

+000004A8:   9180010B    LDS     R24,0x010B    Load direct from data space
+000004AA:   2388        TST     R24           Test for Zero or Minus
+000004AB:   F4A9        BRNE    PC-0x01       Branch if not equal

The first line of assembly gets finished_reading from memory and places it in register 24. The next instruction tests if the value in register 24 is zero. And the last instruction branches back to the previous instruction.

But a problem lies in the last line of assembler. We want the instruction to branch back to the top of the loop. Since GCC thinks the value of finished_reading will never change it jumps back to the second instruction, not the first. The last instruction should read BRNE PC-0x03, not BRNE PC-0x01.

From the compilers perspective the value of finished_reading is set once in main() and never changed. The behavior of the ISR() and how it interacts with the globals used in main() is completely unknown to the compiler. So it is making the right optimization, saving 2 cpu cycles for each loop iteration.

The Solution

The solution to this problem is to change the definition of finished_reading to be volatile.

int volatile finished_reading;

This will change the third instruction to read BRNE PC-0x03, getting a fresh value of finished_reading from its location in memory.

How does volatile work?

A variable declared with a type qualifier volatile, informs the compiler not to optimize references to or modifications of the variable across sequence points, such as the end of statements or expressions in a test. In practice, declaring a variable with the volatile qualifier forces the compiler to produce machine instructions that read or write directly to the memory location of the variable every time the variable is accessed.

In the example above, there are several sequence points between the initial setting of the finished_reading variable, and its occurrence in the loop condition. Under optimization, the compiler noticed that the variable never gets modified between these two points, and so optimized the accesses to it. But the ISR can execute at any time, which the compiler cannot expect. By using volatile, we forced the compiler to put the variable access back in.

Choosing an optimization option in the compiler typically makes the execution of the code faster, at the expense of a larger executable. For example, sections of code may be repeated several times, rather than using a loop variable. Or functions may be copied and placed inline to save the time of a function call. Higher levels of optimization perform even deeper analysis on the code, to skip unnecessary machine instructions. Sometimes, this is not desirable.

In our project, we qualified as volatile every variable used within an ISR. This sometimes makes the code run more slowly than it could, but at least it runs correctly.

More information about volatile can be found here,

The C Book - Const and volatile

And here,

Embedded.com - Introduction to the Volatile Keyword