embedded software boot camp

How to Combine Volatile with Struct

Friday, November 9th, 2012 by Michael Barr

C’s volatile keyword is a qualifier that can be used to declare a variable in such a way that the compiler will never optimize away any of the reads and writes. Though there are several important types of variables to declare volatile, this obscure keyword is especially valuable when you are interacting with hardware peripheral registers and such via memory-mapped I/O.

Sometimes a memory-mapped I/O device could be as simple as just having a single 8-bit control register at a fixed address. In that case, the placement of volatile should be to the left of the * operator in the declaration of the pointer to that address, as in:

uint8_t volatile * p_ledreg = 0x10000000;

In the above code, the variable p_legreg is a pointer to a volatile 8-bit unsigned register located at address 0x10000000.

However, it is far more common that memory-mapped peripherals have at least a half dozen registers. In this more complicated scenario, a C struct can be defined to encapsulate these registers as a set and a pointer to said data structure can be declared. Here’s an example of such a declaration that does not feature the volatile keyword at all:

typedef struct
uint8_t reg1;
uint8_t reg2;
uint8_t _reserved;
uint8_t reg3;

} mydevice_t;

mydevice_t * p_mydevice = 0x10000000;

In this scenario, there are three possible places for the volatile keyword. First, the first line could be modified so that the new type “mydevice_t” always contains the volatile keyword, as in:

typedef volatile struct

Or the last line could be modified so that the pointer “p_mydevice” is a pointer to a volatile mydevice_t:

mydevice_t volatile * p_mydevice = 0x10000000;

Note that the difference between these first two volatile placements is whether all instances of said struct are volatile or only the pointer’s instance is volatile. If there is only one instance of the struct in the whole program and it is the pointer p_mydevice, then that difference is immaterial.

Finally, the third option is to place one or more volatile keywords within the struct definition itself. With this placement, only the specific registers within the struct that are declared volatile will be treated, by the compiler, as subject to asynchronous change. Reads and writes from or to other, non-volatile-declared, registers in the struct may potentially be optimized away. Here’s an example:

typedef struct
uint8_t volatile reg1;
uint8_t volatile reg2;
uint8_t const _reserved;
uint8_t reg3;

mydevice_t * p_mydevice = 0x10000000;

Given that there are multiple choices for the placement of volatile, where is the best place to put the volatile keyword in practice? My preferred placement is typically in the pointer declaration. That way, all of the registers in the struct will be treated, by the compiler, as volatile and yet it is possible to have other (e.g. RAM-based shadows) instances of said struct that are not volatile because they are not actually hardware registers underneath.

Tags: , , ,

8 Responses to “How to Combine Volatile with Struct”

  1. Kristoffer Glembo says:

    I prefer a 4th alternative, having accessor functions that apply the volatile keyword, i.e.

    read8(&p_mydevice->reg1) ;

    static inline uint8_t *read8(const volatile void *addr)
    return *(volatile unsigned uint8_t *)addr;

    This making the code more portable if I need to change the access somehow (byte twisting, memory barriers or whatever).

  2. Andrew Tune says:

    Hi, Shouldn’t there be some Const keywords in the examples, too?

    • Michael Barr says:

      I was above focused only on the placement of volatile. But thank you for reminding us all that each of the pointers in the examples is to a fixed address and should be declared with ‘const’ to the right of the asterisk as in:

      mydevice_t volatile * const p_mydevice = 0x10000000;

  3. David brown says:

    While I agree with you about usually preferring to put the “volatile” in the pointer, it is worth noting a couple of additional things here – including a forth place to put the “volatile”.

    First, one occasionally wants to make a local non-volatile copy of such structures. This is particularly true with bitfield structures – you might want to make a local non-volatile copy, fill in the fields, then copy everything over in one operation. This is much easier to do efficiently if the typedef does not contain “volatile”.

    Secondly, if you have a mixture of volatile and non-volatile registers in the struct, it is vital to remember that the compiler can re-arrange the non-volatile accesses with regard to each other, and with regard to the volatile accesses. A common mistake is to use non-volatile accesses to set up a peripheral, then a volatile access for the master “enable” bit – with the assumption that the other accesses are complete first. In fact the compiler does not have to enforce any ordering of the other accesses.

    The key to understanding “volatile” is to realise that it is not data or registers that are volatile – it is /accesses/ to them that are volatile. With that in mind, it makes sense to tie “volatile” to the pointer, as the pointer is used to access the data. But it is also quite possible to make the accesses themselves explicitly volatile, such as:

    *((volatile uint*) (&(p_mydevice->reg1))) = 0x12;

    This sort of construct puts the “volatile” directly on the access. Of course, it looks a mess, so you want to put use a macro to keep the code clear. But it is very useful if you have something that you sometimes want to access as “volatile”, sometimes with normal accesses.

    • JoeS says:

      Re: “it is very useful if you have something that you sometimes want to access as “volatile”, sometimes with normal accesses”

      Perhaps I lack imagination this early in the morning, but I cannot imagine something that I might sometimes need to access as “volatile” but at other times would be OK to access normally. Can you give an example?

      • Jan Bennett says:

        The need for volatile arises with the possibility of multiple threads/ multiple accesses and/or hardware doing things. This is dynamic in time. So during initialization – when you have ONLY a start-up thread and all interesting hardware is turned off then your volatile worries will seem excessive. Once you turn things on though – start up threads, enable DMA engines and kick your UARTS then multi-access is a real concern. SO answering your question JoeS – during a start-up-just-reset-all-set-buffer_ptrs-etc initialisation code block you may (I wouldn’t, but you may) use non-volatile accesses. The reason I wouldn’t is my lazyness – I wouldn’t want two pieces of code with data to maintain. However it does depend on the system, for in some systems the start up code may be a completely separate unit to that run later when everything is switched on and running. Then you have unavoidable duplication and you may choose to optimise against different goals anyway. (Small,slow boot code, fast,not so small application code etc.).

      • I wrote a stepper motor controller that has a number of timers and counters that are used in various places around the application. The variables are updated only in the interrupt. Outside of the interrupt, I want the variables to have volatile accesses because they may change at any time. Inside the interrupt, I don’t want volatile access because this would lengthen the interrupt processing time. While in the interrupt, I chose to read the value into a local (stack) variable and work with it there. The compiler does an excellent job of optimizing the variable into a register.

    • Lundin says:

      While these comments are good, I do think that the data/registers can be volatile, as well as the access. Because there are even more subtle details. uint8_t volatile * ptr; roughly means “a pointer to volatile data”, while uint8_t * volatile roughly means “a volatile pointer to data”. The latter is probably not useful in many cases, perhaps it would be useful for some such of index register, I can’t remember ever using it myself.

      Though if you want to be absolutely sure, I suppose you could write uint8_t volatile * volatile ptr;

      C is a very weird language…

Leave a Reply