When and How to use the Volatile Keyword (Embedded C Programming)

Started by stephaneb 2 weeks ago7 replieslatest reply 5 days ago2359 views

A few weeks ago, we did the Embedded Programming Good and Bad Programming Habits FAQ.

One thing that came up a couple of times in your contributions is the importance of understanding when and how to use the volatile keyword.  I thought it would make sense to create a new #FAQ with this very topic.  

So hopefully, once you guys are done sharing your insights here, this thread will have become a great resource for embedded systems programmers to gain a better understanding of when and how to use the volatile keyword.

And again, $150 will be shared between the authors of the best contributions to the discussion, based on the number of thumbs-ups awarded.

Thank you!

Edit: Some code examples would be great!  (here is an example where the volatile keyword should- or shouldn't - be used...).

[ - ]
Reply by jorickNovember 30, 2017

First, a definition of "volatile" is needed. A volatile variable is one that can change unexpectedly. Consequently, the compiler can make no assumptions about the value of the variable. In particular, the optimizer must be careful to reload the variable every time it's used instead of holding a copy in a register. Examples of volatile variables are: 

Non-automatic variables referenced within an interrupt service routine

This is probably the most obvious use of the volatile keyword. A global or static variable used in an interrupt will appear to change unexpectedly at the task level, so the volatile keyword is needed to inform the compiler that this will happen.

But at the interrupt level, the variable may not have to be treated as volatile if a higher level interrupt won't be changing the value. If so, the variable value can be read into an automatic variable, any operations on it performed, and the updated value stored back into the variable before returning from the interrupt. This will reduce code size and execution time in the interrupt service routine.

Variables shared by multiple tasks in a multi-threaded application

This is probably the most misused of all the items here. Developers usually don't think to add the volatile keyword to a global variable because "The variable isn't used in an interrupt so I don't need it here." The result can be pandemonium as the variable gets written and then reset seemingly at random as another task writes a previous value to it. 

With variables that are written by only one task and read by multiple others, instead of declaring the variable volatile, it may be better to use a lock semaphore to gain exclusive access until the writing task is finished with it.

Hardware registers in peripherals (for example, status registers)

This is another place either missed by developers or misused by declaring an entire structure of hardware registers as volatile, the latter being the most prevalent (at least to me).

When adding hardware register definitions to an application, each register should be examined. If even one bit can change unexpectedly, the entire register must be declared volatile.  The register must also be declared volatile if it's write only since the optimizer may optimize out the first write in a two-write operation, and optimize out all writes when a subsequent read isn't done. So what registers don't need to be declared volatile? Configuration registers where settings are written and not expected to change.

Delay loop variables in a highly optimized function

Although an RTOS should be used to perform delays, shorter delays on the order of microseconds need to be in a loop with a counter. The compiler will optimize the loop away if it sees that no useful operations are contained within it. Declaring the loop counter volatile will cause the compiler to leave the loop in the code.

Variables that shouldn’t be optimized out if not used

Probably the biggest use of these kind of variables is for debugging. Set a variable to a value at certain points of the code and then see what the value is in the debugger when the program fails. Like the delay loop variables, declaring them volatile will keep them in the code.

[ - ]
Reply by MatthewEshlemanNovember 30, 2017

The volatile keyword is certainly important for any embedded software or firmware engineer to understand. For many years this was a key question I would ask during interviews.

The volatile keyword simply directs the compiler to perform no optimizations when reading or writing to the variable/object in question. It is great for use with microcontroller or other hardware registers, where the value of the register changes outside the control of the compiled application running on the microcontroller. General rule of thumb: use it always with hardware register access.

Partial quoting

"Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution, see std::memory_order). Any attempt to refer to a volatile object through a non-volatile glvalue (e.g. through a reference or pointer to non-volatile type) results in undefined behavior."

The volatile keyword does not help with multithreaded issues, although is often used in such a manner. Sometimes using the volatile keyword does accidentally "fix" thread issues, and very much depends upon the environment, compiler, etc. 

If trying to solve a multithreaded issue, use instead the new "atomic" types in C11 and C++11. Do not forget to use an appropriate RTOS object (typically a mutex) if the operation in question impacts multiple variables which must be updated together in a thread safe manner. And don't forget that even if your system is not using a RTOS, any interrupt service routine can potentially induce the equivalent of multithreaded design issues.

It certainly is a volatile world these days! Good luck to all!

Matthew

https://covemountainsoftware.com/consulting/



[ - ]
Reply by jms_nhDecember 3, 2017

Other answers have done a very good job explaining why the volatile keyword should be used. Here are some important notes about how to use the volatile keyword.

The volatile keyword, like const, is a qualifier; in fact these are known as cv-qualifiers ("c" = const, "v" = volatile), and they are used in the same ways:

- in both C and C++, as a qualification for a type, for example:

typedef volatile uint16_t VU16;      // (1)
VU16 v,                              // (2)
    *pv;                             // (3)
void inc_register_twice(VU16 *preg)  // (4)
{
  ++*preg;
  ++*preg; 
}

In use (1) we have a type definition: VU16 is equivalent to volatile uint16_t

In uses (2) and (3) we have two variable declarations that allocate storage to a volatile uint16_t variable and a pointer to a volatile uint16_t. In use (3), the volatile qualifier refers to the thing being pointed to, not to the pointer itself.

In use (4) we have a function definition; the function signature tells the compiler to treat its argument as if it were a pointer to a volatile uint16_t. (In this case the compiler will not optimize the two increments together, because it has to assume that there may be some side effect elsewhere that modifies *preg in between the two increment operations shown.) The "as if it were" is important. Like const, it is safe to add a volatile qualifier but unsafe to take it away; I can do this:

uint16_t x;
void some_function()
{
  pv = x;
  x = 0;
  inc_register_twice(&x);
}

and even though x is just a regular uint16_t variable, when the compiler accesses it through pv or the inc_register_twice function, this access is via a pointer-to-volatile-uint16_t, so the compiler treats it as though it cannot assume the value will remain unchanged by some outside mechanism, for the purposes of optimization.

This is important: if you create a library function to do something, and you want people to be able to use it on hardware registers, you need to remember to use volatile in the function signature, like inc_register_twice. Otherwise the compiler will give you an error.

- In C++, there is an additional usage which is common for const but somewhat rare for volatile, namely as a qualifier for methods in a class:

class Foo
{
   uint16_t some_state;

   void cautious() volatile    // (5)
   {
     ++some_state;
     ++some_state;
   }
};

This has the affect of applying volatile to the implicit this pointer, and the compiler essentially inhibits any optimizations that assume that member state does not change between accesses within the function.

[ - ]
Reply by Tim WescottDecember 4, 2017

Since you mention const, allow me to point out that

const volatile int foo;

does not mean that foo is a volatile integer that never changes -- it means it's a volatile integer that you are not allowed to write to.

It's sometimes handy to use this for read-only I/O registers, or to refer to a piece of memory that's "owned" by some exclusive writing process but referred to by 'outside' processes that might read it.

Contrast this with 

static const int bar = 42;

which says "bar is now and forevermore equal to 42, and moreover it's local to this file".  If you never take the address of bar, and you're using a decent optimizing compiler, it'll never actually appear in memory -- the compiler will just it the same as if you'd used a #define statement, only with better type checking.  (And if you do take it's address, the compiler will do the right thing, in the background, seamlessly and painlessly).

[ - ]
Reply by sudeep_mohantyDecember 1, 2017

Just adding to the great summary so far as to why we have to use the volatile keyword in developing embedded systems -

The compiler obviously does not optimize any variable declared as volatile. But here, I would like to take a step back and would want to put in a case for the consequences of over usage of it.

The volatile keyword forces the compiler to not store a copy of the variable in the registers and fetch it each time from memory. Memory accesses are rather slow and hence can lead to "degraded" performance on an embedded system if performed repeatedly, especially in very timing critical applications. In my view, before using volatile, any embedded systems programmer should consider these:

  • Do you really need to use volatile here? (The answer is mostly yes if you are even thinking about volatile!)
  • If you answer yes to the previous question then ask - How often is this code going to run? (A profiling and timing analysis is in order)

To conclude - any embedded/firmware programmer should never use volatile liberally. It may look like that volatile makes your life really easy when dealing with entities outside of the MCU but it may also be hampering your system in a way you may not have comprehended in the first go. Do a thorough profiling of the code. At some point, volatile is absolutely mandatory, but make a case for using it to yourself as to why you need it and what are the consequences of doing so, if any.


Sudeep

[ - ]
Reply by LarryADecember 1, 2017

Excellent explanations by all posters, thank you all. The volatile keyword is very important when using optimizing compilers. I do not work with any RTOS's in my embedded designs (yet!) so multithreading is not anything I need to concern myself with, but using them for any variable updated within an interrupt is an absolute ... Good point about also using it for hardware registers, my compiler include files already do this but I will keep that in mind as everything always changes, it's a volatile workspace :-)

Larry Affelt

[ - ]
Reply by alphajbDecember 7, 2017

Others have done a good job of explaining the value of 'volatile' to ensure that the compiler generates correct, and correctly ordered accesses to hardware registers.  However, it's worth noting that an optimizing compiler isn't the only reason that accesses to memory or peripherals may not happen in the way that you intended--some hardware platforms do not guarantee that memory accesses will happen in the same order or in the same quantity as indicated by the issued program instructions, so even if the assembled program is correct it may still be executed incorrectly when it comes to memory accesses.  This is the case even in the ARM Cortex M series, although the details depend on the particular implementation.

The default memory map for the system will usually ensure correct operation (assuming a correct program) for internal registers or other hardware control locations, so this is most likely to be a problem when accessing external peripherals via a memory-mapped interface, for example a parallel LCD connected to an external memory bus.  In such an application, writes to the peripheral may be captured by a cache and never appear on the external interface at all, or they may be reordered by the memory system and happen later or earlier than expected, or repetitive writes may be consolidated with only the latest one actually appearing at the interface, or writes may be repeated on return from an exception.  In some cases, it may be possible to use memory barrier instructions to ensure correct operation, but in others it will be necessary to configure the address range of the peripheral as non-cacheable and/or in-order through the MMU, with the exact configuration details depending on the implementation.