For novel ideas about building embedded systems (both hardware and firmware), join the 27,000+ engineers who subscribe to The Embedded Muse, a free biweekly newsletter. The Muse has no hype and no vendor PR. It takes just a few seconds (just enter your email, which is shared with absolutely no one) to subscribe.
By Jack Ganssle
Hardware for Better Firmware
Published in Embedded Systems Programming June, 2003
In 1971 Intel shocked the industry by announcing their 4004, the first microprocessor. This little memory company (at the time) created the embedded industry more or less out of thin air.
Embedded Systems Programming magazine didn't exist; neither did Dr. Dobbs or any of the many publications now targeted at the microprocessor world. Publications like the now defunct Electronics magazine and EDN carried word of this new technology to their readers, most of whom were electronic engineers, not software people. The word "firmware" hadn't been invented.
EEs, though, quickly grasped the possibilities created by this new invention. It wasn't long before 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 agile 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 both for best 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 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 non-linear scan of IR frequencies beamed onto test sample. Our 1.3 MHz 8008 was, to say the least, limited in its ability to keep up with the 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 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 but 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 into 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 max capabilities. It was a perfect example of designing hardware simply for the sake of improving the quality of the code.
This industry changed in the 80s. Bigger processors and cheap memory 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, and which in some cases led to overly complex code.
So I'd like to make some suggestions for the hardware people, ideas that are simple to incorporate into the circuit board but which can greatly ease the task of building the code.
In the non-embedded 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 ISR, then drive it low when exiting. A scope or logic analyzer then immediately shows the code snippet's execution time.
Another trick is to cycle an output bit high when the system is busy, and low when idle. Connect a voltmeter to the pin, one of the old fashioned units with an analog needle. The meter will integrate the binary pulse stream, so the displayed voltage will be proportional to system loading.
If space and costs permit, include an entire 8 bit register connected to a row of .1 inch spaced vias or headers. Software state-machines can output their current "state" to this port. A logic analyzer captures the data and shows all of the sequencing, with nearly zero impact on the code's execution time.
At least one LED is needed to signal the developer- and perhaps even customers - that the system is alive and working. 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 OK" indication.
Bring the reset line to a switch or jumper, so 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 chip so it's possible to connect an emulator, if you're using one. 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 SMT adaptor might need 4 to 6 inches 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 a 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. The PCB will be a prototype forever, but 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 5 nsec or so in the read and write cycle - and especially in wait state circuits - does not impact 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, 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 is 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 busses 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. ALE (address latch enable), also known as AS (address strobe) on Motorola parts, distinguishes address from data on multiplexed busses. 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 very short a scope 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 these.
Be careful with bit ordering. If reading from an A/D, for instance, a bad design that flips bit 7 to input bit zero, 6 to 1, etc, is a nightmare. Sure, the firmware folks can write code to fix the mix-up, but most processors aren't good at this. The code will be slow and ugly.
Use many narrow I/O ports rather than a few wide ones. When a single port controls 3 LEDs, two interrupt masks, and a stepper motor, changing any output means managing every output. The code becomes a convoluted mess of ANDs/ORs. 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 this practice was verboten, since 74LS inputs were more susceptible to transients than the Vcc pin. 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 very 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.
Pullup 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 10k instead of 100k).
PCB silkscreens are oft-neglected debugging aids. Label switches and jumpers. Always denote pin 1 as there's no standard pin one position in the SMT world. And add tick-marks every 5 or 10 pins around big SMT packages, and indicate if pin numbers increase in a CW or CCW direction. Otherwise, finding pin 139 is a nightmare, especially for bifocal-wearing developers suffering from caffeine-induced tremors.
Key connectors so there's is no guessing about which way the cable is supposed to go.
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 these.
When the design is complete, check every input to every device and make absolutely sure each is connected to something - even 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 are often quite closely coupled to the firmware, making that, too, debug-unfriendly. It's easier to implement these completely in the code. Tools (e.g., 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 above will make a system easier to debug. Remember that a good design works; a great design is also one that's easy to debug.