A deep understanding of the volatile keyword is one of the fundamental pieces of knowledge needed for embedded systems programming. Embedded programmers constantly deal with hardware, interrupts, RTOS, registers etc., which require volatile variables. Let’s take a deep look at the volatile type qualifier.
The Volatile keyword
The volatile keyword is a type qualifier used to instruct the compiler not to invoke any optimizations on the variable which are declared volatile. This is due to the fact that volatile variables can change unexpectedly and the compiler should not make any assumptions about the value of the variable.
For eg., if the compiler sees a write to a volatile location followed by another write to the same location, it should not assume that the first write in an unnecessary use of processor time. The optimizer should treat the variable as though its behavior cannot be predicted at compile time. In this case, the value of the variable must be reloaded every time instead of holding a copy in a register.
Examples of common volatile variables found in embedded systems are
Memory-mapped peripheral registers
Non-stack variables referenced within an Interrupt Service Routine
Global variables shared by multiple tasks in an RTOS multithreaded application.
Syntax of Volatile Keyword
Volatile can appear either as declaration specifiers or in declarators just like the const
keyword. Following are the different ways to use the volatile keyword
Declaration Type | Example Syntax | Explanation |
Volatile Data | uint8_t volatile t_temperature; or volatile uint8_t temperature; | This declares the variable t_temperature which is of type uint8_t as volatile. This means that the value of t_temperature can change unexpectedly. |
Non-volatile pointer to volatile data | uint8_t volatile pStatusReg or volatile uint8_t pStatusReg; | The data of type uint8_t pointed by the pointer \pStatusReg is subject to change without notice. The value of the variable pStatusReg* will remain same for the rest of the program. |
Volatile pointer to non-volatile data | uint8_t \volatile pStatusReg;* | The pointer \pStatusReg is volatile and can change but the uint8_t value in the memory location pointed by pStatusReg* remains unchanged. |
Volatile pointer to volatile data | uint8_t volatile volatile pStatusReg; or volatile uint8_t volatile pStatusReg; | Both the uint8_t data pointed by the pointer and the pointer \pStatusReg* can change. |
Example Use Case in Memory-Mapped Peripheral Registers
One of the most common use cases of using volatile in embedded systems is for memory-mapped peripheral registers. Let's look at an example of declaring a register called P2LTCH (Port 2 I/O Latch Register) which is memory mapped and located at the physical address 0x7200005E. Every bit in the P2LTCH register is associated with the voltage on one of the I/O pins.
We can declare a pointer to a thirty-two bit unsigned int register and initialize it to the address 0x7200005E like
uint32_t *pP2LTCH = (uint32_t *) 0x7200005E;
One important difference between a device register and an ordinary variable is that the contents of the device register can change without the knowledge or intervention of the program. Hence it is important to warn the compiler about the data pointed to by the P2LTCH register by using the volatile keyword. The right way to declare the register variable would be
uint32_t volatile *pP2LTCH = (uint32_t *) 0x7200005E;
Here the register is defined as a non-volatile pointer to volatile data. The correct interpretation of this is that the variable pP2LTCH will remain fixed at 0x7200005E, though its contents may change without notice.