Many of you already know about the use of the C/C++ keyword volatile, but I was having a discussion with a colleague this afternoon and the topic came up. The colleague is a bright up and coming engineer who finds himself writing software, but really wanting to design hardware.
He was telling me about a test that he wrote to estimate the CPU speed of a board. Simple little application, get the current milliseconds using GetTickCount(), then run a busy loop. When the loop completes call GetTickCount() again and calculate the time delta. The code probably looks something like this:
DWORD TimeTheLoop()
{
                DWORD StartTime;
                DWORD Counter;
 
                StartTime = GetTickCount();
 
                for( Counter = 0; Counter < 0x0FFFFFFF; Counter++ )/* Do nothing */;
 
                return StartTime - GetTickCount();
}
But, the first time he ran the test, the delta was zero.   Being a bright engineer, he figured out that if he disabled the optimizer when he compiled the code that the loop would be included and he could time the loop execution. Disabling the optimizer is a big hammer for a small problem though, especially if this little problem was included in a much bigger application.
Let’s start by looking at the assembly code that this produces. If you don’t already have COD files being created when you compile, turn on that feature by setting the environment variable WINCECOD=1. You can do that in your sources file, the platform BAT file, or open a build window and set it on the command line. With WINCECODE set, COD files will be created in the OBJ folder when the code is compiled. The assembly code for this looks like this:
 00000                  |TimeTheLoop| PROC
 
; 5    : {
 
 00000                  |$L37130|
 00000 e92d4010             stmdb       sp!, {r4, lr}
 00004                  |$M37128|
 
; 6    :      DWORD StartTime;
; 7    :      DWORD Counter;
; 8    :
; 9    :      StartTime = GetTickCount();
 
 00004 eb000000             bl          GetTickCount
 00008 e1a04000             mov         r4, r0
 
; 10   :
; 11   :     for( Counter = 0; Counter < 0x0FFFFFFF; Counter++ )/* Do nothing */;
; 12   :
; 13   :     return StartTime - GetTickCount();
 
 0000c   eb000000             bl          GetTickCount
 00010 e0440000             sub         r0, r4, r0
 
; 14   : }
 
 00014 e8bd4010            ldmia       sp!, {r4, lr}
 00018 e12fff1e               bx          lr
 0001c                   |$M37129|
 
                                                 ENDP ; |TimeTheLoop|
What really stands out in this is that the for loop produces absolutely no code. This is where the volatile keyword is very valuable. The volatile keyword tells the optimizer not to optimize access to a variable or memory location.
If we make one small change to the code:
DWORD TimeTheLoop()
{
                DWORD StartTime;
                volatile DWORD Counter; //   < ----- Added the volatile keyword to tell the compiler not to optimize code that accesses this variable
 
                StartTime = GetTickCount();
 
                for( Counter = 0; Counter < 0x0FFFFFFF; Counter++ )/* Do nothing */;
 
                return StartTime - GetTickCount();
}
The for loop now creates some assembly code and therefore will execute when the function is called:
; 10   :
; 11   :     for( Counter = 0; Counter < 0x0FFFFFFF; Counter++ )/* Do nothing */;
 
 0000c   e3a03000             mov         r3, #0
 00010 e58d3000             str         r3, [sp]
 00014 e1a04000             mov         r4, r0
 00018 e59d3000             ldr         r3, [sp]
 0001c   e373021f              cmn         r3, #0x1F, 4
 00020 2a000005             bcs         |$L37094|
 00024                  |$L37092|
 00024 e59d3000             ldr         r3, [sp]
 00028 e2833001             add         r3, r3, #1
 0002c   e58d3000             str         r3, [sp]
 00030 e59d3000             ldr         r3, [sp]
 00034 e373021f              cmn         r3, #0x1F, 4
 00038 3afffff9 bcc         |$L37092|
 0003c                   |$L37094|
 
; 12   :
Of course this is a simple example and applies to more than just Platform Builder and Windows CE, but the reason that I am writing about it is that when working on software that controls hardware, volatile is a very important tool to have in your bag of tricks. Software Engineers who are using Platform Builder are more likely to be writing software that controls hardware than many others (although I know that there are many of you writing drivers for other OS’s.)
Where volatile really has value to driver developers is when writing code that reads or writes registers. The compiler doesn’t “know” that the value in the register can change independent of the software. Think about reading the Rx FIFO in a UART. Each read of the FIFO register reads the next character that was received. Without a way to manage the optimizer, the code may only read one character and then ignore the code that you carefully wrote to read the rest of the characters in the FIFO.
Copyright © 2008 – Bruce Eitman
All Rights Reserved