Aerospace



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


bonk

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.



Copyright © 1996-2008, Aerospace Software Ltd., GPL.