Using Volatile keyword in embedded code

Volatile is probably least documented keyword in most tutorials and books. Probably this is main cause of most misuses and bugs related to it. If you already are programming microcontrollers, you probably know that volatile is always used on global variables that are accessed from interrupt service routines. Otherwise code won’t work.

volatile

After few requests I decided to drop few lines about volatile keyword. This keyword is commonly used to tag memory type. We hear “volatile memory”, “non-volatile memory” when talking about computer hardware. As quick reminder – “non-volatile memory” is type of memory that stores its contents even when power is off. Such type of memory is EEPROM, Flash, FRAM. This is easy from hardware perspective. But what volatile keyword means in C or C++ code? This is an indicator (called qualifier) to compiler that tells that this variable may be changed during program flow even if it doesn’t look like to be. This means that compiler must treat this value seriously and keep optimizer away from it.

Declaring volatile variable

As you already may know, volatile variable is declared in simple sentence:

volatile uint8_t variable;

this is also same:

uint8_t volatile variable;

If you need to use pointers to volatile data then use following declaration:

volatile uint8_t * variable;

or:

uint8_t volatile * variable;

There are more ways of using volatile variable like volatile pointer to volatile data. But this is very rare case so we wont go in to that.

It doesn’t have to be a volatile variable. If you need you can declare structures and unions as volatile. Or in other case declare specific members of structure. It all depends on your program design.

Where to use volatile variables

Ideally you may think, why not use volatile everywhere if it is safe. Matter of fact is that your code would work fine with all volatiles, but it would be inefficient with lots of overhead. Hopefully you noticed the situation when you write a code and compile it without optimization “-O0” it works fine without any volatiles. This means that code is compiled with all overheads it takes much more space and may be executed much slower. But once you include some level of optimization code stops working and as a beginner you may start wondering whats a problem – code looks fine as there is no logical errors and so on. Your interrupts don’t do what they are supposed to do, you get in to infinite loops, etc. This happens because compiler optimizer does some initial code analyze and finds that variable is once initialized and doesn’t seem to be changed. Optimizer isn’t that smart to predict interrupt behavior or other event like RTOS task. So compiler optimizes the variable out leaving it with predefined value and may not check it’s contents even if you would expect it to.

In that case declaring such variable as volatile rings the bell to optimizer not to optimize it. Lets do a quick example of the problem.

Case 1:

 

#include <avr/io.h>
uint8_t i;
int main(void)
{
	//while loop
	while(1)
	{
			for(i=2; i<255; i++)
			{
				//empty
			}
	}
}

 

In following program we simply a for loop where we use global variable i as counter. For this case I’ve chosen compiled program without optimization (-O0).

If we open list file (.lss) of compiled program you can see that while(1) loop does what it has to:

98: 82 e0 ldi r24, 0x02 ; 2

9a: 80 93 00 01 sts 0x0100, r24

9e: 05 c0 rjmp .+10 ; 0xaa <main+0x1a>

a0: 80 91 00 01 lds r24, 0x0100

a4: 8f 5f subi r24, 0xFF ; 255

a6: 80 93 00 01 sts 0x0100, r24

aa: 80 91 00 01 lds r24, 0x0100

ae: 8f 3f cpi r24, 0xFF ; 255

b0: b9 f7 brne .-18 ; 0xa0 <main+0x10>

 

It loads counter value (address 0x0100) and compares it with 255. So non optimized code works fine but is takes much code space. Unoptimized code is only recommended for debugging purposes.

Case 2:

Now lets leave same program but simply change optimization to “-Os” and compile. Now our program shouldn’t work. To understand the reason lets open same list file and look at for loop:

90: ff cf rjmp .-2 ; 0x90 <main>

No surprise – the code is completely optimized out. optimizer assumes that for running loop is useless as variable i isn’t used anywhere else. So it took loop code part completely out.

Case 3:

And now lets make i variable volatile.

volatile uint8_ti;

Leave optimization “-Os” and compile. After this open list file to see how it looks:

00000090 <main>:

90: 92 e0 ldi r25, 0x02 ; 2

92: 90 93 00 01 sts 0x0100, r25

96: 05 c0 rjmp .+10 ; 0xa2 <main+0x12>

98: 80 91 00 01 lds r24, 0x0100

9c: 8f 5f subi r24, 0xFF ; 255

9e: 80 93 00 01 sts 0x0100, r24

a2: 80 91 00 01 lds r24, 0x0100

a6: 8f 3f cpi r24, 0xFF ; 255

a8: a1 f3 breq .-24 ; 0x92 <main+0x2>

aa: f6 cf rjmp .-20 ; 0x98 <main+0x8>

Now we can see that for loop is up and running.

This simple code demonstrates the benefit of using volatile variables. Same rule applies when variable is accessed from interrupt service routine (ISR). So as rule of thumb – always make variables volatile if they are modified from ISR. Generally speaking we can make a small list of where volatile is required:

  • global variables modified from ISR;
  • variables for empty delay loops – rather use better solution;
  • global variables used in multitask applications – RTOS;
  • peripheral registers variables.

As for peripheral registers this is somewhat what you normally use but not notice. Lets open atmega328p header file from avr toolchain. You can find that, for instance, PORTD is defined as:

#define PORTB _SFR_IO8(0x05)

lets dig deeper and find how SFR_IO8() is defined. For this open sfr_defs.h header file and locate:

#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)

where

#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))

so in the end we get that PORTB is defined as:

#define PORTB (*(volatile uint8_t)*)(0x18 + 0x20)

So compiler treats all register labels as volatile that point to specific location in SRAM. This way compiler is forced to write or read specific RAM location in any case.

In the end, if you start thinking of making all variables volatile and be safe – don’t. This is not the way out of problems. First of all your program becomes less efficient as optimizer is left outside. As you know optimizers are pretty good these days – so give them some work. It is better to understand why you need a volatile instead of putting it blindly.

 

Bookmark the permalink.

9 Comments

  1. ОГРОМНОЕ СПАСИБО ! )))
    many thanks !!! )))

  2. Thank you for a clear explanation about volatile variables. I’m int the learning curve from PIC to AVR, and your explanations ae very helpfull! Keep the good working.

  3. Hi,
    I have one question about volatile for operations with I/O Ports. I don´t know if must be variable for reading PINA, or set PORTA volatile, or not. For example:

    uint8_t x;
    x = PINA;
    PORTB = x;

    or must be used:

    volatile uint8_t x;
    x = PINA;
    PORTB = x;

    Is some difference between optimization Os, O1 for this cases?
    Thank you for your reply

  4. Lets run these codes to see:

    uint8_t x;
    x = PINC;
    PORTB = x;

    same results for both optimizations
    -Os and -O1

    in r24, 0x06
    sts 0x0100, r24
    out 0x05, r24

    and with volatile:

    volatile uint8_t x;
    x = PINC;
    PORTB = x;

    same results for both optimizations
    -Os and -O1

    in r24, 0x06
    sts 0x0100, r24
    lds r24, 0x0100
    out 0x05, r24

    As you can see without volatile value is read to register r24 which then is stored to SRAM address 0x0100, but when setting PORTB the value is taken directly from register r24.
    If volatile is used, then reading from pin operation is same, but when writing tp port, the value is read from sram to register and then port value is set. Optimization level doesn’t influence the resulting compilation.
    So if you code doesn’t use fancy code which would access registers (eg. during interrupts), then you don’t need volatile, but if you feel, that register value can be modified by other routine just after PINC is read, then before writing to port seems logical to read this value from memory before out.In this case you should use volatile.
    It is hard to advice withotu seeing whole picture what your code does. Just ecperiment ant try to figure out by inspecting generated listing file (.lss).

  5. Thank you for your reply.
    I didn’t know, if is rule, that must be variable volatile, which will work with PORTx (read or write). From your answer I understand, that both versions are correct for using with c language without direct access to registers using assembler.

  6. Both are correct, but version with volatile is not optimal as it adds overhead. If variable is not accessed from ISR or not accessed from RTOS task, don’t use volatile.

  7. I always used volatile just only for variables, which was changed in ISR, but when I read your posts and I saw fourth point when is used volatile “peripheral registers variables”, so I thought that is for variables which works with I/O Ports.
    Thanks

  8. i want more explanation…

Leave a Reply

Your email address will not be published. Required fields are marked *