Welcome to the Embedded Systems Programming course. My name is Miro Samek and in this lesson I'll show you how to improve the "blinky" program by using the C pre-processor and the volatile keyword. As usual, let's start with making a copy of the previous "lesson4" project and renaming it to "lesson5". If you are just joining the course, you can download the previous projects from state-machine.com/quickstart. Get inside the new "lesson5" directory and double-click on the workspace file to open the IAR toolset. If you don't have the IAR toolset, go back to "lesson0". So, this is the program you created in "lesson4". It works in that it manages to blink the RED led on the Stellaris Launchpad board, but it is certainly is not very readable, because it is full of cryptic numbers and there are no comments to explain what's going on. To improve the readability of the code, it would be very nice to use names for the registers instead of the cryptic numbers. One way you can achieve it, is by means of the C pre-processor, which lets you define any piece of code as a macro. For example, let's define a macro for the first register you write-to. Start a new line with the #-sign followed by the word "define", followed by the name of the macro. The data sheet calls the register Run-Mode Clock Gating Control Register for GPIO, so let's name the macro RCGC-GPIO. After the name you simply paste the piece of code this macro is going to replace. Once the macro is defined, you can use it instead of the original piece of code. Press F7 to check if the compiler accepts your code so far. The C pre-processor is called that way, because it is conceptually a separate first step of simple text substitution before the real compilation. The pre-processor removes all lines starting with the #-sign, so the compiler does not see them at all. For example, you can define a macro to be anything, but as long as it is not used in the code, it doesn't matter and the code still compiles. Also, the pre-processor replaces only the macros that are actually used in the code, so the compiler sees only the replacement sequences of characters and never the macro names themselves. This means that a macro does not need to be any complete element of the C language. For example, a macro FOO is just a piece of the pointer-cast expression, but as long as the text substituted for the macro makes sense in the specific context, the compiler will happily accept it, because really the compiler cannot tell the difference. The corollary of all this is that you need to be careful how you define your macros, so that their meaning will not change unexpectedly depending on the context in which they are substituted. For example, to avoid surprises, it is always a good idea to enclose the macro like RCGC-GPIO in parentheses, so that it always means de-referencing of a pointer in any context it might be used. It is also possible to define macros using other macros. For example, if you define the macro GPIOF_BASE as it is specified in the Data Sheet, you can use it in the definitions of other macros, such as GPIOF_DIR for the pin direction register with the offset 0x400 from the base address, GPIOF_DEN for digital enable with the offset 0x51C from the base address , and GPIOF_DATA with the offset 0x3FC from the base address. Finally, it is always highly recommended to add comments to your code. Comments are only for the benefit of humans, who read your code, and are completely ignored by the compiler. The C99 Standard supports two types of comments: the traditional C comment delimited by /* */, such as this one: and the C++-style comment starting with // and terminated by the end of line, such as these ones Comments are allowed everywhere where you could legally place a space, and in fact, all comments are replaced with a single space before the compilation. Both types of comments can also be used in the macro definitions. So, now it will be interesting to see if this code still blinks the LED. To actually see this, I'm going to use the Stellaris Launchpad board. But If you don't have the board, configure the Debugger for Simulator and follow along. Great, the LED blinks as before, so all your changes seem to work. Let's examine in some detail how the compiler translated the GPIO_DATA macro and weather or not it introduced any overheads. After all, you might be concerned than now the CPU needs to add the base address to the offset at run time. But when you step through the code, you can immediately see that the LDR.N instruction loads directly the full address 0x400253FC into R0 without performing any additions. In other words, the code is as efficient as before, because the compiler folds any constants computable at compile time and avoids unnecessary computations at run time. Finally, it is very interesting to see which single instruction actually turns the LED on. It turns out that this is the STR instruction. In other words, from the CPU point of view, talking to the outside world is fundamentally very simple and boils down to writing a specific value into a specific address. OK, so your program still works and is as efficient as before. But I don't want to leave you with the impression that you have to define macros for all the registers yourself. In fact, typically you don't need to, because the microcontroller vendors, such as Texas Instruments in the case of the Stellaris board, already provide the macros for you in a separate file, which I've copied to the lesson5 directory. You can add this file to the project by right-clicking on the project and choosing the Add->Add Files menu option. The file name is "lm4f120h5qr.h", which corresponds exactly to the microcontroller type on your Stellaris Launchpad board. The file extension ".h" means that it is a header file, specifically designed for inclusion into ".c" files, such as main.c. When you open the header file, you can see that it contains a whole bunch of macros very similar to what you have defined yourself. However, the pointer casts used in the header file are significantly different, which requires some explanation. Let me grab one macro from the header file and copy it to the main.c file for comparison. The first discrepancy is the pointer type. The macros in main.c use "unsigned int", while the header file "unsigned long". I will discuss data types in a separate lesson, but for now let me only say that on a 32-bit machine, like the ARM processor, the int-type is 32-bit wide, so is the long-type. So, "unsigned int" and "unsigned long" are equivalent. So, the real difference is the "volatile" qualifier. It informs the compiler that the object pointed-to by the pointer might change spontaneously. When you declare an object to be "volatile", you are telling the compiler that the object might change even though no statements in the program appear to change it. For example, two bits in the GPIOF register on the Launchpad board are connected to the user switches. These bits can be changed when the user presses or releases the switches, which is obviously not caused by any program instruction. Therefore, the GPIOF register, and in fact most other I/O registers, are volatile. This is important, because the compiler can optimize access to non-volatile objects by reading an object's value into a CPU register, working with that register for a while, and eventually writing the value in the register back to the object. The compiler is NOT permitted to do this soft of optimization with "volatile" objects. Every time the source program says to read-from or to write-to a "volatile" object, the compiler has to do so. So clearly, the "volatile" qualifier is useful for I/O registers such as GPIOF. But it can also be very useful for "normal" variables to prevent optimizations that the compiler otherwise might do. For example, the "counter" variable is used only in the two delay loops. But if you look at these loops from the compiler's perspective, they make no contribution whatsoever to the computation, because the final value of the "counter" is either overwritten or discarded. In this case, the compiler is permitted to optimize both delay loops "away". You can actually see this quite easily by allowing a higher-level of optimization as follows. Click on the Project options, choose "C/C++ Compiler" section and click on the "Optimizations" tab. Select "High" optimization level and click OK. Recompile and run the program on the Launchpad board. As you can see, the LED lights up and stays on, all the time... When you single-step through the code, you can see that the instructions for turning the LED on and off are still there, but the delay loops between them are gone. But now, that you know about the "volatile" keyword, you know how to prevent the compiler from optimizing the delay loops away. You need to make the "counter" variable "volatile". By the way, the "volatile" keyword can be placed either before the type, like in the macro from the header file, or after the type. I recommend placing it after the type. Let's quickly test weather the "volatile" counter indeed fixed the problem. Yes, the LED blinks... You can also see the delay loop when you single-step through the code. So now, let's make use of the .h header file by actually including it into the main program. Again, you use the pre-processor for this. To include a file, you start a new line with the #-sign, followed by the word "include" followed by the name of the file in double quotes. Let's put the header file side-by-side with the main program to replace all the macros you've defined so far with the macros from the header file. The header file provided by the microcontroller vendor uses the register names from the Datasheet, so you should have no trouble to recognize the interesting registers, such as: GPIO_PORTF_DATA GPIO_PORTF_DIR and GPIO_PORTF_DEN If you have any doubts about the correct name of the resister, you can always check it's address to make sure that this is what you want. After replacing all the macros, you can remove your own definitions and recompile the code. Let's test the code one last time to check if the LED will still blink... This concludes this lesson about the C pre-processor and the "volatile" keyword. From now on, you are able to write programs that will run correctly at any level of optimization. So congratulations! In the next lesson you will learn how to use the bit-wise OR and AND operators to blink other colors of the composite LED and you will also learn about the more advanced features of the GPIO register. If you like this channel, please subscribe to stay tuned. You can also visit state-machine.com/quickstart for the class notes and project file downloads. --- Course web-page: http://www.state-machine.com/quickstart YouTube playlist of the course: http://www.youtube.com/playlist?list=PLPW8O6W-1chwyTzI3BHwBLbGQoPFxPAPM