In this chapter we’ll dive right into embedded programming by way of an example. The program we’ll look at is similar in spirit to the “Hello, World!” example found in the beginning of most other programming books. As we discuss the code, I’ll provide justification for the selection of the particular program and point out the parts of it that are dependent on the target hardware. This chapter contains only the source code for this first program. We’ll discuss how to create the executable and actually run it in the two chapters that follow.
Hello, World!
It seems like every programming book ever written begins with the same example—a program that prints “Hello, World!” on the user’s screen. An overused example like this may seem a bit boring. But it does help readers to quickly assess the ease or difficulty with which simple programs can be written in the programming environment at hand. In that sense, “Hello, World!” serves as a useful benchmark of programming languages and computer platforms. Unfortunately, by this measure, embedded systems are among the most difficult computer platforms for programmers to work with. In some embedded systems, it may even be impossible to implement the “Hello, World!” program. And in those systems capable of supporting it, the printing of text strings is usually more of an endpoint than a beginning.
You see, the underlying assumption of the “Hello, World!” example is that there is some sort of output device on which strings of characters can be printed. A text window on the user’s monitor often serves that purpose. But most embedded systems lack a monitor or analogous output device. And those that do have one typically require a special piece of embedded software, called a display driver, to be implemented first—a rather challenging way to begin one’s embedded programming career.
It would be much better to begin with a small, easily implemented, and highly portable embedded program in which there is little room for programming mistakes. After all, the reason my book-writing counterparts continue to use the “Hello, World!” example is that it is a no-brainer to implement. This eliminates one of the variables if the reader’s program doesn’t work right the first time: it isn’t a bug in their code; rather, it is a problem with the development tools or process that they used to create the executable program.
Embedded programmers must be self-reliant. They must always begin each new project with the assumption that nothing works—that all they can rely on is the basic syntax of their programming language. Even the standard library routines may not be available to them. These are the auxiliary functions—like printf and scanf—that most other programmers take for granted. In fact, library routines are often as much a part of the language standard as the basic syntax. However, that part of the standard is more difficult to support across all possible computing platforms and is occasionally ignored by the makers of compilers for embedded systems.
So you won’t find an actual “Hello, World!” program in this chapter. Instead, we will assume only the basic syntax of C is available for our first example. As we progress through the book, we will gradually add C++ syntax, standard library routines, and the equivalent of a character output device to our repertoire. Then, in Chapter 9, we’ll finally implement a “Hello, World!” program. By that time you’ll be well on your way to becoming an expert in the field of embedded systems programming.
Das Blinkenlights
Every embedded system that I’ve encountered in my career has had at least one LED that could be controlled by software. So my substitute for the “Hello, World!” program has been one that blinks an LED at a rate of 1-Hz (one complete on-off cycle per second). Typically, the code required to turn an LED on and off is limited to a few lines of C or assembly. So there is very little room for programming errors to occur. And because almost all embedded systems have LEDs, the underlying concept is extremely portable.
The superstructure of the Blinking LED program is shown below. This part of the program is hardware-independent. However, it relies on the hardware-dependent functions toggleLed and delay to change the state of the LED and handle the timing, respectively.
/********************************************************************** * * Function: main() * * Description: Blink the green LED once a second. * * Notes: This outer loop is hardware-independent. However, * it depends on two hardware-dependent functions. * * Returns: This routine contains an infinite loop. * **********************************************************************/ void main(void) { while (1) { toggleLed(LED_GREEN); /* Change the state of the LED. */ delay(500); /* Pause for 500 milliseconds. */ } } /* main() */
toggleLed
In the case of the Arcom board, there are actually two LEDs: one red and one green. The state of each LED is controlled by a bit in a register called the Port 2 I/O Latch Register (P2LTCH, for short). This register is located within the very same chip as the CPU and takes its name from the fact that it contains the latched state of eight I/O pins found on the exterior of that chip. Collectively, these pins are known as I/O Port 2. And each of the eight bits in the P2LTCH register is associated with the voltage on one of the I/O pins. For example, bit 6 controls the voltage going to the green LED:
#define LED_GREEN 0x40 /* The green LED is controlled by bit 6. */
By modifying this bit, it is possible to change the voltage on the external pin and, thus, the state of the green LED. As shown in figure 2-1, when bit 6 of the P2LTCH register is 1 the LED is off; when it is 0 the LED is on.
The P2LTCH register is located in a special region of memory called the I/O space, at offset 0xFF5E. Unfortunately, registers within the I/O space of an 80x86 processor can be accessed only by using the assembly language instructions in and out. The C language has no built-in support for this operation. Its closest replacements are the library routines inport and outport, which are declared in the PC-specific header file dos.h. Ideally, we would just include that header file and call those library routines from our embedded program. However, since they are part of the DOS programmer’s library, we’ll have to assume the worst: that they won’t work on our system. At the very least, we shouldn’t rely on them in our very first program.
An implementation of the toggleLed routine that is specific to the Arcom board and does not rely on any library routines is shown below. The actual algorithm is straightforward: read the contents of the P2LTCH register, toggle the bit that controls the LED of interest, and write the new value back into the register. You will notice that although this routine is written in C, the functional part is actually implemented in assembly language. This is a handy technique, known as inline assembly, that separates the programmer from the intricacies of C’s function calling and parameter passing conventions while still giving her the full expressive power of assembly language.
#define P2LTCH 0xFF5E /* The offset of the P2LTCH register. */ /********************************************************************** * * Function: toggleLed() * * Description: Toggle the state of one or both LED's. * * Notes: This function is specific to Arcom's Target188EB board. * * Returns: None defined. * **********************************************************************/ void toggleLed(unsigned char ledMask) { asm { mov dx, P2LTCH /* Load the address of the register. */ in al, dx /* Read the contents of the register. */ mov ah, ledMask /* Move the ledMask into a register. */ xor al, ah /* Toggle the requested bits. */ out dx, al /* Write the new register contents. */ }; } /* toggleLed() */
delay
We also need to implement a half-second (500-ms) delay between LED toggles. This is done by busy-waiting within the delay routine shown below. This routine accepts the length of the requested delay, in milliseconds, as its only parameter. It then multiplies that number by the constant CYCLES_PER_MS to obtain the total number of while-loop iterations required to delay for the requested time period.
/********************************************************************** * * Function: delay() * * Description: Busy-wait for the requested number of milliseconds. * * Notes: The number of decrement-and-test cycles per millisecond * was determined through trial and error. This value is * dependent upon the processor type and speed. * * Returns: None defined. * **********************************************************************/ void delay(unsigned int nMilliseconds) { #define CYCLES_PER_MS 260 /* Number of decrement-and-test cycles. */ unsigned long nCycles = nMilliseconds * CYCLES_PER_MS; while (nCycles--); } /* delay() */
The hardware-specific constant CYCLES_PER_MS represents the number of decrement-and-test cycles (nCycles-- != 0) that the processor can perform in a single millisecond. To determine this number I used trial and error. I made an approximate calculation (I think it came out to around 200), then wrote the remainder of the program, compiled and ran it. The LED was indeed blinking, but at a rate faster than 1-Hz. So I used my trusty stopwatch to make a series of small changes to CYCLES_PER_MS until the rate of blink was as close to 1-Hz as I cared to test.
That’s it! That’s all there is to the Blinking LED program. The three functions—main, toggleLed, and delay—do the whole job. If you want to port this program to some other embedded system, you should read the documentation that came with your hardware, rewrite toggleLed as necessary, and change the value of CYCLES_PER_MS. Of course, we do still need to talk about how to build and execute this program. We’ll examine those topics in the next two chapters. But first, I have a little something to say about infinite loops and their role in embedded systems.
The Role of the Infinite Loop
One of the most fundamental differences between programs developed for embedded systems and those written for other computer platforms is that the embedded programs almost always end with an infinite loop. Typically, this loop surrounds a significant portion of the program’s functionality—as it does in the Blinking LED program. The infinite loop is necessary because the embedded software’s job is never done. It is intended to be run until either the world comes to an end or the board is reset, whichever happens first.
In addition, most embedded systems have just one piece of software running on them. And although the hardware is important, it is not a digital watch or a cellular phone or a microwave oven without that embedded software. If the software stops running, the hardware is rendered useless. So the functional parts of an embedded program are almost always surrounded by an infinite loop that ensures that they will run forever.
This behavior is so common that it’s almost not worth mentioning. And I wouldn’t, except that I’ve seen quite a few first-time embedded programmers get confused by this subtle difference. So if your first program appears to run, but instead of blinking the LED simply changes its state once, it could be that you forgot to wrap the calls to toggleLed and delay in an infinite loop.