By Derrick Klotz, Freescale, Canada
It's a great feeling when you get your first microcontroller (MCU) program running. It might just be the simple blinking of an LED, but you're learning something new and beginning to feel that you understand it. When I did this for the first time, I saw it as an invitation to get creative and play. I have always found, and continue to find, that working with MCUs is fun. And this is especially true when I'm directly controlling the MCU's internal peripheral registers - also known as programming the bare metal.
I had the luxury of learning assembly language from MCU data books. That's right, I use the word "luxury". By studying software from a hardware manual, you develop an understanding of how the ones and zeros are organized for opcodes and operands. You can envision the program counter running through memory. You can grasp how the stack pointer handles return addresses for subroutines and interrupt service routines. This provides a pretty good foundation for comprehending how your code gets executed by the machine — the bare metal. But you don't learn anything about software structure. For me, that was a longer learning experience.
There are many different styles for writing software. As I write follow-up blog entries I am going to refer to one way — my way. Or at least the way that I currently write software. My programming style today is not what it was 10 years ago — or 20 years ago — or 30 years ago. I expect it to continue to evolve. I won't claim that my method is the best, and I am always open to ideas on how to make it better.
I use Freescale's CodeWarrior Integrated Development Environment for my software development. I was a big fan of the CodeWarrior IDE well before the Metrowerks acquisition. I continue to like using it in its current Eclipse incarnation. There are several other IDEs available and I use them on occasion in order to provide software support for some of our customers. But when I create my own code to demonstrate MCU features, I use the CodeWarrior IDE.
I also like the Processor Expert Software included with the CodeWarrior IDE. Processor Expert Software generates a software foundation (or template) that helps you get your MCU running. It also creates reasonably well documented software for peripheral initialization and manipulation. I often use Processor Expert Software to help me understand how to manipulate an on-chip peripheral by comparing the code that it generates to the information provided by the MCU data book or reference manual. This invariably speeds up my learning process and makes reading the technical documentation a little less tedious.
Bare metal programming does not mean that you need to program in assembly language. It does mean that you need to understand assembly language. You will need to disassemble the code that you've written using the C programming language. This will show you exactly what you told the compiler to do, which won't necessarily be the same as what you wanted it to do. Really. Don't ever mix up those two. You have learned a valuable lesson when you realize the difference between what you told a C compiler to do and what you actually wanted it to do.
You will also need to understand the MCU's data book - its memory map, registers, peripherals, etc. This can be a daunting task since these manuals continue to get bigger as silicon design geometries get smaller and MCUs become more complex. But that's also a part of the fun. In the same way that software should be written in small manageable pieces, so too should your approach to comprehending how to manipulate each hardware peripheral. Eat the elephant one byte (ahem) at a time.
The Basic Template
Let's start at the beginning. There is a simple template that is commonly used to get an MCU up and running when you switch on the power supply. With some subtle differences or extensions, this is the underlying structure upon which C compilers and Real-Time Operating System (RTOS) developers build their environments. It is the template used by the Processor Expert Software. Figure 1 provides a graphical summary of the basic MCU software foundation.
Figure 1. | Summary of the basic MCU software foundation. |
Once the power gets turned on, there are some fundamental hardware elements that should be initialized first, before doing anything else. These components are related more to running the MCU as opposed to running the application. At a minimum, this will include the protection watchdog timer and the oscillator. While their frequency of operation may be closely tied to the application, all programs will need to initialize these two hardware modules.
Once the hardware clocks are up and running, software initialization is next. Or, more correctly, you need to configure RAM. RAM is a resource that is shared between the stack and the application's variables. In general, the stack will grow up from the bottom of RAM, while you allocate variables starting at the top of RAM. There are some possible exceptions to this organization, but this is the simplest arrangement.
If the stack pointer hasn't automatically been configured to its initial value, here's the place to do it - before executing any subroutines or interrupt service routines.
Whenever possible, I prefer to clear all RAM at this stage. This has two important benefits. First of all, by definition, this will clear all program variables to zero. And it does it quickly. I find that most algorithms are a little easier to develop if I know that the variables that they use are starting at zero. Secondly, this provides an easy memory dump visual debugging technique that allows me to see the RAM usage - and if the stack has crashed into the variable space. I intend to expand on this topic in a future blog entry.
With the basic hardware running and the software initialized, we're ready to run the main() routine. Here is where you initialize the application specific hardware and software modules. Using modular programming techniques, each functional module should have its own routine that performs the necessary hardware and/or software initialization as required by the function.
With everything finally initialized, you need to enable the hardware interrupts prior to your software execution falling into the main control loop. There are many different loop architectures from which to choose. Three of the more popular techniques are State Machines, Schedulers and Real-Time Operating Systems. Regardless of the chosen software structural design, function subroutines such as states or tasks execute as a response to software events, while hardware events trigger Interrupt Service Routines (ISRs). These will all return back to the main control loop.
The main control loop is also the one and only location in the code where the protection watchdog timer is reset (if it is enabled). Resetting the watchdog timer in multiple locations in the code can defeat its purpose - which is to protect against a "run away" condition of the software's execution. Effective use of the watchdog timer requires minimum execution time for subroutines and interrupt service routines.
Without getting into the details of any specific MCU, I've discussed the basics of getting an MCU started and provided a simple software flow template. You know, this may seem like old hat or familiar territory, but I know I’m not alone in wishing I had seen a simple overview like this at a point in my career, and it isn’t easy to come by. As well, it serves as a basic foundation for where I’m going. It applies equally to 8-bit MCUs as it does to 16-bit and 32-bit architectures.
I think that it's important to understand the machine that is executing your code. High-level abstracted software certainly has its place. But for me, the real fun is working with the Bare Metal. I hope that some of you agree. Stay tuned for more. We are off on a grand adventure in understanding how to get the most out of Freescale silicon.