EMBEDDED SYSTEMS
An embedded system is a special-purpose computer
system usually built into a smaller device. An embedded system is required to
meet very different requirements than a general-purpose personal
computer.
Examples of embedded systems
§ automatic
teller machines (ATMs)
§ cellular
telephones and telephone
switches
§ computer
network equipment, including routers,
timeservers
and firewalls
§ disk
drives (floppy disk drives and hard disk drives)
§ engine
controllers and antilock
brake controllers for automobiles
§ home
automation products, like thermostats,
air
conditioners, sprinklers,
and security
monitoring systems
§ handheld
calculators
§ household
appliances,
including microwave
ovens, washing
machines, television
sets, DVD
players/recorders
§ inertial
guidance systems, flight control hardware/software and other integrated
systems in aircraft
and missiles
§ measurement
equipment such as digital storage oscilloscopes,
logic
analyzers, and spectrum
analyzers
§ multifunction
wristwatches
§ personal
digital assistants (PDAs), i.e. small handheld computers with PIMs
and other applications
§ programmable
logic controllers (PLCs) for industrial automation and monitoring
§ stationary
videogame
consoles and handheld
game consoles
Two major areas of differences are cost and power
consumption. Since many embedded systems are produced in the tens of thousands
to millions of units range, reducing cost is a major concern. Embedded systems
often use a (relatively) slow processor and small memory size to minimize costs.
The slowness is not just clock speed. The whole
architecture of the computer is often intentionally simplified to lower costs.
For example, embedded systems often use peripherals controlled by synchronous
serial interfaces, which are ten to hundreds of times slower than comparable
peripherals used in PCs.
Programs on an embedded system often must run
with real-time
constraints with limited hardware
resources: often there is no disk drive, operating system, keyboard or screen. A
flash drive
may replace rotating media, and a small keypad and LCD
screen may be used instead of a PC's keyboard and screen.
Firmware
is the name for software
that is embedded in hardware devices, e.g. in one or more ROM/Flash
memory IC
chips.
Embedded systems are routinely expected to
maintain 100% realibility while running continuously for long periods, sometimes
measured in years. Firmware
is usually developed and tested to much stricter requirements than is general
purpose software
which can usually be easily restarted if a problem occurs.
There are many different CPU
architectures used in embedded designs. This in contrast to the desktop
computer market, which as of this writing (2003) is limited to just a few
competing architectures, mainly the Intel/AMD
x86, and the Apple/Motorola/IBM
PowerPC, used
in the Apple
Macintosh.
One common configuration for embedded systems is
the system on a chip, an application-specific
integrated circuit, for which the CPU was purchased as intellectual property
to add to the IC's design.
Like a typical computer programmer, embedded
system designers use compilers,
assemblers
and debuggers
to develop an embedded system.
Those software tools can come from several
sources:
§ Software
companies that specialize in the embedded market
§ Ported
from the GNU
software development tools. (cross-compiler: http://www.kegel.com/linux/embed/
)
§ Sometimes,
development tools for a personal computer can be used if the embedded processor
is a close relative to a common PC processor.
Embedded system designers also use a few software
tools rarely used by typical computer programmers.
§ Some
designers keep a utility program to turn data files into code, so that they can
include any kind of data in a program.
§ Most
designers also have utility programs to add a checksum or CRC
to a program, so it can check its program data before executing it.
They often have no operating
system, or a specialized embedded
operating system (often a real-time
operating system), or the programmer is assigned to port one of these to the
new system.
Debugging
is usually performed with an in-circuit
emulator, or some type of debugger that can interrupt
the microcontroller's internal microcode.
The microcode interrupt lets the debugger operate
in hardware in which only the CPU works. The CPU-based debugger can be used to
test and debug the electronics of the computer from the viewpoint of the CPU.
This feature was pioneered on the PDP-11.
Developers should insist on debugging which shows
the high-level language, with breakpoints
and single-stepping,
because these features are widely available. Also, developers should write and
use simple logging facilities to debug sequences of real-time events.
PC or mainframe programmers first encountering
this sort of programming often become confused about design priorities and
acceptable methods. Mentoring, code-reviews and egoless programming are
recommended.
Autonetics
D-17 guidance computer from a Minuteman I missile.
The first recognizably modern embedded system was
the Apollo
Guidance Computer, developed by Charles
Stark Draper and the MIT
Instrumentation Laboratory. Each flight to the moon had two. They ran the inertial
guidance systems of both the command module and LEM.
At the project's inception, the apollo guidance
computer was considered the riskiest item in the apollo project.
The first mass-produced embedded system was the
guidance computer for the Minuteman
missile. It also used integrated circuits, and was the first volume user of
them. Without this program, integrated circuits might never have reached a
usable price-point.
The crucial design features of the Minuteman
computer were that its guidance algorithm could be reprogrammed later in the
program, to make the missile more accurate, and the computer could also test the
missile, saving cable and connector weight.
The electronics usually uses either a microprocessor
or a microcontroller.
Some large or old systems use general-purpose mainframe
computers or minicomputers.
All embedded systems have start-up code. Usually
it disables interrupts, sets up the electronics, tests the computer (RAM, CPU
and software), and then starts the application code. Many embedded systems
recover from short-term power failures by restarting (without recent
self-tests). Restart times under a tenth of a second are common.
Many designers have found one of more hardware
plus software-controlled LEDs
useful to indicate errors during development (and in some instances, after
product release, to produce troubleshooting
diagnostics). A common scheme is to have the electronics turn off the LED(s) at
reset, whereupon the software turns it on at the first opportunity, to prove
that the hardware and start-up software have performed their job so far. After
that, the software blinks the LED(s) or sets up light patterns during normal
operation, to indicate program execution progress and/or errors. This serves to
reassure most technicians/engineers and some users.
There are several basically different types of
software architectures in common use.
In this design, the software simply has a loop.
The loop calls subroutines. Each subroutine manages a part of the hardware or
software. Interrupts generally set flags, or update counters that are read by
the rest of the software.
A simple API disables and enables interrupts.
Done right, it handles nested calls in nested subroutines, and restores the
preceding interrupt state in the outermost enable. This is one of the simplest
methods of creating an exokernel.
Typically, there's some sort of subroutine in the
loop to manage a list of software timers, using a periodic real time interrupt.
When a timer expires, an associated subroutine is run, or flag is set.
Any expected hardware event should be backed-up
with a software timer. Hardware events fail about once in a trillion times.
That's about once a year with modern hardware. With a million mass-produced
devices, leaving out a software timer is a business disaster.
State machines may be implemented with a
function-pointer per state-machine (in C++, C or assembly, anyway). A change of
state stores a different function into the pointer. The function pointer is
executed every time the loop runs.
Many designers recommend reading each IO device
once per loop, and storing the result so the logic acts on consistent values.
Many designers prefer to design their state
machines to check only one or two things per state. Usually this is a hardware
event, and a software timer.
Designers recommend that hierarchical state
machines should run the lower-level state machines before the higher, so the
higher run with accurate information.
Complex functions like internal combustion
controls are often handled with multi-dimensional tables. Instead of complex
calculations, the code looks up the values. The software can interpolate between
entries, to keep the tables small and cheap.
One major weakness of this system is that it does
not guarantee a time to respond to any particular hardware event.
Careful coding can easily assure that nothing
disables interrupts for long. Thus interrupt code can run at very precise
timings.
Another major weakness of this system is that it
can become complex to add new features. Algorithms that take a long time to run
must be carefully broken down so only a little piece gets done each time through
the main loop.
This system's strength is its simplicity, and on
small pieces of software the loop is usually so fast that nobody cares that it
is not predictable.
Another advantage is that this system guarantees
that the software will run. There is no mysterious operating system to blame for
bad behavior.
Nonpreemptive multitasking
This system is very similar to the above, except
that the loop is hidden in an API.
One defines a series of tasks, and each task gets its own subroutine stack.
Then, when a task is idle, it calls an idle routine (usually called
"pause", "wait", "yield", or etc.).
An architecture with similar properties is to
have an event queue, and have a loop that removes events and calls subroutines
based on a field in the queue-entry.
The advantages and disadvantages are very similar
to the control loop, except that adding new software is easier. One simply
writes a new task, or adds to the queue-interpreter.
Take any of the above systems, but add a timer
system that runs subroutines from a timer interrupt. This adds completely new
capabilities to the system. For the first time, the timer routines can occur at
a guaranteed time.
Also, for the first time, the code can step on
its own data structures at unexpected times. The timer routines must be treated
with the same care as interrupt routines.
Take the above nonpreemptive task system, and run
it from a preemptive timer or other interrupts.
Suddenly the system is quite different. Any piece
of task code can damage the data of another task—they must be precisely
separated. Access to shared data must be rigidly controlled by some
synchronization strategy, for example message queues or semaphores. (Recently non-blocking
synchronization strategies have been developed).
Often, at this stage, the developing organization
buys a real-time
operating system. This can be a wise decision if the organization lacks
people with the skills to write one, or if the port of the operating system to
the hardware will be used in several products. Otherwise, be aware that it
usually adds six to eight weeks to the schedule, and forever after programmers
can blame delays on it.
These are popular for embedded projects that have
no systems budget. In the opinion of at least one author of this article, they
are usually a mistake. Here's the logic.
Operating systems are specially-packaged
libraries of reusable code. If the code does something useful, the designer
saves time and money. If not, it's worthless.
Operating systems for business systems lack
interfaces to embedded hardware. For example, if one uses Linux to write a motor
controller or telephone switch, most of the real control operations end up as
numbered functions in an IOCTL call. Meanwhile, the normal read, write, fseek,
interface is purposeless. So the operating system actually interferes with
development.
Office style operating systems protect the
hardware from user programs. That is, they interfere with embedded systems
development profoundly.
Since most embedded systems do not perform office
work, most of the code of an office operating systems is wasted. For example,
most embedded systems never use a file system or screen, so file system and GUI
logic is wasted.
Operating systems must invariably be ported to an
embedded system. That is, the hardware driver code must always be written
anyway. Since this is the most difficult part of the operating system, little is
saved by using one.
Last, the genuinely useful, portable features of
operating systems are small pieces of code. For example, a basic TCP/IP
interface is about 3,000 lines of C code. Likewise, a simple file system. So, if
a design needs these, they can be had for less than 10% of the typical embedded
system's development budget, without a royalty, just by writing them. Also, if
the needed code is sufficiently generic, the back of embedded systems magazines
typically have vendors selling royalty-free C implementation.
A notable, beloved exception to all of these
objections is DOS for an IBM-PC. If you use a single-card computer, the BIOS is
done, thus no drivers. DOS permits code to write to hardware. Finally, DOS
doesn't do much, so it's compact.
Some systems require safe, timely, reliable or
efficient behavior unobtainable with the above architectures. There are
well-known tricks to construct these systems:
Hire a real system programmer. They cost a little
more, but can save years of debugging, and the associated loss of revenue.
RMA, rate monotonic analysis, can be used to find
whether a set of tasks can run under a defined hardware system. In its simplest
form, the designer assures that the quickest-finishing tasks have the highest
priorities, and that on average, the CPU has at least 30% of its time free.
Harmonic tasks optimize CPU efficiency.
Basically, designers assure that everything runs from a heartbeat timer. It's
hard to do this with a real-time
operating system, because these usually switch tasks when they wait for an
I/O device.
Systems with exactly two levels of priority
(usually running, and interrupts-disabled) cannot have inverted
priorities, in which a lower priority task waits for a higher-priority task
to release a semaphore or other resource.
Systems with monitors can't have deadlocks. A monitor
locks a region of code from interrupts or other preemption. If the monitor is
only applied to small, fast pieces of code, this can work acceptably well.
This means that systems that use dual priority
and monitors are safe and reliable because they lack both deadlocks and priority
inversion. If they use harmonic tasks, they can even be fairly efficient.
However, RMA can't characterize these systems, and levels of priority had better
not exist anywhere, including in the operating system.
User interfaces for embedded systems vary wildly,
and thus deserve some special comment.
Designers recommend testing the user interface
for usability at the earliest possible instant. A quick, dirty test is to ask an
executive secretary to use cardboard models drawn with magic markers, and
manipulated by an engineer. The videotaped result is likely to be both humorous
and very educational. In the tapes, every time the engineer talks, the interface
has failed: It would cause a service call.
Exactly one person should approve the user
interface. Ideally, this should be a customer, the major distributor or someone
directly responsible for selling the system. The decisionmaker should be able to
decide. The problem is that a committee will never make up its mind, and neither
will some people. Not doing this causes avoidable, expensive delays. A usability
test is more important than any number of opinions.
Interface designers at PARC,
Apple
Computer, Boeing
and HP
minimize the number of types of user actions. For example, use two buttons (the
absolute minimum) to control a menu system (just to be clear, one button should
be "next menu entry" the other button should be "select this menu
entry"). A touch-screen or screen-edge buttons also minimize the types of
user actions.
Another basic trick is to minimize and simplfy
the type of output. Designs should consider using a status light for each
interface plug, or failure condition, to tell what failed. A cheap variation is
to have two light bars with a printed matrix of errors that they select- the
user can glue on the labels for the language that she speaks.
For example, Boeing's standard test interface is
a button and some lights. When you press the button, all the lights turn on.
When you release the button, the lights with failures stay on. The labels are in
Basic English.
For another example, look at a small computer
printer. You might have one next to your computer. Notice that the lights are
labelled with stick-on labels that can be printed in any language. Really look
at it.
Designers use colors. Red means the users can get
hurt- think of blood. Yellow means something might be wrong. Green means
everything's OK.
Another essential trick is to make any modes
absolutely clear on the user's display.
If an interface has modes, they must be
reversible in an obvious way.
Most designers prefer the display to respond to
the user. The display should change immediately after a user action. If the
machine is going to do anything, it should start within 7 seconds, or give
progress reports.
If a design needs a screen, many designers use
plain text. It can be sold as a temporary expedient. Why is it better than
pictures? Users have been reading signs for years. A GUI
is pretty and can do anything, but typically adds a year from artist, approval
and translator delays and one or two programmers to a project's cost, without
adding any value. Often, a clever GUI
actually confuses users.
If a design needs to point to parts of the
machine (as in copiers), these are labelled with numbers on the actual machine,
that are visible with the doors closed.
A network interface is just a remote screen. It
needs the same caution as any other user interface.
One of the most successful general-purpose
screen-based interfaces is the two menu buttons and a line of text in the user's
native language. It's used in pagers, medium-priced printers, network switches,
and other medium-priced situations that require complex behavior from users.
When there's text, there are languages. The
default language should be the one most widely understood. Right now this is
English. French and Spanish follow.
Most designers recommend that one use the native
character sets, no matter how painful. People with peculiar character sets feel
coddled and loved when their language shows up on machinery they use.
Text should be translated by professional
translators, even if native speakers are on staff. Marketing staff have to be
able to tell foreign distributors that the translations are professional.
A foreign organization should give the
highest-volume distributor the duty to review and correct any translations in
his native language. This stops critiques by other native speakers, who tend to
believe that no foreign organization will ever know their language as well as
they.