This site is still very much a work in progress, and not yet ready for prime time! If you are seeing this website now it's probably because you got the URL from my resume. If so, that's because I took the chance that an incomplete (!) website would give you more information about my background than no website at all. This endeavor has proven to be a lot more work than I expected, but maybe it will also end up being a little more useful to beginners as a result.
Welcome to the OLD site of the Introduction to Embedded Programming website. Please visit the NEW site for the latest version of this tutorial.
This tutorial is intended for students, hobbyists, programmers and hardware designers who want to learn the basics of embedded systems programming, or who want to fill in some gaps in their knowledge of such programming. This tutorial will not teach you programming, although it will discuss programming techniques of particular interest for embedded systems. This tutorial will also not teach you hardware design, although it will illustrate hardware issues commonly faced in embedded systems.
Every rule has
one many exceptions. This applies to just about everything you will read in this tutorial. If you read "X" here, don't assume it means "X and only X, in every possible situation, with no exceptions or qualifications, now and forever." Microcontroller designers have come up with many different, interesting and sometimes wierd ways of doing things. And as an embedded programmer you too can come up with many different and interesting (notice I left out wierd) ways of doing things. In my experience, given an N-step program, there are about N-squared ways of writing that program (but see Caveat Lector!). The goal of this tutorial is to try and give you a solid foundation for embedded programming, not to be a comprehensive encyclopedia of the field. For every example program, I could think of many other ways to write it, using all kinds of clever tricks, but I will try and write it in a simple and understandable fashion and let you discover your own clever tricks further down the road.
Another possible source of confusion is in terminology. Different manufacturers quite often use different terminology for the same or similar features, registers and configuration/status options. In this tutorial sometimes I will adopt the terminology used by one of the three uCs used in the tutorial (AVR, HCS12, LPC2xxx ARM), and other times I will use non-specific terminology. I will use whatever seems to be suitable for each situation.
What is Embedded Programming?
Embedded programming is the term for the computer programming that lives in and operates the great many computer-controlled devices that surround us in our homes, cars, workplaces and communities. For every desktop or notebook computer you have, you may have a dozen or more (perhaps a great deal more) microcontrollers quietly doing their embedded duty, and in these devices most people don’t even realize there’s a computer running a program. But there is, and it is, and those programs had to be written, and that’s why the world needs embedded programming. Embedded computers (microcontrollers) add intelligence to countless devices and systems, enabling those devices and systems to operate better, faster, more safely, more efficiently, more conveniently, more usefully, and in many cases allowing the very existence of devices and systems that could not be built otherwise. Spend some time looking around you and trying to perceive where microcontrollers are working, and you will begin to get a sense of how ubiquitous they have become since their invention some 40 years ago.
On top of all that, many people, myself included, find embedded programming a particularly fascinating and rewarding branch of the programming tree, and we just like to program embedded systems. In ways very different from desktop or mainframe programming, embedded programs make stuff do stuff, and to an embedded programmer, stuff doing stuff is endlessly cool. If you’re a relative newcomer to embedded programming and want to learn more about the subject, then please read along. I hope you’ll find some of the information you seek here.
What is an Embedded System?
There’s no perfect answer to that question, since every answer will have some exceptions (remember?). However, for our purposes let us declare that an embedded system is one that uses one or more microcomputers (that is, small to very, very small computers), running custom programs and connected to specialized hardware, to perform a dedicated set of functions. This can be contrasted with a general-purpose computer such as the familiar desktop or notebook, which are not designed to run only one dedicated program with one specialized set of hardware. It’s not a perfect definition, but it’s a start.
Some examples of embedded systems are:
- Alarm / security system
- Automobile cruise control
- Heating / air conditioning thermostat
- Microwave oven
- Anti-skid braking controller
- Traffic light controller
- Vending machine
- Gas pump
- Handheld Sudoku game
- Irrigation system controller
- Singing wall fish (or this gift season’s equivalent)
- Mars Rover
For the most part I have listed example embedded applications on the less-complex end of the spectrum, since this is after all a beginning tutorial. By the end of this tutorial you should have a good idea how most of these applications would be programmed, and in general terms what kinds of I/O, timing, interrupt and communications hardware and functionality they would require.
There are a few things worth noticing about the above list. While many embedded systems use fairly traditional user input-output devices (keypads, displays), many others do not. Also, many embedded systems interact directly with human beings, but others do not (we’re still waiting to see if the Mars Rover interacts directly with Martians).
What is different about Embedded Programming?
Embedded programs must work closely with the specialized components and custom circuitry that makes up the hardware. Unlike programming on top of a full-function operating system, where the hardware details are removed as much as possible from the programmer’s notice and control, most embedded programming acts directly with and on the hardware. This includes not only the hardware of the CPU, but also the hardware which makes up all the peripherals (both on-chip and off-chip) of the system. Thus an embedded programmer must have a good knowledge of hardware, at least as it pertains to writing software that correctly interfaces with and manipulates that hardware. This knowledge will often extend to specifying key components of the hardware (microcontroller, memory devices, I/O devices, etc), and in smaller organizations will often go as far as designing and laying out (as a printed circuit board) the hardware. An embedded programmer will also need to have a good understanding of debugging equipment such as multimeters, oscilloscopes, logic analysers and the like.
Another difference from more general purpose computers is that most (but not all) embedded systems are quite limited as compared to the former. The microcomputers used in embedded systems may have program memory sizes of a few thousand to a few hundred thousand bytes rather than the gigabytes in the desktop machine, and will typically have even less data (RAM) memory than program memory. Further, the processors used will often be smaller 8 and 16 bit devices as opposed to the 32 bit and larger devices found in a desktop (although small 32-bit microcontrollers are now under a dollar in moderate quantities, which is amazingly amazing).
What is the difference between microcomputer, microprocessor and microcontroller?
A microprocessor is usually understood to be a single-chip central processing unit (CPU), with the CPU being the "brains" of a computer. A microcomputer is any computer built around a microprocessor, along with program and data memory, and I/O devices and other peripherals as needed. A microcontroller (often shortened to “μC” in this tutorial) is a single chip device which has on board not only a microprocessor but also, on the same chip, nonvolatile program (ROM) and volatile data (RAM) memory, along with useful peripherals such as general-purpose I/O (GPIO), timers and serial communications channels. Thus it follows that all microcontrollers are microcomputers, but not all microcomputers use microcontrollers.
In smaller embedded systems it is most common to use microcontrollers since they give the most compact design and the lowest hardware cost. Larger embedded systems, on the other hand, may use one or more microprocessors if a microcontroller of suitable speed and functionality cannot be found. It is also possible to include both microprocessors and microcontrollers in a complex embedded system. The only rules are, use whatever device(s) fit the task, given the constraints on budget, availability, time, tools, etc.
It should also be pointed out that with most microcontrollers it is possible to add external memory and peripherals, should the on-board mix not take care of all the system needs. When it makes sense to add such external devices, as opposed to choosing a larger microcontroller with the needed resources on-board, is a choice that needs to be made on an individual design basis.
What is an “N-bit CPU/microprocessor/microcontroller”?
There is much discussion about what it means to call a device an N-bit processor, but really it’s pretty obvious in most cases. If the device can perform most of its data manipulation instructions on data words to a maximum of N bits in size, the device is an N-bit processor. By way of example, a device may have a full set of instructions that can operate on 8 bit data, along with a few instructions that operate on 16 bit data. That device should be considered an 8-bit design, even if the marketing department says otherwise and calls it a 16-bit chip.
By volume, 8-bit microcontrollers are the biggest segment of the embedded market. Many applications simply don’t need any more power, and never will. 16-bit devices are more powerful, but they are squeezed between the 8-bit devices on the low end and the 32-bit devices on the high end. 32-bit devices are at the high end of the embedded spectrum for all but the most complex or high-performance designs, but they are moving ever downward in price.
What microcontroller families are used in this tutorial?
To give a good overview of the different “flavors” of microcontrollers available, this tutorial will be written around one 8-bit family (the Atmel AVR), one 16-bit family (the Freescale - formerly Motorola - HCS12), and one 32-bit family (the ARM7TDMI architecture in the form of the NXP LPC2xxx family). These families were chosen to give a broad picture of the devices and approaches found in the world of microcontrollers. Most software examples will be written in assembly language for each of these families, as well as in C.
What else is required for this tutorial?
While you could, I suppose, work through this tutorial using just a microcontroller simulator, I strongly recommend that you have either a microcontroller training/development board, or even just a bare μC chip, assorted components and a powered breadboard. In addition you will need an assembler for your device, and optionally a C compiler that targets your device. You should have no trouble finding a free assembler for your chip, and you should also be able to find a free C compiler, even if it is a reduced-functionality version of a commercial compiler. You will also need a method of downloading your programs into your μC. The details of this download process will depend intimately on the particular μC.
These are the details of the hardware and tools used for each of the processor families:
- Hardware: Atmel STK-500 board with ATmega16 installed
- Tools: AVR Studio 4 with WinAVR plugin (both free)
- Hardware: Olimex LPC-E2294 board
- Tools: Rowley Crossworks ($150 for personal license - suggest IAR Embedded Workbench Kickstart Edition for free toolset)
- Hardware: Wytec Dragon 12 board
- Tools: Code Warrior (free version)
Which programming language?
This is a good time to talk about the various programming languages that one can use to write embedded software. The two languages I will use in this tutorial are C and assembly language. The first thing I want to point out is that these are not the only two languages available to embedded programmers, and that in many cases other languages may be a better choice. That being said, both C and assembly language are useful not only for learning about embedded programming, but also for actually doing productive embedded programming. They are also ubiquitous in that no matter what microcontroller you choose, it will almost certainly have available both an assembler (for converting assembly language code) and a C compiler (for converting C code). The same is definitely not the case for other languages. But I would encourage you to consider other languages if you are so inclined and, big IF, if they are available for your device family.
On the subject of assembly language, even if you don’t plan on using assembly language in your embedded programming, I would strongly suggest that you become at least somewhat familiar with the concepts, and with the instruction set of your uC. The reason for this is that, even if you don’t end up writing any assembly language, you will find yourself at some point needing to examine the output of your compiler and/or your compiler-supplied startup files written or output in assembly language.
Also note that the term "assembly language" will often be shortened, in this tutorial and elsewhere, to "asm" or "ASM."
How does an embedded program run?
Before talking much more about embedded programming, this is a good place to give a brief overview of how an embedded program starts up and runs. Assuming that you have generated a program file and have loaded it into the μC program memory (all steps that we will talk about later), the good stuff happens when you either turn on the device or you push the RESET button. When the μC comes out of reset it will always go to a particular memory location, as defined by the manufacturer, to begin executing whatever code is found there. Sometimes this memory location is defined directly, e.g. “upon coming out of reset, program execution begins at program address 0”. Other times the fixed memory location is a vector, a location that holds the actual address of the beginning of the program, e.g. “upon coming out of reset, the controller will load its program counter with the value found at program address 0xFFFE”. In the first instance you will have to make sure that your program has loaded at the specified startup address, while in the second instance you will load your program wherever the program memory has been placed in the controller address space, and you will have to make sure that you then load that startup address into the reset address vector.
When an embedded program starts to run, there is usually a fair amount of initialization and housekeeping that must be done before the “meat” of the program begins. Most of this initialization is something that the average desktop programmer never sees, since it is handled by the computer boot code and operating system. But in an embedded system, it is just as likely as not that there is no operating system, and all boot code and other startup code must be explicitly provided. Some very critical hardware may need to be initialized first e.g. hardware that controls memory access times and address maps, as well as system clock hardware. Then some software initialization may need to happen, such as setting up a stack pointer and perhaps copying data from nonvolatile memory to volatile memory where it can be accessed and perhaps modified. After that will usually come another round of hardware initialization, setting up any peripheral devices that the system requires, and setting initial output states. Finally, yet another round of software initialization may occur.
This initialization is usually broken up into two sections, with the first hardware and software initialization steps often being done in what is known as the startup code, and the later hardware and software steps being done in the user program. This delineation is more distinct in a C program, where the startup code is invisible to the C program, being the code that happens before main() is run, and ending in a jump or call to main() where the “visible” C program begins. In an assembler program all the initialization steps may be equally visible in the user code, although even then the first steps may reside in a separate startup source file.
Ones and Zeros, Highs and Lows
As you should already know, a computer program manipulates data in the form of binary digits, 1s and 0s. This data may represent characters, times, temperatures, button pushes, alarm signals, screen pixels, the list is almost endless. But in the computer it's all 1s and 0s. When any computer, and in particular a microcontroller, interfaces with the real world as it must, there must be a translation between these 1s and 0s and external voltages on the device pins. It is enough now to know that externally a 1 will be represented to and from the uC by a high voltage (we'll talk later about how high is high) and a 0 will be represented by a low voltage (again, details will follow). Like every other rule there can be exceptions, but for now just remember 1=high, 0=low. We will try to use "1" and "0" when talking about logical states (the data in the program) and "high" and "low" when talking about signals external to the uC, but we may fuzz up that dividing line now and then.
A Note on the Example Programs
Each tutorial section will include a number of short example programs. The examples will start with the simplest concepts and add some concepts in each succeeding program. Along the way, some comments will be trimmed to try and help keep the visual clutter down and keep the focus on the newer concepts being presented. As an example, comments to the effect that "this bit/port/address needs to be adjusted for your particular hardware" will eventually disappear, because by then you should know that e.g. if I am discussing an LED output on PORTA bit 0 and on your hardware you are using an LED on PORTB bit 7 then you'll make that change accordingly. Or when I mention in the first programs that after a "ret" instruction that you'd better have set up the stack first, after a while that comment and others like it will disappear.
After all this it's finally time to think about writing our first embedded program. In deference to tradition we are calling this first program Embedded Hello World.