BUY THIS BOOK
Add to Cart

Print Book $49.99


Add to Cart

PDF $39.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £35.50

What is this?

Looking to Reprint or License this content?


Programming Embedded Systems
Programming Embedded Systems, Second Edition With C and GNU Development Tools

By Michael Barr, Anthony Massa
Book Price: $49.99 USD
£35.50 GBP
PDF Price: $39.99

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introduction
I think there is a world market for maybe five computers.
—Thomas Watson, Chairman of IBM, 1943
There is no reason anyone would want a computer in their home.
—Ken Olson, President of Digital Equipment Corporation, 1977
One of the more surprising developments of the last few decades has been the ascendance of computers to a position of prevalence in human affairs. Today there are more computers in our homes and offices than there are people who live and work in them. Yet many of these computers are not recognized as such by their users. In this chapter, we’ll explain what embedded systems are and where they are found. We will also introduce the subject of embedded programming and discuss what makes it a unique form of software programming. We’ll explain why we have selected C as the language for this book and describe the hardware used in the examples.
An embedded system is a combination of computer hardware and software—and perhaps additional parts, either mechanical or electronic—designed to perform a dedicated function. A good example is the microwave oven. Almost every household has one, and tens of millions of them are used every day, but very few people realize that a computer processor and software are involved in the preparation of their lunch or dinner.
The design of an embedded system to perform a dedicated function is in direct contrast to that of the personal computer. It too is comprised of computer hardware and software and mechanical components (disk drives, for example). However, a personal computer is not designed to perform a specific function. Rather, it is able to do many different things. Many people use the term general-purpose computer to make this distinction clear. As shipped, a general-purpose computer is a blank slate; the manufacturer does not know what the customer will do with it. One customer may use it for a network file server, another may use it exclusively for playing games, and a third may use it to write the next great American novel.
Frequently, an embedded system is a component within some larger system. For example, modern cars and trucks contain many embedded systems. One embedded system controls the antilock brakes, another monitors and controls the vehicle’s emissions, and a third displays information on the dashboard. Some luxury car manufacturers have even touted the number of processors (often more than 60, including one in each headlight) in advertisements. In most cases, automotive embedded systems are connected by a communications network.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
What Is an Embedded System?
An embedded system is a combination of computer hardware and software—and perhaps additional parts, either mechanical or electronic—designed to perform a dedicated function. A good example is the microwave oven. Almost every household has one, and tens of millions of them are used every day, but very few people realize that a computer processor and software are involved in the preparation of their lunch or dinner.
The design of an embedded system to perform a dedicated function is in direct contrast to that of the personal computer. It too is comprised of computer hardware and software and mechanical components (disk drives, for example). However, a personal computer is not designed to perform a specific function. Rather, it is able to do many different things. Many people use the term general-purpose computer to make this distinction clear. As shipped, a general-purpose computer is a blank slate; the manufacturer does not know what the customer will do with it. One customer may use it for a network file server, another may use it exclusively for playing games, and a third may use it to write the next great American novel.
Frequently, an embedded system is a component within some larger system. For example, modern cars and trucks contain many embedded systems. One embedded system controls the antilock brakes, another monitors and controls the vehicle’s emissions, and a third displays information on the dashboard. Some luxury car manufacturers have even touted the number of processors (often more than 60, including one in each headlight) in advertisements. In most cases, automotive embedded systems are connected by a communications network.
It is important to point out that a general-purpose computer interfaces to numerous embedded systems. For example, a typical computer has a keyboard and mouse, each of which is an embedded system. These peripherals each contain a processor and software and is designed to perform a specific function. Another example is a modem, which is designed to send and receive digital data over an analog telephone line; that’s all it does. And the specific function of other peripherals can each be summarized in a single sentence as well.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Variations on a Theme
Unlike software designed for general-purpose computers, embedded software cannot usually be run on other embedded systems without significant modification. This is mainly because of the incredible variety of hardware in use in embedded systems. The hardware in each embedded system is tailored specifically to the application, in order to keep system costs low. As a result, unnecessary circuitry is eliminated and hardware resources are shared wherever possible.
In this section, you will learn which hardware features are common across all embedded systems and why there is so much variation with respect to just about everything else. Later in the book, we will look at some techniques that can be used to minimize the impact of software changes so they are not needed throughout all layers of the software.
By definition, all embedded systems contain a processor and software, but what other features do they have in common? Certainly, in order to have software, there must be a place to store the executable code and temporary storage for runtime data manipulation. These take the form of read-only memory (ROM) and random access memory (RAM), respectively; most embedded systems have some of each. If only a small amount of memory is required, it might be contained within the same chip as the processor. Otherwise, one or both types of memory reside in external memory chips.
All embedded systems also contain some type of inputs and outputs. For example, in a microwave oven, the inputs are the buttons on the front panel and a temperature probe, and the outputs are the human-readable display and the microwave radiation. The outputs of the embedded system are almost always a function of its inputs and several other factors (elapsed time, current temperature, etc.). The inputs to the system usually take the form of sensors and probes, communication signals, or control knobs and buttons. The outputs are typically displays, communication signals, or changes to the physical world. See Figure 1-2 for a general example of an embedded system.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Embedded Design Examples
To demonstrate the variation in design requirements from one embedded system to the next, as well as the possible effects of these requirements on the hardware, we will now take some time to describe three embedded systems in some detail. Our goal is to put you in the system designer’s shoes for a few moments before narrowing our discussion to embedded software development.
At the current peak of the evolutionary path that began with sundials, water clocks, and hourglasses is the digital watch. Among its many features are the presentation of the date and time (usually to the nearest second), the measurement of the length of an event to the nearest hundredth of a second, and the generation of an annoying little sound at the beginning of each hour. As it turns out, these are very simple tasks that do not require very much processing power or memory. In fact, the only reason to employ a processor at all is to support a range of models and features from a single hardware design.
The typical digital watch contains a simple, inexpensive 4-bit processor. Because processors with such small registers cannot address very much memory, this type of processor usually contains its own on-chip ROM. And, if there are sufficient registers available, this application may not require any RAM at all. In fact, all of the electronics— processor, memory, counters, and real-time clocks—are likely to be stored in a single chip. The only other hardware elements of the watch are the inputs (buttons) and outputs (display and speaker).
A digital watch designer’s goal is to create a reasonably reliable product that has an extraordinarily low production cost. If, after production, some watches are found to keep more reliable time than most, they can be sold under a brand name with a higher markup. For the rest, a profit can still be made by selling the watch through a discount sales channel. For lower-cost versions, the stopwatch buttons or speaker could be eliminated. This would limit the functionality of the watch but might require few or even no software changes. And, of course, the cost of all this development effort may be fairly high, because it will be amortized over hundreds of thousands or even millions of watch sales.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Life As an Embedded Software Developer
Let’s now take a brief look at some of the qualities of embedded software that set embedded developers apart from other types of software developers. An embedded software developer is the one who gets her hands dirty by getting down close to the hardware.
Embedded software development, in most cases, requires close interaction with the physical world—the hardware platform. We say “in most cases” because there are very large embedded systems that require individuals to work solely on the application-layer software for the system. These application developers typically do not have any interaction with the hardware. When designed properly, the hardware device drivers are abstracted away from the actual hardware so that a developer writing software at the application level doesn’t know how a string gets output to the display, just that it happens when a particular routine is called with the proper parameters.
Hardware knowledge
The embedded software developer must become intimately familiar with the integrated circuits, the boards and buses, and the attached devices used in order to write solid embedded software (also called firmware). Embedded developers shouldn’t be afraid to dive into the schematics, grab an oscilloscope probe, and start poking around the circuit to find out what is going on.
Efficient code
Because embedded systems are typically designed with the least powerful and most cost-effective processor that meets the performance requirements of the system, embedded software developers must make every line of code count. The ability to write efficient code is a great quality to possess as a firmware developer.
Peripheral interfaces
At the lowest level, firmware is very specialized, because each component or circuit has its own activity to perform and, furthermore, its own way of performing that activity. Embedded developers need to know how to communicate with the different devices or peripherals in order to have full control of the devices in the system. Reacting to stimuli from external peripherals is a large part of embedded software development.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The C Language: The Lowest Common Denominator
One of the few constants across most embedded systems is the use of the C programming language. More than any other, C has become the language of embedded programmers. This has not always been the case, and it will not continue to be so forever. However, at this time, C is the closest thing there is to a standard in the embedded world. In this section, we’ll explain why C has become so popular and why we have chosen it as the primary language of this book.
Because successful software development so frequently depends on selecting the best language for a given project, it is surprising to find that one language has proven itself appropriate for both 8-bit and 64-bit processors; in systems with bytes, kilobytes, and megabytes of memory; and for development teams that range from one to a dozen or more people. Yet this is precisely the range of projects in which C has thrived.
The C programming language has plenty of advantages. It is small and fairly simple to learn, compilers are available for almost every processor in use today, and there is a very large body of experienced C programmers. In addition, C has the benefit of processor-independence, which allows programmers to concentrate on algorithms and applications rather than on the details of a particular processor architecture. However, many of these advantages apply equally to other high-level languages. So why has C succeeded where so many other languages have largely failed?
Perhaps the greatest strength of C—and the thing that sets it apart from languages such as Pascal and FORTRAN—is that it is a very “low-level” high-level language. As we shall see throughout the book, C gives embedded programmers an extraordinary degree of direct hardware control without sacrificing the benefits of high-level languages. The “low-level” nature of C was a clear intention of the language’s creators. In fact, Brian W. Kernighan and Dennis M. Ritchie included the following comment in the opening pages of their book The C Programming Language (Prentice Hall):
C is a relatively “low level” language. This characterization is not pejorative; it simply means that C deals with the same sort of objects that most computers do. These may be combined and moved about with the arithmetic and logical operators implemented by real machines.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
A Few Words About Hardware
It is the nature of programming that books about the subject must include examples. Typically, these examples are selected so that interested readers can easily experiment with them. That means readers must have access to the very same software development tools and hardware platforms used by the authors. Unfortunately, it does not make sense to run any of the example programs on the platforms available to most readers—PCs, Macs, and Unix workstations.
Even selecting a standard embedded platform is difficult. As you have already learned, there is no such thing as a “typical” embedded system. Whatever hardware is selected, the majority of readers will not have access to it. But despite this rather significant problem, we do feel it is important to select a reference hardware platform for use in the examples. In so doing, we hope to make the examples consistent and, thus, the entire discussion more clear—whether you have the chosen hardware in front of you or not.
In choosing an example platform, our first criterion was that the platform had to have a mix of peripherals to support numerous examples in the book. In addition, we sought a platform that would allow readers to carry on their study of embedded software development by expanding on our examples with more advanced projects. Another criterion was to find a development board that supported the GNU software development tools; with their open source licensing and coverage on a wide variety of embedded processors, the GNU development tools were an ideal choice.
The chosen hardware consists of a 32-bit processor ( the XScale ARM), a hefty amount of memory (64 MB of RAM and 16 MB of ROM), and some common types of inputs, outputs, and peripheral components. The board we’ve chosen is called the VIPER-Lite and is manufactured and sold by Arcom. A picture of the Arcom VIPER-Lite development board (along with the add-on module and other supporting hardware) is shown in Figure 1-4. Additional information about the Arcom board and instructions for obtaining one can be found in Appendix A.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Getting to Know the Hardware
hard·ware n. The part of a computer system that can be kicked.
As an embedded software engineer, you’ll have the opportunity (and challenge) to work with many different pieces of hardware in your career. In this chapter, we will begin by taking a look at the basics in understanding a schematic. We will also teach you a simple procedure that we use to familiarize ourselves with any new board. In the process, we’ll guide you through the creation of a C-language header file that describes the board’s most important features and a piece of software that initializes the hardware to a known state.
Before writing software for an embedded system, you must first be familiar with the hardware on which it will run. At first, you just need to understand the general operation of the system, such as what the board’s main function is and what the inputs and outputs are. Initially, you do not need to understand every little detail of the hardware—how every component or peripheral operates and what registers need to be programmed for particular functions.
Whenever you receive a new board, you should take some time to read the main documents provided with it. If the board is an off-the-shelf product, it might arrive with a “User’s Guide” or “Programmer’s Manual” that has been written for software developers. (The Arcom development kit, for example, includes this information as well as datasheets for all major components on the board.) However, if the board was custom designed for your project, the documentation might be more cryptic or may have been written mainly for the reference of the hardware designers. Either way, this is the single best place to start.
While you are reading the documentation, set the board itself aside. This will help you to focus on the big picture. There will be plenty of time to examine the actual board more closely when you have finished reading. Before picking up the board, you should be able to answer two basic questions about it:
  • What is the overall purpose of the board?
  • How does data flow through it?
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Understanding the Big Picture
Before writing software for an embedded system, you must first be familiar with the hardware on which it will run. At first, you just need to understand the general operation of the system, such as what the board’s main function is and what the inputs and outputs are. Initially, you do not need to understand every little detail of the hardware—how every component or peripheral operates and what registers need to be programmed for particular functions.
Whenever you receive a new board, you should take some time to read the main documents provided with it. If the board is an off-the-shelf product, it might arrive with a “User’s Guide” or “Programmer’s Manual” that has been written for software developers. (The Arcom development kit, for example, includes this information as well as datasheets for all major components on the board.) However, if the board was custom designed for your project, the documentation might be more cryptic or may have been written mainly for the reference of the hardware designers. Either way, this is the single best place to start.
While you are reading the documentation, set the board itself aside. This will help you to focus on the big picture. There will be plenty of time to examine the actual board more closely when you have finished reading. Before picking up the board, you should be able to answer two basic questions about it:
  • What is the overall purpose of the board?
  • How does data flow through it?
For example, imagine that you are a software developer on a design team building a print server. You have just received an early prototype board from the hardware designers. The purpose of the board is to share a printer among several computers. The hardware reads data from a network connection and sends that data to a printer for output. The print server must mediate between the computers and decide which computer from the network gets to send data to the printer. Status information also flows in the opposite direction to the computers on the network.
Though the purpose of most systems is self-explanatory, the flow of the data might not be. We often find that a block diagram is helpful in achieving rapid comprehension. If you are lucky, the documentation provided with your hardware will contain a block diagram. However, you might also find it useful to create your own block diagram. That way, you can leave out hardware components that are unrelated to the basic flow of data through the system.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Hardware Basics
The next step in understanding the hardware is to take a look at the schematic. A schematic is a drawing comprised of standardized symbols to represent all of a circuit’s components and connections. The schematic gives the details of the hardware, showing the individual components represented in the block diagram, how the components are interconnected, and, most importantly, where to put the oscilloscope probe to see what’s going on with a particular circuit. On most projects, it is not necessary for you to understand how every electrical circuit on the board operates, but you do need to understand the basic operation of the hardware.
Along with the user’s guides or manuals for the board, it is also helpful to collect the datasheets for all major components on your board. The datasheet is a complete specification of a particular hardware component, including electrical, timing, and interface parameters.
Often the hardware engineer has already collected the datasheets; if so, your work is partly complete. You might want to take a look at the other information available for a particular component, because there are often separate hardware and software documents, especially for more complex devices. For example, a processor often includes a Programmer’s Guide in addition to the other literature. These documents can give you valuable information for using various features of the processor; they occationally even provide code snippets.
There are also application notes that address particular issues associated with a specific component. It is a good idea to look for any errata documents for all devices. The device’s errata will give you a heads-up on any issues regarding the way a device operates, and, more importantly, workarounds for these issues.
It’s a good idea to periodically check for updates of the board components’ information. This will save you the frustration of chasing a problem that was fixed in the latest datasheet. All of this information is an asset when you are trying to understand a new piece of hardware. You can generally find these documents on the manufacturer’s web site.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Examine the Landscape
It is often useful to put yourself in the processor’s shoes for a while. Imagine what it is like to be the processor. What does the processor’s world look like? Who is connected to it? How does it talk to these other devices?
If you think about it from this perspective, one thing you quickly realize is that the processor has a lot of compatriots. These are the other pieces of hardware on the board, with which the processor communicates. The processor has different methods for communicating with these other pieces of hardware. In this section, you will learn to recognize their names and addresses.
The first thing to notice is that there are two basic types of hardware to which processors connect: memories and peripherals. Memories are for the storage and retrieval of data and code. Peripherals are specialized hardware devices that either coordinate interaction with the outside world or perform a specific hardware function. For example, two of the most common peripherals in embedded systems are serial ports and timers.
Members of Intel’s 80x86 and some other processor families have two distinct address spaces through which they can communicate with these memories and peripherals. The first address space is called the memory space and is intended mainly for memory devices; the second is reserved exclusively for peripherals and is called the I/O space. However, peripherals can also be located within the memory space, at the discretion of the hardware designer. When that happens, we say that those peripherals are memory-mapped and that system has memory-mapped I/O. Some processors support only a memory space, in which case all peripherals are memory-mapped.
From the processor’s point of view, memory-mapped peripherals look and act very much like memory devices. However, the function of a peripheral is quite different from that of a memory device. Instead of simply storing the data that is provided to it, a peripheral might instead interpret it as a command or as data to be processed in some way.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Learn How to Communicate
Now that you know the names and addresses of the memory and peripherals attached to the processor, it is time to learn how to communicate with the peripherals. There are two basic communication techniques: polling and interrupts. In either case, the processor usually issues some sort of command to the device by writing—by way of the memory or I/O space—particular data values to particular addresses within the device, and then waits for the device to complete the assigned task. For example, the processor might ask a timer to count down from 1,000 to 0. Once the countdown begins, the processor is interested in just one thing: is the timer finished counting yet?
If polling is used, the processor repeatedly checks to see whether the task has been completed. This is analogous to the small child who repeatedly asks, “Are we there yet?” throughout a long trip. Like the child, the processor spends a large amount of otherwise useful time asking the question and getting a negative response. To implement polling in software, you need only create a loop that reads the status register of the device in question. Here is an example:
    do
    {
        /* Play games, read, listen to music, etc. */
        ...

        /* Poll to see if we're there yet. */
        status = areWeThereYet(  );

    } while (status == NO);
The second communication technique uses interrupts. An interrupt is an asynchronous electrical signal from a peripheral to the processor. Interrupts can be generated from peripherals external or internal to the processor, as well as by software.
When interrupts are used, the processor issues commands to the peripheral exactly as before, but then waits for an interrupt to signal completion of the assigned work. While the processor is waiting for the interrupt to arrive, it is free to continue working on other things. When the interrupt signal is asserted, the processor finishes its current instruction, temporarily sets aside its current work, and executes a small piece of software called the interrupt service routine
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Getting to Know the Processor
If you haven’t worked with the processor on your board before, you should take some time to get familiar with it now. This shouldn’t take very long if you do all of your programming in a high-level language such as C. You need to dig in and find out how particular peripherals of the processor work. Generally, to the user of a high-level language, most processors look and act pretty much the same. However, if you’ll be doing any assembly language programming, you need to familiarize yourself with the processor’s register architecture and instruction set.
Everything you need to know about the processor can be found in the databooks provided by the manufacturer. If you don’t have a databook or programmer’s guide for your processor already, you should obtain one immediately. If you are going to be a successful embedded systems programmer, you must be able to read databooks and get something out of them. Processor databooks are usually well written—as databooks go—so they are an ideal place to start. Begin by flipping through the databook and noting sections that are most relevant to the tasks at hand. Then go back and begin reading the processor overview section.
Things you’ll want to learn about the processor from its databook are:
  • What address does the processor jump to after a reset?
  • What is the state of the processor’s registers and peripherals at reset?
  • What is the proper sequence to program a peripheral’s registers?
  • Where should the interrupt vector table be located? Does it have to be located at a specific address in memory? If not, how does the processor know where to find it?
  • What is the format of the interrupt vector table? Is it just a table of pointers to ISR functions?
  • Are there any special interrupts—sometimes called traps—that are generated within the processor itself? Must an ISR be written to handle each of these?
  • How are interrupts enabled and disabled? Globally and individually?
  • How are interrupts acknowledged or cleared?
In addition to the processor databook, the Internet contains a wealth of information for embedded software developers. The manufacturer’s site is a great place to start. In addition, a search for a particular processor can yield oodles of useful information from fellow developers, including code snippets giving you exact details on how to write your software. Several newsgroups are also targeted toward embedded software development and toward specific processors.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Study the External Peripherals
At this point, you’ve studied every aspect of the new hardware except the external peripherals. These are the hardware devices that reside outside the processor chip and communicate with it by way of interrupts and I/O or memory-mapped registers.
Begin by making a list of the external peripherals. Depending on your application, this list might include LCD or keyboard controllers, analog-to-digital (A/D) converters, network interface chips, or custom application-specific integrated circuits (ASICs). In the case of the Arcom board, the list contains just two items: the SMSC Ethernet controller and the parallel port.
You should obtain a copy of the user’s manual or datasheet for each device on your list. At this early stage of the project, your goal in reading these documents is to understand the basic functions of the device. What does the device do? What registers are used to issue commands and receive the results? What do the various bits and larger fields within these registers mean? When, if ever, does the device generate interrupts? How are interrupts acknowledged or cleared at the device?
When you are designing the embedded software, you should try to break the program down along device lines. It is usually a good idea to associate a software module called a device driver with each of the external peripherals. A device driver is nothing more than a collection of software routines that control the operation of a specific peripheral and isolate the application software from the details of that particular hardware device. We’ll have a lot more to say about device drivers later on.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Initialize the Hardware
The final step in getting to know your new hardware is to write some initialization software. This is your best opportunity to develop a close working relationship with the hardware, especially if you will be developing the remainder of the software in a high-level language.
During hardware initialization, it may be impossible to avoid using assembly language. However, after completing this step, you will be ready to begin writing small programs.
If you are one of the first software engineers to work with a new board—especially a prototype—the hardware might not work as advertised. All processor-based boards require some amount of software testing to confirm the correctness of the hardware design and the proper functioning of the various peripherals. This puts you in an awkward position when something is not working properly. How do you know whether the hardware or your software is causing the problem? If you happen to be good with hardware or have access to a simulator, you might be able to construct some experiments to answer this question. Otherwise, you should probably ask a hardware engineer to join you in the lab for a joint debugging session.
The hardware initialization should be executed before the startup code described in Chapter 4. The code described there assumes that the hardware has already been initialized and concerns itself only with creating a proper runtime environment for high-level language programs. Figure 2-7 provides an overview of the entire initialization process, from processor reset through hardware initialization and C startup code to main.
Figure 2-7: The hardware and software initialization process
The first stage of the initialization process is the reset code. This is a small piece of assembly language (usually only two or three instructions) that the processor executes immediately after it is powered on or reset. The sole purpose of this code is to transfer control to the hardware initialization routine. The first instruction of the reset code must be placed at a specific location in memory, usually called the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Your First Embedded Program
ACHTUNG! Das machine is nicht fur gefingerpoken und mittengrabben. Ist easy schnappen der springenwerk, blowenfusen und corkenpoppen mit spitzensparken. Ist nicht fur gewerken by das dummkopfen. Das rubbernecken sightseeren keepen hands in das pockets. Relaxen und vatch das blinkenlights!
—Electronics Laboratory Sign
In this chapter, we’ll dive right into embedded programming by way of an example. Our example is similar in spirit to the “Hello, World!” example found in the beginning of most other programming books. We’ll discuss why we picked this particular program and point out the parts of it that are dependent on the target hardware. This chapter contains only the source code for the program. We’ll discuss how to create the executable and how to actually run it in the chapters that follow.
It seems that every programming book ever written begins with the same example—a program that prints “Hello, World!” on the user’s screen. An overused example such as this might seem a bit boring. Among other things, the example helps readers 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 for users of programming languages and computer platforms.
Based on the “Hello, World!” benchmark, embedded systems are among the most challenging computer platforms for programmers to work with. In some embedded systems, it might even be impossible to implement the “Hello, World!” program. And in those systems that are capable of supporting it, the printing of text strings is usually more of an endpoint than a beginning.
A principal 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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Hello, World!
It seems that every programming book ever written begins with the same example—a program that prints “Hello, World!” on the user’s screen. An overused example such as this might seem a bit boring. Among other things, the example helps readers 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 for users of programming languages and computer platforms.
Based on the “Hello, World!” benchmark, embedded systems are among the most challenging computer platforms for programmers to work with. In some embedded systems, it might even be impossible to implement the “Hello, World!” program. And in those systems that are capable of supporting it, the printing of text strings is usually more of an endpoint than a beginning.
A principal 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 our book-writing counterparts continue to use the “Hello, World! example is that implementing it is a no-brainer. This eliminates one of the variables in the case that the user’s program doesn’t work correctly the first time: it isn’t a bug in his code; rather, it is a problem with the development tools or process he 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 might not be available to them. These are the auxiliary functions—such as
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Blinking LED Program
Almost every embedded system that we’ve encountered in our respective careers has had at least one LED that could be controlled by software. If the hardware designer plans to leave the LED out of the circuit, lobby hard for getting one attached to a general-purpose I/O (GPIO) pin. As we will see later, this might be the most valuable debugging tool you have.
A popular substitute for the “Hello, World!” program is 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 code, 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.
Our first step is to learn how to control the green LED we want to toggle. On the Arcom board, the green LED is located on the add-on module shown in Figure 3-1. The green LED is labeled “LED2” on the add-on module. The Arcom board’s VIPER-Lite Technical Manual and the VIPER-I/O Technical Manual describe how the add-on module’s LEDs are connected to the processor. The schematics can also be used to trace the connection from the LED back to the processor, which is typically the method you need to use once you have your own hardware.
Figure 3-1: Arcom board add-on module containing the green LED
LED2 is controlled by the signal OUT2, as described in the LEDs section in the Arcom board’s VIPER-I/O Technical Manual. This text also informs us that the signals to the LEDs are inverted; therefore, when the output is high, the LEDs are off, and vice versa. The general-purpose I/O section of the VIPER Technical Manual shows that the OUT2 signal is controlled by the processor’s GPIO pin 22. Therefore, we will need to be able to set GPIO pin 22 alternately high and low to get our blinker program to function properly.
The superstructure of the Blinking LED program is shown next. This part of the program is hardware-independent. However, it relies on the hardware-dependent functions ledInit
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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 have an infinite loop. Typically, this loop surrounds a significant part 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 run only one piece of software. Although hardware is important, the system is not a digital watch or a cellular phone or a microwave oven without that 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.
If we had forgotten the infinite loop in the Blinking LED program, the LED would have simply changed state once.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 4: Compiling, Linking, and Locating
I consider that the golden rule requires that if I like a program I must share it with other people who like it. Software sellers want to divide the users and conquer them, making each user agree not to share with others. I refuse to break solidarity with other users in this way. I cannot in good conscience sign a nondisclosure agreement or a software license agreement. So that I can continue to use computers without dishonor, I have decided to put together a sufficient body of free software so that I will be able to get along without any software that is not free.
—Richard Stallman, Founder of the GNU Project The GNU Manifesto
In this chapter, we’ll examine the steps involved in preparing your software for execution on an embedded system. We’ll also discuss the associated development tools and see how to build the Blinking LED program shown in Chapter 3.
But before we get started, we want to make it clear that embedded systems programming is not substantially different from the programming you’ve done before. The only thing that has really changed is that you need to have an understanding of the target hardware platform. Furthermore, each target hardware platform is unique—for example, the method for communicating over a serial interface can vary from processor to processor and from platform to platform. Unfortunately, this uniqueness among hardware platforms leads to a lot of additional software complexity, and it’s also the reason you’ll need to be more aware of the software build process than ever before.
We focus on the use of open source software tools in this edition of the book. It’s wonderful that software developers have powerful operating systems and tools that are totally free and are available for exploring and altering. Open source solutions are very popular and provide tough competition for their commercial counterparts.
When build tools run on the same system as the program they produce, they can make a lot of assumptions about the system. This is typically not the case in embedded software development, where the build tools run on a
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Build Process
When build tools run on the same system as the program they produce, they can make a lot of assumptions about the system. This is typically not the case in embedded software development, where the build tools run on a host computer that differs from the target hardware platform. There are a lot of things that software development tools can do automatically when the target platform is well defined. This automation is possible because the tools can exploit features of the hardware and operating system on which your program will execute. For example, if all of your programs will be executed on IBM-compatible PCs running Windows, your compiler can automate—and, therefore, hide from your view—certain aspects of the software build process. Embedded software development tools, on the other hand, can rarely make assumptions about the target platform. Instead, the user must provide some of her own knowledge of the system to the tools by giving them more explicit instructions.
The process of converting the source code representation of your embedded software into an executable binary image involves three distinct steps:
  1. Each of the source files must be compiled or assembled into an object file.
  2. All of the object files that result from the first step must be linked together to produce a single object file, called the relocatable program.
  3. Physical memory addresses must be assigned to the relative offsets within the relocatable program in a process called relocation.
The result of the final step is a file containing an executable binary image that is ready to run on the embedded system.
The embedded software development process just described is illustrated in Figure 4-1. In this figure, the three steps are shown from top to bottom, with the tools that perform the steps shown in boxes that have rounded corners. Each of these development tools takes one or more files as input and produces a single output file. More specific information about these tools and the files they produce is provided in the sections that follow.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Building the Blinking LED Program
In this section, we show an example build procedure for the Arcom VIPER-Lite development board. If another hardware platform is used, a simlar process should be followed using the tools and conventions that accompany that hardware.
The installation procedure for the software development tools is provided in Appendix B. Once the tools are installed, the commands covered in the following sections are entered into a command shell. For Windows users, the command shell is a Cygwin bash shell (Cygwin is a Unix environment for Windows); for Linux users, it is a regular command shell.
In this and subsequent chapters, commands entered in a shell environment are indicated by the number sign (#) prompt. Commands entered in the RedBoot environment are indicated by the RedBoot prompt (RedBoot>).
We will next take a look at the individual commands in order to manually perform the three separate tasks (compiling, linking, and locating) described earlier in this chapter. Then we will learn how to automate the build procedure with makefiles.
As we have implemented it, the Blinking LED example consists of two source modules: led.c and blink.c. The first step in the build process is to compile these two files. The basic structure for the gcc compiler command is:
arm-elf-gcc [
                  options
               ] 
                  file
               ...
The command-line options we’ll need are:
-g
To generate debugging info in default format
-c
To compile and assemble but not link
-Wall
To enable most warning messages
-I../include
To look in the directory include for header files
Here are the actual commands for compiling the C source files:
# arm-elf-gcc –g -c –Wall -I../include led.c
# arm-elf-gcc -g –c -Wall -I../include blink.c
            
We broke up the compilation step into two separate commands, but you can compile the two files with one command. To use a single command, just put both of the source files after the options. If you wanted different options for one of the source files, you would need to compile it separately as just shown. For additional information about compiler options, take a look at
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
A Quick Look at Makefiles
You can imagine how tedious the build process could be if you had a large number of source code files for a particular project. Manually entering individual compiler and linker commands on the command line becomes tiresome very quickly. In order to avoid this, a makefile can be used. A makefile is a script that tells the make utility how to build a particular program. (The make utility is typically installed with the other GNU tools.) The make utility follows the rules in the makefile in order to automatically generate output files from a set of input source files.
Makefiles might be a bit of a pain to set up, but they can be a great timesaver and a very powerful tool when building project files over and over (and over) again. Having a sample available can reduce the pain of setting up a makefile.
The basic layout for a makefile build rule is:
target:   prerequisite
          command          
The target is what is going to be built, the prerequisite is a file that must exist before the target can be created, and the command is a shell command used to create the target. There can be multiple prerequisites on the target line (separated by white space) and/or multiple command lines. But be sure to put a tab, not spaces, at the beginning of every line containing a command.
Here’s a makefile for building our Blinking LED program:
XCC     = arm-elf-gcc
LD      = arm-elf-ld
CFLAGS  = -g -c -Wall \\
          -I../include
LDFLAGS = -Map blink.map -T viperlite.ld -N

all: blink.exe

led.o: led.c led.h
    $(XCC) $(CFLAGS) led.c

blink.o: blink.c led.h
    $(XCC) $(CFLAGS) blink.c

blink.exe: blink.o led.o viperlite.ld
    $(LD) $(LDFLAGS) -o $@ led.o blink.o

clean:
    -rm -f blink.exe *.o blink.map
The first four statements in this makefile contain variables for use in the makefile. The variable names are on the left side of the equal sign. In this makefile, the respective variables do the following:
XCC
Defines the compiler executable program
LD
Defines the linker executable program
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 5: Downloading and Debugging
I can remember the exact instant when I realized that a large part of my life from then on was going to be spent in finding mistakes in my own programs.
—Maurice Wilkes, Head of the Computer Laboratory of the University of Cambridge, 1959
Once you have an executable binary image stored as a file on the host computer, you will need a way to download that image to the embedded system and execute it. The executable binary image is usually loaded into a memory device on the target board and executed from there. And if you have the right tools at your disposal, it will be possible to set breakpoints in the program or to observe its execution in less intrusive ways. This chapter describes various techniques for downloading, executing, and debugging embedded software in general, as well as focuses on the techniques available on our development environment.
With most embedded systems, there are several means to get an image onto the target and run the program, some more challenging than others. In this section, we investigate the methods available for downloading the Blinking LED program onto the Arcom board, as well as some other methods that may be useful for other projects.
The software development cycle for a PC and an embedded system include many of the same stages. Figure 5-1 is a general diagram of the embedded software development cycle.
Figure 5-1: Software development cycle
As shown in Figure 5-1, the software development cycle begins with design and the first implementation of the code. After that, there are usually iterations of the build, download and debug, and bug-fixing stages. Because a lot of time is spent in these three stages, it helps to eliminate any kinks in this process so that the majority of time can be spent on debugging and testing the software. (This diagram does not take into account the handling of feature creep inevitably inflicted by the marketing department.)
Because this is a very basic diagram, other stages that may be necessary are profiling and optimization
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Downloading the Blinking LED Program
With most embedded systems, there are several means to get an image onto the target and run the program, some more challenging than others. In this section, we investigate the methods available for downloading the Blinking LED program onto the Arcom board, as well as some other methods that may be useful for other projects.
The software development cycle for a PC and an embedded system include many of the same stages. Figure 5-1 is a general diagram of the embedded software development cycle.
Figure 5-1: Software development cycle