The chasm between hardware and software engineers has been widening since the 1980s. These tips may help narrow the gap.
In 1971, Intel shocked the electronics industry by announcing their 4004, the first microprocessor. Thus did a little memory company create the embedded industry more or less out of thin air.
Embedded Systems Programming magazine wouldn't exist for another 18 years; nor would any of the other publications now targeted at the embedded world. Publications like EDN and the now-defunct Electronics magazine carried word of this new technology to their readers, most of whom were electronic engineers, not software people. The word “firmware” hadn't even been invented.
Electrical engineers quickly grasped the possibilities created by this new invention. And it wasn't long before numerous products were developed using the 4004 and its successor, the 8008. Virtually all of the development, both hardware and software, was done by EEs. It was common for a single engineer to design the board and write the code.
Today, various development methods stress the benefits of working in small teams. Back in the '70s, one additional benefit was apparent: the designer, being so intimate with both the hardware and the firmware, could, and did, optimize each for better system performance. No hardware decision was made without considering its impact on the code, and where a bit of extra logic could drastically simplify firmware, well, that hardware was added without a second thought.
I remember working on a scanning monochrometer designed by Dick Hayden, the most brilliant logic designer I ever met. Flat interference filters rotated in a beam of light to create a quite nonlinear scan of infrared frequencies beamed onto a test sample. Our 1.3MHz 8008 was, to say the least, limited in its ability to keep up with the analog-to-digital (A/D) converter that sampled the photosensor. An analysis showed that we could probably write code that could keep up, but the system would be so loaded that other housekeeping would suffer. Dick created a stunning state machine so intricate in its complexity that no other engineer really ever understood all of its implications. Yet, in a synchronous design comprising just a handful of chips, he created a circuit that modeled the system's mechanical motion, initiating A/D reads when appropriate and parking the data in a FIFO made from RAM chips. That clever bit of hardware allowed us to write well-organized firmware, code that wasn't an abysmal mess tuned explicitly for sucking in data at the processor's maximum capabilities. It was a perfect example of hardware designed for the sake of improving the quality of the software.
This industry changed in the '80s. Bigger processors and cheaper memories led to products bloated with features. Embedded development largely fragmented into discrete hardware and software groups. As the wall between these engineers grew, the firmware folks often found they had little say in the hardware design and were forced to accept designs that were practically impossible to debug, designs that in some cases led to overly complex software.
Twenty years on, I'd like to make some suggestions for the hardware people, ideas that are simple to incorporate into the circuit board and can greatly ease the task of building the code.
In the nonembedded world, a favorite debugging trick is to seed print statements into the code. These tell the programmer if the execution stream ever got to the point of the print. But firmware people rarely have this option. So add a handful of unassigned parallel I/O bits. The firmware people desperately need these as a cheap way to instrument their code. Seeding I/O instructions into the code that drives these outputs is a simple and fast way to see what the program is doing.
Developers can assert a bit when entering a routine or interupt service routine, then drive it low when exiting. A scope or logic analyzer then immediately shows that code's execution time.
Another trick is to cycle an output bit high when the system is busy and low when idle. Connect a voltmeterone of the old-fashioned units with an analog needleto the pin. The meter will integrate the binary pulse stream, so the displayed voltage will be proportional to system loading.
If space and costs permit, connect an entire 8-bit I/O port to a header. Software state machines can output their current “state” to this port. A logic analyzer can capture the data and show all of the sequencing, with nearly zero impact on the code's execution time.
At least one LED is needed to signal the developerand perhaps even customersthat the system is alive and working, generally by flashing a 1Hz rate. It's a confidence indicator driven by a low-priority task or idle loop, which shows the system is alive and not stuck somewhere in an infinite loop. A lot of embedded systems have no user interface; a blinking LED can be a simple “system okay” indication.
Highly integrated CPUs now offer a lot of on-chip peripherals, sometimes more than we need in a particular system. If there's an extra UART, connect the pins to an RS-232 level shifting chip (such as the MAX232). There's no need to actually load the chip onto the board except for prototyping. The firmware developers may find themselves in a corner where their tools just aren't adequate, and they'll then want to add a debug monitor (see www.simtel.com) to the code. The RS-232 port makes this possible and easy.
If PCB real estate is so limited that there's no room for the level shifter, then at least bring Tx, Rx, and ground to accessible vias so it's possible to suspend a MAX232 on green wires above the circuit board.
Bring the reset signal out to a switch or jumper so that engineers can assert the signal independently of the normal power-up reset. Power-up problems can sometimes be isolated by connecting reset to a pulse generator, creating a repeatable scenario that's easy to study with an oscilloscope.
Orient the CPU in-circuit so that it's possible to connect an emulator. Sometimes the target board is so buried inside of a cabinet that access is limited at best. Most emulator pods have form factors that favor a particular direction of insertion. Watch out for vertical clearance, too! A pod stacked atop a large surface mount (SMT) adaptor might need 4 to 6 in. of space above the board. Be sure there's nothing over top of the board that will interfere with the pod.
Don't use a “clip-on” adaptor on an SMT package. They are just not reliable (the one exception is PLCC packages, which have a large lead pitch). A butterfly waving its wings in Brazil creates enough air movement to topple the thing over. Better, remove the CPU and install a soldered-down adaptor on the prototype. At least it will be a reliable prototype.
Leave margin in the system's timing. If every nanosecond is accounted for, no emulator will work reliably. An extra 5ns or so in the read and write cycleand especially in wait state circuitsdoes not affect most designs.
If your processor has a BDM or JTAG debug port, be sure to add the appropriate connector on the PCB. Even if you're planning to use a full-blown emulator or some other development tool instead, at least add PCB pads and wiring for the BDM connector. The connector's cost approaches zero and may save a project suffering from tool woes.
A logic analyzer is a fantastic debugging tool, yet always a source of tremendous frustration. By the time you've finished connecting 100 clip leads, the first 50 have popped off. There's a better solution: surround your CPU with AMP's Mictor connectors. These are high-density, controlled impedance parts that can propagate the system's address, data, and control buses off-board. Both Tektronix and Agilent support the Mictor. Both companies sell cables that lead directly from the logic analyzer to a Mictor. No clip leads, no need to make custom cables, and a guaranteed reliable connection in just seconds. Remove the connectors from production versions of the board, or just leave the PCB pads without loading the parts.
Some signals are especially prone to distortion when we connect tools. Address latch enable (ALE), also known as address strobe (AS) on Motorola parts, distinguishes address from data on multiplexed buses. The tiniest bit of noise induced from an emulator or even a probe on this signal will cause the system to crash. Ditto for any edge-triggered interrupt input (like NMI on many CPUs). Terminate these signals with a twin-resistor network. Though your design may be perfect without the terminations, connecting tools and probing signals may corrupt the signals.
Add test points! Unless its ground connection is short, an oscilloscope cannot accurately display the high-speed signals endemic to our modern designs. In the good old days, it was easy to solder a bit of wire to a logic device's pins to create an instant ground connection. With SMT, this is either difficult or impossible, so distribute plenty of accessible ground points around the board.
Other signals we'll probe a lot, and which must be accessible, include clock, read, write, and all interrupt inputs. Make sure these either have test points or each has a via of sufficient size a developer can solder a wire (usually a resistor lead) to the signal.
Do add a Vcc test point. Logic probes are old but still very useful tools. Most need a power connection.
Make all output ports readable. This is especially true for control registers in ASICs as there's no way to probe them.
Be careful with bit ordering. If reading from an A/D, for instance, a bad design that flips bit 7 to input bit 0, 6 to 1, and so on, is a nightmare. Sure, the firmware folks can write code to fix the mix-up, but most processors aren't good at this. The resulting hack will be slow and ugly.
Use many narrow I/O ports rather than a few wide ones. When a single port controls three LEDs, two interrupt masks, and a stepper motor, changing any output means managing every output. The code becomes a convoluted mess of AN s and OR s. Any small hardware change requires a lot of software tuning. Wide ports do minimize part counts when implemented using discrete logic, but inside a PLD or FPGA there's no cost advantage.
Avoid tying unused digital inputs directly to Vcc . In the olden days, since 74LS inputs were more susceptible to transients than Vcc , all unused inputs went to Vcc via resistor pull-ups. That's no longer needed with logic devices, but is still a good practice. It's much easier to probe and change a node that's not hardwired to power.
However, if you must connect power directly to these unused inputs, be careful with the PCB layout. Don't run power through a pin; that is, don't use the pin as a convenient way to get the supply to the other pins, or to the other side of the board. It's much better to carefully run all power and ground connections to input signals as tracks on the PCB's outside layers that are visible when the IC is soldered in place. Then developers can easily cut the tracks with an X-Acto knife and make changes.
Pull-up resistors bring their own challenges. Many debugging tools have their own pull-ups, which can bias nodes oddly. It's best to use lower values rather than the high ones permitted by CMOS (say 10,000 Ω instead of 100,000 Ω).
PCB silkscreens are oft-neglected debugging aids. Label switches and jumpers. Always denote pin 1 on a part, since there's no standard pin 1 position in the SMT world. And add tick-marks every five or 10 pins around big SMT packages. Also indicate if pin numbers increase in a clockwise or counterclockwise direction. Otherwise, finding pin 139 is a nightmare, especially for bifocal-wearing developers suffering from caffeine-induced tremors.
Key connectors so there's no guessing about which way the cable is supposed to be installed
Please add comments to your schematic diagrams! For all off-page routes, indicate what page the route goes to. Don't hide the pin numbers associated with power and ground, explicitly label them instead.
When the design is complete, check every input to every device and make absolutely sure each is connected to somethingeven if it's not used. I have seen hundreds of systems fail in the field because an unused input drifted to an asserted state. You may expect the software folks to mask these off in the code, but that's not always possible; and even when it is, it's often forgotten.
Try to avoid hardware state machines. They're hard to debug and often quite closely coupled to the firmware, making that, too, debug-unfriendly. It's easier to implement state machines completely in the code. Tools (such as VisualState from IAR) can automatically generate the state machine code.
Embedded systems are a blend of hardware and software. Each must complement the other. Hardware people can make the firmware easier to implement.
Many of the suggestions in this column will make a system easier to debug. Remember that a good design works; a great design is also one that's easy to debug.
Jack G. Ganssle is a lecturer and consultant on embedded development issues. He conducts seminars on embedded systems and helps companies with their embedded challenges. Contact him at .