| |
|
April 23, 1998
Debugging embedded systems:
using hardware tricks to trace program flow
Stuart R Ball
If your embedded system is too small to provide even a trace buffer or a
serial port as debugging aids, you can use a variety of hardware tricks to observe your
program's activity.
A trace buffer is a valuable debugging aid. It provides a history of your
program's behavior by recording explanatory "action codes" whenever your program
reaches certain key points in its execution (Reference 1).
In some small µC-based designs, however, you might not have enough memory to implement a
trace buffer. Likewise, your system may not have a serial port that could aid debugging by
transferring data back and forth between the system and a PC. You can still track your
program's execution flow in such a minimal system, however. One way is by writing data to
an unused memory or I/O location and capturing it on a logic analyzer in state mode. The
analyzer's memory, in effect, serves as a trace buffer.
An illustration of tracing with
80188-based hardware appears in Figure 1. A 74ACT138
3-to-8-line decoder decodes the low order address from a 74ACT373 latch. PCS2 and WR (from
the 80188) enable the 74ACT138. On the 80188, the PCSx lines are active for 128 contiguous
addresses, and they may map into either memory or I/O space.
For purposes of illustration, assume
that PCS2 is active (low) for all I/O addresses in the range 100 to 17F hex, or 512 to 639
decimal. As shown, the circuit uses Y0, Y1, and Y2, leaving the remaining lines free. Y7
provides a signal called TRACE WR that doesn't connect to any system hardware, but can
serve as a strobe to a logic analyzer's clock input. Because the 74ACT138 decodes lines A3
through A5, Y7 is active at addresses 138 to 13F hex. If the software writes action codes
to address 138, and if Y7 (TRACE WR) is connected to a logic analyzer's clock input (Figure 2), the logic analyzer can pick up the codes and
store them.
Time tags and other data
Of course, you might want to store more than action codes in some cases,
just as you might if you had a trace buffer in your embedded system's memory. Time tags,
for example, can provide valuable information for debugging. Fortunately, most modern
logic analyzers can time stamp captured states, so your software doesn't have to. And you,
in turn, don't have to keep track of trace-buffer memory pointers, which you would need to
do if you were mixing action codes that stand alone and action codes that have time tags
associated with them. Of course, you still need a way to distinguish action codes and data
if the data contains anything other than time information.
A slight change in the logic-analyzer connection can make it easy to
separate action codes and data. To see how, note that in Figure 1,
the ACT138 decodes only A3, A4, and A5 (a partial decode) of the address bus, so each line
is active for more than one address. For example, Y0 (pin 15) is active for addresses 100
to 107, and again from 140 to 147, 180 to 187, and 1C0 to 1C7. Table
1 shows the address ranges for which the ACT138 decoder's outputs become active.
Remember that, on the 80188, PCSx lines are active for 128 contiguous addresses.
Figure 3 shows how you can
connect a logic analyzer to take advantage of this situation. As in Figure
2, Y7 serves as a diagnostic strobe, connected to the logic analyzer's clock
input. Data capture still occurs on the analyzer's pod 1. However, Figure
3 adds A0 from the 80188 address latch to the logic analyzer, connecting it to
channel 8. Now, when you want to write an action code, you write it to address 138 (hex)
as before. But, if you want to write additional diagnostic data, you write it to address
139 (hex). Figure 3 includes an example display.
With the connections of Figure 3, writing
to address 138 toggles the TRACE WR signal, clocking data into the analyzer as with the
connections of Figure 2. Writing to address 139 also
toggles the TRACE WR signal and clocks data into the analyzer, but with the A0 line
high. As a result, you can tell the difference between an action code and an action code's
accompanying data. You can even attach more than two bytes of data with selected action
codes, because you can always find the action code corresponding to a stream of data
bytes. Just look backward in the analyzer buffer for the write with A0=0. Note that,
instead of connecting A0 to the analyzer and writing action codes and data to 138 and 139,
respectively, you can achieve the same result by connecting A6 to the analyzer and writing
action codes and data to 138 and 178, respectively.
The concept of writing to unused addresses will work with any processor
that uses an external data bus and decodes addresses to generate write strobes to external
hardware. Sometimes, though, there is no address-decode logic or there are no unused
strobes. Or, perhaps, some other situation prevents you from using a decoded write strobe
to trigger your analyzer. In this case, there are other tricks you can use to generate a
strobe for trace data. One way is by writing to ROM.
Using ROM space
In most embedded systems, the only thing in the ROM space is the ROM
itself. However, the address-decoding logic often does not differentiate between a read
and a write, so if you write your trace data to the ROM space, it usually will not affect
any hardware. Use caution, though. Make sure the address-decode logic doesn't turn on the
ROM outputs when you write to that address range, or you'll have bus contention. And, if
you're using a nonvolatile memory such as EEPROM, be sure that a write to the ROM space
won't change the contents of the memory.
In the partial schematic of Figure 4, the circled logic gates decode a write to ROM
space and generate a strobe to clock trace data into an analyzer. As before, the trace
clock has the label TRACE WR. The circled logic could be a permanent part of your
circuit board, or you could connect it to your board with a DIP clip when necessary. When
using the write-to-ROM scheme for Motorola-type processors, which need an ACK signal to
terminate the cycle, make sure the PROM decoding logic generates the ACK on both reads and
writes.
On some microcontrollers, such as the
8031, you can't write to ROM space. However, if you're not using all the ROM space, you
can accomplish your objective by reading from ROM. In Figure 5,
the 27256 EPROM requires 32k of the 64k address space, so you can decode any read from the
upper 32k (A15=1) as a trace output. The circled gates in Figure
5 provide logic for generating a logic-analyzer write strobe from a read-from-ROM
operation. Listing 1 shows the necessary
program code. Note that the logic analyzer takes trace data from the low order address
lines.
Whatever scheme you use to generate trace-output signals, you can simplify
hookup by adding a connector for these signals to your board's design. A 10-pin inline
header works well with 8-bit processors; a dual-row header is good for 16-bit designs.
Eight (or 16) pins connect to the data lines, one connects the write strobe, and one
serves as ground. You can also add an edge connector on one side of the board. If board
space is at a premium, you can add a row of pads and access the test points with a fixture
containing spring-loaded "pogo pins" of the type used in bed-of-nails testers.
Another debugging aid is the use of
LEDs as status indicators (Figure 6). The LEDs
typically connect to a register output (in the figure, pin 19 of a 74AC374 flip-flop) or a
port pin. When connecting an LED to some µPs, you need to add a drive transistor, because
the µP's output sink current is insufficient for adequate LED brightness. In the figure,
an LED connects to pin 8 of an 8031 through a MOSFET. If you use a bipolar LED, as shown
connected to pins 2 and 5 of the 74AC374 in the figure, the LED can glow either red or
green to indicate different status conditions:
| Pin 5 |
Pin 2 |
LED color |
0 |
0 |
off |
0 |
1 |
green |
1 |
0 |
red |
1 |
1 |
off |
If the pins toggle quickly between the 10 and 01 states,
the LED can also display an amber color.
A single software-controlled LED on a
board can serve as a good/bad indicator--useful for indicating which board in a
malfunctioning system needs replacing. Often, though, you'll want a more detailed idea of
what went wrong. One way to supply this additional information is to flash an error code
on an LED. Listing 2 shows how to do this on an 8031,
although the technique will work for any processor.
Listing 2's code turns an LED on for
about one second as a start indicator, then flashes the LED quickly to indicate the most
significant error-code digit and then more slowly to indicate the least significant digit.
To read a two-digit error code, look for the start indication, count the slow flashes, and
then count the fast flashes. (For ease in counting and remembering the flashes, implement
error codes in which neither digit has a value greater than five.) Listing
2's 8031 code is suitable as a display-and-hang error handler. If you want the
processor to keep running, servicing interrupts, or communicating with a display, you can
implement the LED flashes as part of a regular tick interrupt service routine.
The software listings in this article are available by clicking
here:
download 09msdesp2 |
Reference
Ball, Stuart, "Debugging embedded
systems: using a trace buffer to see what went wrong," EDN, April
9, 1998, pg 161.
This article, one of an occasional series on basic debugging techniques,
is an adaptation from the book, Debugging
Embedded Microprocessor Systems by Stuart R Ball. Material reproduced
courtesy of Newnes, an imprint of Butterworth-Heinemann, 225
Wildwood Ave, Woburn, MA 01801-2041. For more information, check www.bh.com. To order, call 1-800-366-2665.
|