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 toconst
. 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:
- The variable or memory location can change even without an access from the program code.
- 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 codeTIMER_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:
- 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 thevolatile
afterwards.- 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 aboutvolatile
: 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’):
- Write to the register
- 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
EnterCritical()
andExitCritcal()
build a critical section, e.g. with disabling and re-enabling interrupts.TimerInterrupt()
itself is not interrupted (has highest priority, or no nested interrupts).- 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 usingvolatile
alone can be harmful: Additional measures like read-after-write, memory barriers or disabling interrupts is necessary.Happy Volatiling 🙂
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
LikeLike
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().
LikeLike
If we do not use volatile qualifier the following problems may arise:
Code that works fine-until you turn optimization on
Code that works fine-as long as interrupts are disabled
Flaky hardware drivers
Tasks that work fine in isolation-yet crash when another task is enabled
Here i find same article They might wrong
http://www.firmcodes.com/volatile-keyword-in-c-and-embedded-system/
LikeLike
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 😦
LikeLike
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 🙂
LikeLike
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.
LikeLike
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?
LikeLike
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.
LikeLike
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
LikeLike
#define TIMER_A (*((volatile uint16_t*)0xAA)), is it as *(uint16 *const p) ?
LikeLike
Hi Jose,
no, it is not the same. The first is a constant pointer to a volatile 16bit timer, while the second is a normal constant pointer to a 16bit value. The code from both might be the same, but the first with volatile properly references a hardware which is ‘volatile’.
LikeLike
Thank you!
LikeLike