“Volatile” can be harmful…

What could be wrong with this code:

volatile uint16_t myDelay;

void wait(uint16_t time) {
  myDelay = 0;
  while (myDelay<time) {
    /* wait .... */
  }
}

void TimerInterrupt(void) {
  myDelay++;
}

?

Obviously, given this post title and the usage of volatile in above source, it is about a C/C++ keyword which is very important for every embedded systems programmer.

Volatile Keyword and Type Qualifier

The volatile is a reserved word (‘keyword’) in C/C++. It it is a ‘type qualifier’. It means that it is added as an extra attribute or qualifier to the type, similar to const. The following is a typical example:

volatile int flags;

So this is a variable named ‘flags’ of type ‘int’, and it is marked as ‘volatile’.

Compiler, don’t be smart!

‘Volatile’ means the variable might change all the time. And this is exactly what it tells to the compiler:

  1. The variable or memory location can change even without an access from the program code.
  2. Reading or writing to that memory location might cause side effects: it might trigger changes in other memory locations, or might change the variable itself.

Consequently, this tells the compiler not to be smart and not to optimize accesses to the variable.

Volatile for Peripheral Registers

A typical usage of is for volatile peripheral registers, e.g. for a variable which maps to a timer register:

extern volatile uint16_t TIMER_A; /* Timer A counter register */

Another way to use such a timer register is

#define TIMER_A (*((volatile uint16_t*)0xAA)) /* Timer A counter register at address 0xAA */

which is casting the integer 0xAA as a pointer to a volatile 16bit value.

Used like this

timerVal = TIMER_A;

will read the content of the timer register.

The volatile makes sure that the compiler does not transform this code

TIMER_A = 0;
while (TIMER_A == 0) {
  /* wait until timer gets incremented */
}

into an endless loop. The compile knows that the variable might change, and is performing ‘dumb’ instructions, not keeping the variable in a register or optimizing the access.

The volatile has the effect that it will not optimize a ‘read only’ access to a variable:

TIMER_A; /* read access to register, not optimized because volatile */

Without the volatile in the type, the compiler could remove the access.

❗ While using C language for this kind of accesses might be fine, I recommend using assembly or inline assembly for when the hardware requires a very specific access method.

Keep in mind that volatile is qualifier to the type. So

volatile int *ptr;

is a pointer to a volatile int, while

int *volatile ptr;

is a volatile pointer to a (normal) int.

Volatile for Local Variables

volatile can be useful for local variables or parameters too:

void foo(volatile int param) {
  ...
}

or as in this function:

void bar(int flags) {
  volatile int tmp;
  ...
}

As outlined above, the compiler will not optimize it. There are two use cases for this:

  1. Making the code ‘easier to debug’: if you are not sure about what the compiler does, using volatile will make sure that the compiler generates simple code, so it might be easier to follow the code sequence and to debug a (user code) problem. Of course you might want to remove the volatile afterwards.
  2. Workaround for a compiler problem: Compilers might optimize things too much, that the code is wrong :-(. In that case, volatile should trick out the optimization, and can be used as a workaround :-).

Volatile and Interrupts

Because volatile informs the compiler that the variable can be changed, it is a perfect way to mark shared variables between interrupt functions and the main program:

static volatile bool dataSentFlag = FALSE;

void myTxInterrupt(void) {
  ...
  dataSentFlag = TRUE;
  ...
}

void main(void) {
  ...
  dataSentFlag = FALSE;
  TxData(); /* send data, will raise myTxInterrupt() */
  while(!dataSentFlag) {
    /* wait until interrupt sets flag */
  }
  ...
}

While usage of volatile is perfect here, there is a general misconception about volatile: it does NOT ❗ guarantee reentrant access to the variable!

The above example can be considered not exposing a problem if the microcontroller reads and writes the shared variable in an atomic way.

💡 ‘Atomic’ means that the access to the variable is in ‘one piece’, and cannot be interrupted or performed in multiple steps which can be interrupted.

This might not be the case for the following example which counts the number of transmitted bytes:

static volatile uint32_t nofTxBytes = 0;

void myTxInterrupt(void) {
  ...
  nofTxBytes++;
  ...
}

void main(void) {
  ...
  nofTxBytes = 0;
  TxData(); /* send data, will raise myTxInterrupt() */
  while(nofTxBytes < nofDataSent) { /* compare against how much we sent */
    /* wait until transaction is done */
  }
  ...
}

It now all depends on the microcontroller and bus architecture what happens. The access and usage of nofTxBytes is not atomic any more, resulting in likely wrong runtime behaviour. To avoid race conditions, access to the shared variable needs to be protected with a critical section.

💡 Critical sections can be easily implemented with disabling and re-enabling interrrupts. Processor Expert generates the EnterCritical() and ExitCritical() macros which disables and re-enables interrupts. These macros have the advantage that the interrupt state gets preserved. Keep in mind that the EnterCritical() and ExitCritical() macros cannot be nested!

Serializing Memory Access

Things can get even more complicated. Sometimes it is necessary to understand the underlying bus accesses of the microcontroller. An article I was reading recently is one about Serialization of Memory Operations and Events: Just doing a write in my code does not mean that the write is effective immediately! Because the way how the bus works, and because of caching and wait states, the write can happen way later than I would expect it. So if I have a register write, and based on that write I need to read something else (dependency of write to read), I might not get the result I expect because of the bus cycles. Instead, I need to do a read of the register to enforce the writing (‘serialize the memory access’):

  1. Write to the register
  2. Immediately read the register again to serialize the memory access

Without doing so, subtle timing problems might occur.

ARM Memory Barrier Instructions

Below is the FreeRTOS source which creates a context switch within an interrupt service routine:

void vPortYieldFromISR(void) {
  /* Set a PendSV to request a context switch. */
  *(portNVIC_INT_CTRL) = portNVIC_PENDSVSET_BIT;
  /* Barriers are normally not required but do ensure the code is completely
     within the specified behavior for the architecture. */
  __asm volatile("dsb");
  __asm volatile("isb");
}

Notice the two assembly instructions at the end: dsb (data synchronization barrier) and isb (instruction synchronization barrier). They ensure that data and instructions get serialized. See this ARM Infocenter article for details.

Critical Section

Coming back to the example at the beginning, what could be one solution is this:

volatile uint16_t myDelay;

void wait(uint16_t time) {
  uint16_t tmp;

  EnterCritical();
  myDelay = 0;
  ExitCritical();
  do {
    EnterCritical();
    tmp = myDelay();
    ExitCritical();
  } while(tmp<time);
}

void TimerInterrupt(void) {
  myDelay++;
}

It assumes that

  1. EnterCritical() and ExitCritcal() build a critical section, e.g. with disabling and re-enabling interrupts.
  2. TimerInterrupt() itself is not interrupted (has highest priority, or no nested interrupts).
  3. The interrupt is not so fast that it would immediately overflow the counter.

Summary

The volatile keyword tells the compiler not to be smart about a memory access. Basically this avoids compiler optimization and is used to mark peripheral registers having side effects. On modern microprocessors, volatile alone is not enough to guarantee serialization or to make sure re-entrant access to memory. Only using volatile alone can be harmful: Additional measures like read-after-write, memory barriers or disabling interrupts is necessary.

Happy Volatiling 🙂

12 thoughts on ““Volatile” can be harmful…

  1. Do you a code example for the following:

    “While using C language for this kind of accesses might be fine, I recommend using assembly or inline assembly for when the hardware requires a very specific access method.”

    is Enter/Exit Critical enough, do we need to consider using __asm volatile(“dsb”); and
    __asm volatile(“isb”); ?

    Thanks

    Like

    • Hi Andy,
      good question! Reading the documenation on the ARM site let me believe that the memory barrier instructions are needed for proper execution of the code. I stumbled on this while porting FreeRTOS 7.5.0, and it seems that it *might* work without them, but there can be cases where they are needed. Especially if a context switch or an interrupt can happen right after the ExitCritical().

      Like

      • Yes, that example in your link is not accurate or misleading.
        I disagree that in the first part the compiler will optimize it (if (x == 0) // This condition is always true), because that variable is a global one and might be changed by an interrupt. I get the idea, but that example is not a good one 😦

        Like

  2. I have had numerous encounters where the volatile modifier solved problems for me, this is an elaborate explanation. I find it very insightful. Thanks for sharing 🙂

    Like

    • Yes, me, too. Very informative. I pulled my hair out about 6 years ago on an S08QG8 chip trying to get interrupt driven software UART working reliably. I bet volatile was related to making that work.

      Like

  3. In the last example if in the TimertInterrupt() routine there are other data elaboration these remain locked. Is better to use a semaphore system that lock only the variables that we want?

    EXAMPLE:
    volatile uint16_t myDelay;
    uint8_t sem_myDelay = UNLOCK;

    void wait(uint16_t time) {
    uint16_t tmp;
    myDelay = 0;
    while(sem_myDelay ==LOCK) { asm volatile(“nop”); } // wait for semaphore free
    sem_myDelay =LOCK;
    myDelay = 0;
    sem_myDelay =UNLOCK;

    do {
    while(sem_myDelay ==LOCK) { asm volatile(“nop”); } // wait UNLOCK semaphore
    sem_myDelay =LOCK;
    tmp = myDelay;
    sem_myDelay =UNLOCK;
    } while(tmp<time);
    }

    void TimerInterrupt(void) {
    if( flag_myDelay ==UNLOCK) myDelay++;

    // other data elaboration
    }

    What do you think?

    Like

    • Hi,
      not sure where flag_myDelay is defined, but I think that one should be volatile.
      And I would count down the value in the timer interrupt.
      To your question: yes, if you need to access such a thing, you need a semaphore to be consistent.

      Like

      • Hi,
        yes the variable flag_myDelay is defined volatile, and the interrupt was wrong, it must be :

        void TimerInterrupt(void) {
        if( flag_myDelay ==UNLOCK) myDelay–;

        // other data elaboration
        }

        Thanks Erich

        Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.