Citadel Forums
Home
Company Information
Information Request
Linux How-to Guides
ADSP 21xx Digital Signal
Processing Tutorials
SW Utilities
On-line Order Form
Server Support
Have you found this site useful? Did we save you time? Did we cure your head-ache? Is your hair growing back now?
Please make a donation to help with maintenance.
|
Objective Real-Time Software on the ADSP21XX
Debugging Real-Time Code on the ADSP 21XX
General
Testing and debugging of real-time code can be very difficult. Asynchronous
interaction between differrent processes and high rate interrupt routines
can create strange side effects that only become aparant when the system
is running in real-time. Code that performs correctly in the simulator,
can fall apart when run concurrently with other processes. The addition
of small test routines to find these problems will not always help, since
the mere presence of these routines may change the timing in the system,
thereby masking the problem, or causing new ones.
What we need to enable us to debug code in real-time, is a system that
will allow us to run at full speed, until the processor reaches a certain
address, then stop and yield control. The address at which we are going
to stop to break execution, is known as a breakpoint. There are several
techniques to achieve this. Some use special hardware, while others use
only software.
Analog Devices do provide an Easy Ice kit, that provides a very respectable
debugger, but it requires the hardware to be designed to take advantage
of it. When an ICE port on the DSP card is not available, due to design
or cost constraints, one has to be a little more innovative. If there is
a serial port on the target card, that can interface to a terminal or PC,
you are home free.
A Mini Debugger
This debugger is so small, that one can link it into the
application software and leave it there, as a permanent feature.
|
The source code for the basic debugger discussed in this chapter, resides
on the CD-ROM in directory work\dbugger. An improved
version resides in directory work\dbugger2. The
better version can perform single stepping, display data memory and the
DAG registers, in addition to the basic functions, which uplifts it
from example status to quite useful. This debugger is so small, that
one can link it into the application software and leave it there, as a
permanent feature. It would however be wise to prevent the user from
accidentally gaining access to it, either through the use of a password,
or a hardware test select link.
A Software Debugger
In this chapter, we will develop a very small and simple software debugger,
which can be used to set two breakpoints in the code and display the common
registers. The advantage of a software only debugger, is of course cost.
Especially when we write it ourselves! The drawback is that it only works
while the processor runs properly. If the processor would crash, then we
are well and truly stuck and need to resort back to the Wonder Method.
The Wonder Method
The Wonder Method entails running and crashing a few nanoseconds
later and then wondering what happened, after which one makes a small change
and then start all over again. Not very efficient and rather frustrating,
but all too familiar right?
|
Well, fear no more! We shall overcome! and once you have learned these
techniques, it can be applied to any processor.
Set a Breakpoint
All this talk about breakpoints is OK, but how does one go about
setting a breakpoint? That happens to be the $64000 question (except
that you can get the answer right here for free).
There are quite a few techniques to create a breakpoint. All of them
involves making a small change (a.k.a. patch) to the code at the breakpoint
address. Of course, once the breakpoint is no longer required, one has
to fix it up again. We should also ensure that the instruction where the
breakpoint code is inserted, is preserved and executed, when we choose
to continue running, else things are sure to act funny. All of this is
of course only possible if one runs from RAM - fortunately that is usually
the case.
The most common break method involves the use of a special software
interrupt. Surprise! The ADSP doesn't have one. However, we can use any
unused interrupt and then force a software interrupt with the Interrupt
Force/Clear (IFC) register. What if we are already using all interrupts?
Never mind that, what if one wants to set a breakpoint inside an interrupt
routine and nesting is disabled? Maybe we should not use this method...
Another method is to insert a jump to the breakpoint handler in the
code. This is OK, except that we then don't have a clue where the processor
came from, so we would need dedicated breakpoint handlers for all breakpoints.
This method can be very simple, provided that the number of breakpoints
are kept small.
The third method, which is the one we are going to use, is to insert
a call statement at the breakpoint. This has the advantage that we can
look on the PC stack, to figure out where the processor came from and it
would be easy to continue. Simply issue a return, as with an ordinary procedure
call. However, in order to keep things simple, we are going to use dedicated
breakpoint handlers, as in the jump method. Accessing the PC stack and
using a fancy user interface can make things very complicated and is beyond
the scope of this book. (OK, I don't want to do that, since it is very
time consuming and frustrating to debug, but you are free to try it and
I'd like to have a copy when you are done...). Bear in mind that the
simpler the debugger, the easier it is to prove that it is unconditionally
correct, a very important attribute, since you don't want the debugger
to lead you off on a tangent.
Anti Bugging
The simpler the debugger, the easier it is to prove that
it is unconditionally correct.
|
In order to make a useful debugger, we need to be able to set at
least two breakpoints. This allows us to guard both outcomes of a decision
and see where the processor went - yea or nay. After this, we may either
want to continue running, in the hope of hitting the breakpoints again,
or we may want to set two new breakpoints somewhere else. It is also very
useful to be able to examine the registers when one arrives at a breakpoint,
to see what happened in the code.
Example 1 shows the code to set a breakpoint. First, there is a macro,
which pushes a large number of registers onto the stack. This is important,
since we do not want to disrupt the application program in any way. Then
we display a user prompt and obtain the required breakpoint address, which
is saved in a variable for future reference.
Example 1. Set a Breakpoint
/*********************************************************************
* Name: bug_set_break1
* Description: Set breakpoint 1
* Copy one code instruction into the breakpoint handler
* Copy the call template into the code
* Constraints: none
*********************************************************************/
bug_set_break1:
BUG_ENTER
ar = ^bug_b1; /* b1# */
call bug_out_string;
call bug_get_hex4; /* get the break address */
dm(bug_break_addr1) = ar;
i4 = ar; /* point to break */
m4 = 0;
modify(i4, m4);
i5 = ^bug_break_code1; /* point to break handler */
m5 = 0;
modify(i5, m5);
ar = pm(i4, m4); /* save break instruction in handler */
pm(i5, m5) = ar;
i5 = ^bug_call_code1; /* point to call template */
m5 = 0;
modify(i5, m5);
ar = pm(i5, m5); /* save call template at break */
pm(i4, m4) = ar;
BUG_EXIT
rts;
/*
* Call template 1
*/
bug_call_code1:
call bug_at_break1;
|
What we do next, is to copy a single instruction at the breakpoint address,
to the dedicated breakpoint handler, for later execution and restoration.
We do this, by overwriting a nop, which we purposely inserted
there as a place holder. Thereafter, we copy a call template instruction
into the code at the breakpoint address. This links the application code
to the handler and the handler back to the application code. That's it!
QED, quite easily done (fortunately, few people understand Latin these
days, or I'll get stoned due to this silly pun...), except that we still
need a breakpoint handler.
Handle a Breakpoint
The bug_set_break function links the application breakpoint
to the bug_at_break handler. It also inserts the instruction
that was removed from the application at the end of the breakpoint handler,
for execution immediately before the return, but after all registers were
restored to the way they were originally before the fun started. Example
2 shows the dedicated breakpoint handler.
Example 2. Breakpoint Handler
/*********************************************************************
* Name: bug_at_break1
* Description: Display arrival at breakpoint 1
* Allow setting of new breakpoint or continue
* execution with the saved instruction, then return.
* Constraints: none
*********************************************************************/
bug_at_break1:
BUG_ENTER
call bug_regs; /* display the registers before dbuggering them up */
ar = ^bug_crlf;
call bug_out_string;
ar = dm(bug_break_addr1);
call bug_out_hex4;
ar = ^bug_at1; /* @1? = Breakpoint reached prompt */
call bug_out_string;
bug_at_get1:
call ser_char_in;
if eq jump bug_at_get1;
call ser_char_out;
ay1 = 0x72; /* r = just run */
af = ar - ay1;
if eq jump bug_at_run1;
ay1 = 0x62; /* b = set new breakpoint */
af = ar - ay1;
if eq jump bug_at_brk1;
jump bug_at_run1; /* bad command = just run anyway */
bug_at_brk1:
nop;
i4 = ^bug_break_old1; /* point to old break handler */
nop;
m4 = 0;
nop;
l4 = 0;
nop;
modify(i4, m4);
nop;
i5 = ^bug_break_code1; /* point to break handler */
nop;
m5 = 0;
nop;
l5 = 0;
nop;
modify(i5, m5);
nop;
ar = pm(i5, m5); /* save old break instruction in handler */
nop;
pm(i4, m4) = ar;
nop;
call bug_clr_break1; /* set new breakpoints and then run */
call bug_clr_break2;
call bug_set_break1;
call bug_set_break2;
BUG_EXIT
bug_break_old1:
nop; /* execute the old break code and continue */
rts;
bug_at_run1:
BUG_EXIT
bug_break_code1: /* execute the new break code and continue */
nop; /* Code instruction is copied in here */
rts;
|
This routine starts off by saving the complete processor context, as
usual, after which it displays all the common registers, to allow us to
see what is happening at the breakpoint. This is essentially the whole
purpose of the exercise. It then continues with a simple user command line
interface, offering a choice to continue running, or to set two new breakpoints.
Not very sophisticated, but nevertheless very powerful. (Unfortunately,
being a processor with a rather large register file, the register you are
interested in will always be one which is not shown!)
At this stage, to continue running is easy, simply restore the context
from the stack, execute the saved instruction and issue a return. If we
want to set a new breakpoint, things get a bit tricky again.
The problem is that in order to set a new breakpoint, we have to
repair the old one and then set the new one, however, we still have to
execute the previously saved instruction before we return. We do not
want to execute the newly saved instruction, since that one belongs somewhere
else in the execution thread. In order to handle this funny case, we created
the procedure with two different exit points. (This is usually an absolute
no-no. A procedure should have only one entry and exit point!). In this
case, being special test code, it is probably excusable to have two exit
points. H.Acker would have been proud...
Breakpoints Old and New
In order to set a new breakpoint, we have to repair the
old one and then set the new one, however, we still have to execute the
previously saved instruction before we return.
|
What we do here, is to first copy the old saved instruction to the second
exit point, then clear the breakpoints and finally set new ones. We can
then execute the saved instruction and return to the instruction thread
immediately after the previous breakpoint.
This example does not have such niceties as the ability to modify registers
or dump memory. However, it is extremely helpful as is and adding special
features to the debugger is straight forward once the breakpoint handling
is reliable. You can even use the basic debugger, to debug a new and improved
debugger, thereby pulling yourself up by your own bootstraps. (See directory
work\dbugger2.)
Getting Started
This is all very nice, but you may have wondered how to wrench control
over the system, in order to set the very first breakpoint. It is also
important to be able to regain control when the system missed both breakpoints
and ran away from you. This is done with a dedicated procedure, which can
be linked into the terminal screen and activated upon a special key press,
for instance CTRL-B = 02H. Example 3, shows the debugger
command menu procedure.
Example 3. Debugger Command Menu
/*********************************************************************
* Name: bug_cmd
* Description: Main dBugger menu
* Constraints: Activate this procedure from a terminal screen, using
* a special key, e.g.: CTRL-B
*********************************************************************/
bug_cmd:
BUG_ENTER
call bug_regs; /* display the registers before dbuggering them up */
ar = ^bug_crlf;
call bug_out_string;
ar = 0x3F; /* ? Prompt */
call ser_char_out;
bug_mnu_get1:
call ser_char_in;
if eq jump bug_mnu_get1;
call ser_char_out;
ay1 = 0x62; /* b = enter breakpoint number 1/2*/
af = ar - ay1;
if eq jump bug_mnu_set;
ay1 = 0x63; /* c = clear all breakpoints - take care! */
af = ar - ay1; /* it will clear them even if they were not set */
if eq jump bug_mnu_clr; /* which will write garbage into the code */
jump bug_mnu_exit;
bug_mnu_clr:
call bug_clr_break; /* c = clear both*/
jump bug_mnu_exit;
bug_mnu_set:
call bug_set_break1; /* set both breakpoints */
call bug_set_break2;
bug_mnu_exit:
BUG_EXIT
rts;
|
If this function is linked to a special key, you can view the registers
and set a pair of breakpoints. Viewing the registers at this point will
not be very interesting, since they will always be the same! At least,
they should be. If you find that the stack pointer (i7)
is different every time, you have a serious problem in your code, so it
may be useful to keep an eye on it.
If you have not set breakpoints before, you can now set them. If you
have set ones before and the system ran away, you can clear the breakpoints
and then set new ones. However, you should not clear the breakpoints
if nothing was set before. This debugger is very crude, with no error
checks. That keeps it very simple and dangerous.
Simple and Dangerous
You should not clear the breakpoints if nothing was set
before.
|
By the way, how does one get the addresses to set the breakpoints
at!? Easy. The linker creates a symbol file .sym, which
contains all the labels and their addresses. (The same information is also
in the .map file). You can use a text editor and a simple
macro, to search the symbol file when you click on a label in a source
file. Intermediate addresses can be obtained by counting the lines down
from a known address, since each line of assembler code is one instruction.
Note that the use of macros is quite troublesome, since it is not clear
how many instructions are in them. You can either add a label after an
annoying macro, or consult the list file in the obj directory,
to figure out the address. The list file shows the address offset from
the start of the module, so the Win95 calculator is required. It may be
a good idea to write a symbol lookup program to automate the process and
make it less painful. (I use an editor and a simple macro. When I highlight
a symbol in the source file and press CTRL-=, the editor jumps to the start
of the symbol file and executes a text search.)
Support Functions
Now we get to the boring part, which I specialy kept till last. Example
4 shows all the remaining parts of the debugger.
Example 4. Support Functions
/*********************************************************************
* Name: bug_regs
* Description: Crude display of common registers
* Constraints: none
*********************************************************************/
bug_regs:
BUG_ENTER
si = ar; /* save ar */
ar = ^bug_crlf; /* carriage return */
call bug_out_string;
ar = ^bug_ar;
call bug_out_string;
ar = si; /* get ar */
call bug_out_hex4; /* ar value in hex */
ar = ^bug_af;
call bug_out_string;
ar = pass af;
call bug_out_hex4; /* af value in hex */
ar = ^bug_ax0;
call bug_out_string;
ar = ax0;
call bug_out_hex4; /* ax0 value in hex */
ar = ^bug_ax1;
call bug_out_string;
ar = ax1;
call bug_out_hex4; /* ax1 value in hex */
ar = ^bug_ay0;
call bug_out_string;
ar = ay0;
call bug_out_hex4; /* ay0 value in hex */
ar = ^bug_ay1;
call bug_out_string;
ar = ay1;
call bug_out_hex4; /* ay1 value in hex */
ar = ^bug_sp;
call bug_out_string;
ar = i7;
call bug_out_hex4; /* sp (i7) value in hex */
BUG_EXIT
rts;
/*********************************************************************
* Name: bug_get_hex
* Description: Get a single hex digit
* return the digit in ar
* Constraints: none
*********************************************************************/
bug_get_hex:
MAC_ENTER
bug_get:
call ser_char_in;
if eq jump bug_get;
call ser_char_out;
ay1 = 0x61; /* a */
af = ar - ay1;
if lt jump bug_get_upper;
ay1 = 0x20; /* convert to upper case */
ar = ar - ay1;
bug_get_upper:
ay1 = 0x41; /* A */
af = ar - ay1;
if lt jump bug_get_num;
ay1 = 0x37; /* A+10 */
ar = ar - ay1;
jump bug_get_exit;
bug_get_num:
ay1 = 0x30; /* 0 */
ar = ar - ay1;
bug_get_exit:
MAC_EXIT
rts;
/*********************************************************************
* Name: bug_get_hex4
* Description: Get a 4 digit hex address
* return the address in ar
* Constraints: none
*********************************************************************/
bug_get_hex4:
MAC_ENTER
call bug_get_hex;
sr = LSHIFT ar by 12 (LO);
call bug_get_hex;
sr = sr OR LSHIFT ar by 8 (LO);
call bug_get_hex;
sr = sr OR LSHIFT ar by 4 (LO);
call bug_get_hex;
sr = sr OR LSHIFT ar by 0 (LO);
ar = sr0;
MAC_EXIT
rts;
/*********************************************************************
* Name: bug_out_hex
* Description: Output a single hex digit
* pass the digit in ar
* Constraints: none
*********************************************************************/
bug_out_hex:
MAC_ENTER
ay1 = 0x0A; /* A */
af = ar - ay1;
if ge jump bug_out_a;
ay1 = 0x30; /* make numeric */
ar = ar + ay1;
jump bug_out_exit;
bug_out_a:
ay1 = 0x37; /* make alpha */
ar = ar + ay1;
bug_out_exit:
call ser_char_out; /* output the hex digit */
MAC_EXIT
rts;
/*********************************************************************
* Name: bug_out_hex4
* Description: Output a 4 digit hex address
* pass the address in ar
* Constraints: none
*********************************************************************/
bug_out_hex4:
MAC_ENTER
sr0 = ar; /* load ar */
ay1 = 0x000F; /* nibble mask */
cntr = 4;
do bug_out until ce;
sr = LSHIFT sr0 by 4 (LO); /* output MSB first */
ar = sr1 AND ay1;
call bug_out_hex; /* output a nibble */
nop;
bug_out: nop;
MAC_EXIT
rts;
/*********************************************************************
* Name: bug_out_string
* Description: Output a C style zero terminated string
* pass the string address in ar
* Constraints: none
*********************************************************************/
bug_out_string:
MAC_ENTER
i1 = ar; /* create a string pointer */
m1 = 0;
modify(i1, m1);
m1 = 1;
ax1 = BUG_STRING_MAX;
bug_out_string_next:
ar = dm(i1, m1); /* get a character */
af = pass ar; /* set the flags */
if eq jump bug_out_string_exit; /* quit when zero */
call ser_char_out; /* output the character */
ar = ax0 - 1;
ax0 = ar;
if eq jump bug_out_string_exit;
jump bug_out_string_next;
bug_out_string_exit:
MAC_EXIT
rts;
|
Remember that the user supplied single character in/out functions are
still missing. These can be simple polled, or interrupt driven functions.
Note that the whole debugger is interruptable, but not re-entrant, due
to the globally saved breakpoint addresses. The result is that one can
set breakpoints almost anywhere, including inside Interrupt Service Routines,
the exception being the serial I/O interrupt routine which is used for
the character I/O.
Finally, to complete the picture, remember that there are two individual
breakpoint handlers and you will also need the set of macros in example
5, to get the debugger working.
Example 5. Macros and other Definitions
/************************ Macros and Literals ************************/
/***************************** Literals ******************************/
#define MAC_DOWN -1 /* Push Down */
#define MAC_UP 1 /* Pop Up */
#define MAC_STOP 0 /* Don't increment */
#define MAC_OFFSET 1 /* Parameter Offset */
/****************************** Macros ******************************/
/*
* Macro: MAC_RD_DM
* Description: Read a data memory array
* Parameters: addr = DM address (^TABLE)
* index = DM index (ar)
* Constraints: m2 = modified
* i2 = modified
* Returns: data (ar)
*/
#define MAC_RD_DM(data, addr, index) \
i2 = addr; /* get the table address */ \
m2 = index; /* get the table index */ \
modify(i2, m2); /* post modify i1 */ \
data = dm(i2, m2); /* read the value */
/*
* Macro: MAC_WR_DM
* Description: Write to a data memory array
* Parameters: addr = DM address (^TABLE)
* data = data to write to DM (ar)
* index = DM index (ay1)
* Constraints: m2 = modified
* i2 = modified
* Returns: none
*/
#define MAC_WR_DM(data, addr, index) \
i2 = addr; /* get the DM table address */ \
m2 = index; /* get the table index */ \
modify(i2, m2); /* post modify i1 */ \
dm(i2, m2) = data; /* save the value */
/*
* Macro: MAC_ENTER
* Description: Save a bunch of commonly used registers
*/
#define MAC_ENTER \
m7 = MAC_DOWN; /* Push down */ \
dm(i7, m7) = ax0; /* save common registers */ \
dm(i7, m7) = ax1; \
dm(i7, m7) = ay0; \
dm(i7, m7) = ay1; \
dm(i7, m7) = mr0; \
dm(i7, m7) = mr1;
/*
* Macro: MAC_EXIT
* Description: Restore a bunch of commonly used registers
*/
#define MAC_EXIT \
m7 = MAC_UP; /* Pop up */ \
modify(i7, m7); /* i7 is one too far down, move up */ \
mr1 = dm(i7, m7); /* restore common registers */ \
mr0 = dm(i7, m7); \
ay1 = dm(i7, m7); \
ay0 = dm(i7, m7); \
ax1 = dm(i7, m7); \
m7 = MAC_STOP; \
ax0 = dm(i7, m7);
/*
* Macro: MAC_BUG_ENTER
* Description: Save a bunch of commonly used registers
* to ensure that the dBugger will not upset
* normal operations.
*/
#define BUG_ENTER \
m7 = MAC_DOWN; /* Push down */ \
dm(i7, m7) = ax0; /* save common registers */ \
dm(i7, m7) = ax1; \
dm(i7, m7) = ay0; \
dm(i7, m7) = ay1; \
dm(i7, m7) = mr0; \
dm(i7, m7) = mr1; \
dm(i7, m7) = ar; \
ar = pass af; \
dm(i7, m7) = ar; \
dm(i7, m7) = si; \
dm(i7, m7) = sr0; \
dm(i7, m7) = sr1; \
si = m4; \
dm(i7, m7) = si; \
si = i4; \
dm(i7, m7) = si; \
si = m5; \
dm(i7, m7) = si; \
si = i5; \
dm(i7, m7) = si;
/*
* Macro: BUG_EXIT
* Description: Restore a bunch of commonly used registers
* to ensure that the dBugger will not upset
* normal operations.
*/
#define BUG_EXIT \
m7 = MAC_UP; /* Pop up */ \
modify(i7, m7); /* i7 is one too far down, move up */ \
si = dm(i7, m7); \
i5 = si; \
si = dm(i7, m7); \
m5 = si; \
si = dm(i7, m7); \
i4 = si; \
si = dm(i7, m7); \
m4 = si; \
sr1 = dm(i7, m7); \
sr0 = dm(i7, m7); \
si = dm(i7, m7); \
ar = dm(i7, m7); \
af = pass ar; \
ar = dm(i7, m7); \
mr1 = dm(i7, m7); \
mr0 = dm(i7, m7); \
ay1 = dm(i7, m7); \
ay0 = dm(i7, m7); \
ax1 = dm(i7, m7); \
m7 = MAC_STOP; \
ax0 = dm(i7, m7);
#define BUG_STRING_MAX 0x32 /* Maximum string length */
/*************************** Constants ******************************/
/*
* Strings are defined in PM and copied to DM at runtime
*/
.VAR/RAM/DM/seg=INT_INIT bug_crlf[3],
bug_ar[4],
bug_af[5],
bug_ax0[6],
bug_ax1[6],
bug_ay0[6],
bug_ay1[6],
bug_sp[6], /* The Stack Pointer is i7 */
bug_at1[4],
bug_at2[4],
bug_b1[3],
bug_b2[5];
.VAR/RAM/PM/seg=INT_CONST BUG_CRLF[3],
BUG_AR[4],
BUG_AF[5],
BUG_AX0[6],
BUG_AX1[6],
BUG_AY0[6],
BUG_AY1[6],
BUG_SP[6], /* The Stack Pointer is i7 */
BUG_AT1[4],
BUG_AT2[4],
BUG_B1[3],
BUG_B2[5];
.INIT BUG_CRLF : 0x000D00, 0x000A00, 0x000000; /* CR, LF */
.INIT BUG_AR : 0x004100, 0x005200, 0x003D00, 0x000000; /* AR= */
.INIT BUG_AF : 0x002000, 0x004100, 0x004600, 0x003D00, 0x000000; /* AF= */
.INIT BUG_AX0 : 0x002000, 0x004100, 0x005800, 0x003000, 0x003D00, 0x000000; /* AX0= */
.INIT BUG_AX1 : 0x002000, 0x004100, 0x005800, 0x003100, 0x003D00, 0x000000; /* AX1= */
.INIT BUG_AY0 : 0x002000, 0x004100, 0x005900, 0x003000, 0x003D00, 0x000000; /* AY0= */
.INIT BUG_AY1 : 0x002000, 0x004100, 0x005900, 0x003100, 0x003D00, 0x000000; /* AY1= */
.INIT BUG_SP : 0x002000, 0x005300, 0x005000, 0x003D00, 0x000000; /* SP= */
.INIT BUG_AT1 : 0x004000, 0x003100, 0x003F00, 0x000000; /* @1? */
.INIT BUG_AT2 : 0x004000, 0x003200, 0x003F00, 0x000000; /* @2? */
.INIT BUG_B1 : 0x003100, 0x002300, 0x000000; /* 1# */
.INIT BUG_B2 : 0x002000, 0x006200, 0x003200, 0x002300, 0x000000; /* b2# */
/*************************** Variables ******************************/
.VAR/RAM/DM bug_break_addr1, /* Save breakpoint addresses */
bug_break_addr2;
|
Serial Communication
So far, we have omitted reference to all the serial port input/output
functions. The full code of the debugger is in subdirectory work\dbug
on the CD-ROM and includes everything except the two functions
required to input and output a single character over a serial port,
ser_char_out and ser_char_in. These will
be different for each application and needs to be user supplied.
User Supplied Funtions
Only two user supplied funtions: ser_char_out
and ser_char_in.
|
Otherwise, the debugger is fully functional and set up in a directory,
with a batch file to build a simple LED flasher system. The code will compile
correctly as is, with the two stub functions. All you have to do, is add
some serial interface code to file ser.dsp and off you
go.
|
|