manufacturer of I/O-rich SBCs, operator interfaces, handheld instruments, and development tools for embedded control low cost single board computers, embedded controllers, and operator interfaces for scientific instruments & industrial control development tools for embedded control order our low cost I/O-rich embedded control products embedded controller manufacturer profile single board computers & embedded controllers development tools & starter kits for your embedded design operator interfaces with touchscreens and graphical user interface plug-in expansion modules for digital & analog I/O C language & Forth language integrated development tools, IDE single board and embedded computer accessories embedded controller enclosures, bezels, environmental gaskets

The C Programmer’s Guide to the Mosaic Handheld

Table of Contents

PART 1 GETTING STARTED

Introduction. How to Use This Manual

Chapter 1: Getting to Know Your Handheld Instrument

Chapter 2: Powering Your Handheld

PART 2 PROGRAMMING THE MOSAIC HANDHELD

Chapter 3: Your First Program

Chapter 4: The IDE: Writing, Compiling, Downloading and Debugging Programs

Writing Programs

Using the Editor/Compiler

Sylistic Conventions

Accessing the Standard (Kernel) Library Functions

Initializing Variables

Compiling Programs

Using the Interactive Debugger

Overview of the Forth Language and Programming Environment

Displaying the Values of Static Variables

Use Type Keywords To Interactively Call C Functions

Displaying the Values of FORTH_ARRAY Elements

Assigning Values to Static Variables and FORTH_ARRAY Elements

Under the Hood of the QED-Forth Interactive Debugger

The Handheld Kernel vs. Prior Kernels

Chapter 5: Making Effective Use of Memory

Chapter 6: Real Time Programming

Chapter 7: Failure and Run-Time Error Recovery

Chapter 8: Programming the Graphical User Interface

PART 3 COMMUNICATIONS, MEASUREMENT, AND CONTROL

Chapter 9: Digital and Timer-Controlled I/O

Chapter 10: Data Acquisition

Chapter 11: Serial Communications

Chapter 12: The Battery-Backed Real Time Clock

Chapter 13: Customizing the Handheld's I/O

PART 4: REFERENCE DATA

Appendix A: GUI Function Reference

Appendix B: Handheld Schematics

Chapter 4

<< Previous | Next>>

The IDE: Writing, Compiling, Downloading and Debugging Programs

In this Chapter we’ll explore the Handheld’s tools for writing, editing, downloading and debugging your application program.  You’ll learn:

How to efficiently use the editor and compiler to write and compile both short and long programs;

Coding and file-naming conventions;

How to access the Handheld’s onboard funcitons; and,

How to interactively debug your programs.

Writing Programs

Using the Editor/Compiler

In the prior Chapter we introduced the Mosaic IDE and the tools you can use to edit, compile, and download your program. Briefly, the text pad toolbar provides the following buttons:

The Debug Tool, designated by the “Magnifying Glass” icon, invokes the Control-C compiler – Forth programmers do not need to use it because Forth programs are compiled as they are downloaded onto the Handheld.  The Debug Tool invokes the compiler and assembler only – it does not produce downloadable code.  Use it to quickly check the syntax of a program and find compile errors.

The Sngle-Page Compile Tool, designated by the “Single-Page” icon is also for C programmers.  It controls how C programs are linked and stored in memory. Forth programmers do not need to use it.  The Single-Page Compile Tool performs a standard, single-page memory model build of your program for programs that do not exceed 32 Kbytes of memory.  If you see the following warning printed in the compiler output, then you must switch to the multi-page memory model build (Multi-Page Make icon):

WARNING:  Input section “.doubleword” from ‘progname.o11’ is not used !

Forth programmers will also not need the Multi-Page Compile Tool, designated by the “Multi-Page” icon, which assists in compiling long C programs.  The Multi-Page Compile Tool invokes the C compiler’s multi-page build mode for long programs.  Still, each source code file should be less than 32 Kbytes worth of compiled source code or the above warning will be issued and the program will not run.

The Terminal icon launches the communications program, Mosaic Terminal, which is used to download your compiled program to the Handheld.

Sylistic Conventions

Code Comments

At the top of the GETSTART.C GETSTART.4TH source code file are some comments that tell what the program does.  At the top of the source code file are some comments that tell what the program does.  The \ character means that the remainder of the line is a comment.  Note that the editor colors all comments differently to make it easy to distinguish comments from source code.  Forth keywords are also colored differently than user-defined routines.  You can change the default colors if you like.  Single- or multi-line comments can be enclosed in the standard

 

/*    */

delimiters.  The double-slash

 

//

token means that the remainder of the line is a comment.  Note that the editor colors all comments differently to make it easy to distinguish comments from source code.  C keywords are also colored differently than user-defined routines.  You can change the default colors if you like.

Style Conventions

The example programs on your CD-ROM follow several stylistic conventions.  Here is a brief summary:

  Macros and constants are spelled with CAPITAL_LETTERS.

  Variable names are spelled with small_letters.

  Function names use both capital and small letters, with underscores capital letters indicating the start of a new subword within the function name.  For example:

 

: Save_Circle_Parameters ( -- )

 

void SaveCircleParameters(void)

To minimize the need to skip from one file to another, we have decided not to group all #define statements in a header file that is separate from the program being compiled.  Rather, the #define statements are defined close to where they are used in the program file that is being compiled.

File Naming Conventions

For backward compatibility with DOS and Windows 3.1, all filenames have 8 or fewer characters.  C source code files have the .C extension, and header files have the .H extension.  When you use the Make Tool utility to compile a source code file with the filename,

NAME.C

several files with the extensions shown in Table 4‑1 are created:

Table 4‑1      Files Created by the C Compiler.

FILENAME.ext

Description

NAME.C

Source code text file created by you, the programmer

NAME.A11

Assembled output text file created by C11 compiler

NAME.O11

Object code binary file created by ASM11 assembler

NAME.LCF

Linker command text file created by CC or CCM batch file

NAME.S

S-record (raw download ascii file) created by linker

NAME.DLF

Final download file created by CC or CCM batch file; includes S records and definitions for QED-Forth

NAME.MAP

Map file listing created by linker

NAME.MEM

Symbols map file

NAME.USE

Memory usage summary

NAME.BAK

Backup file sometimes created by the editor

While this list may seem overwhelming, you won’t have to worry about most of these files.  You’ll create your NAME.C and .H source code and header files in a directory of your choice, run the automated Make Tool by clicking on the Make icon, and send the resulting NAME.DLF download file to the Handheld using the Terminal program. In fact, unless you tell it otherwise, the editor’s “File” menu will typically show you only files with the .C and .H extensions (“Source Files”); you won’t have to wade through the files with the other extensions.  Similarly, the Mosaic Terminal typically lists only files with the .DLF extension, so it will be easy to select the download file to send to the Handheld.

Using Function Prototypes

This stylistic convention deserves its own section.  We strongly urge that you define or prototype each function before it is called.  If the compiler generates a warning that a function has been called without a prototype, we recommend that you check your source code and insert the required function prototype, or move the definition of the function so that it is defined before it is called. 

A prototype is a declaration that specifies the function name and the types of its return value and input parameters.  For example, the following is a valid function prototype:

 

_Q float CalcArea( unsigned int radius);

This declaration specifies that CalcArea() is a function that expects one unsigned integer input and returns a floating point value.  As discussed below, the _Q tags the function as one that is interactively callable during debugging. The CalcArea() function can then be defined later in the source code file.  If a function is prototyped in one file and defined in another, add the extern specifer before the prototype.

You can preface any function prototype with the _Q tag if you want to interactively call the function from the terminal.  The Handheld’s onboard operating system maintains a list, called the dictionary, of the names of functions tagged with the _Q so that it can recognize them when you send a command line from your terminal.

Prototype and Declare the Parameter Types of Every Function

Defining or prototyping a function before it is called allows the compiler to help find parameter passing errors, and it also prevents unnecessary promotion of parameters that can render the code slower and defeat the Handheld’s interactive function-calling capability.  To avoid unwanted promotion and runtime errors, each and every parameter in the function prototype or function definition must be preceded with a type specifier.  For example, leaving out the unsigned int keywords in the prototype for CalcArea() above would lead to promotion of the input parameter, possibly resulting in a runtime error message from the compiler or linker. 

Using function prototypes and definitions that explicitly specify the type of each and every input and output parameter results in more readable and reliable code.

Accessing the Standard (Kernel) Library Functions

The command

 

#include < \mosaic\allqed.h >

near the top of the GETSTART.C file is a preprocessor directive that includes all of the relevant header files for the Handheld, and all of the standard C header files (such as stdio.h, math.h, float.h, string.h, etc.)  We strongly recommend that this statement be placed at the top of each C program file that you write.  It gives you access to all of the pre-coded library routines that reside on the Handheld.  These routines let you control the A/D converters, digital I/O, serial ports, real-time clock, and many other useful functions.  The ALLQED.H file also gives you access to the Handheld’s multitasking and paged memory capabilities, as well as the standard ANSI C library functions including printing and string conversion routines such as printf() and sprintf(). Including these files is very efficient; it generates almost no additional runtime code while giving you access to very powerful capabilities.

You can call any of these functions from within your C code.  There is one limitation however:

Do not nest functions of the type _forth.

Many functions that are callable from C are actually of the _forth type.  This includes functions that are in the kernel on the Handheld, or are part of software distributions such as the Graphical User Interface (GUI) Toolkit.  A call to one of these _forth functions may not be made from within the parameter list of a call to another _forth function.

There is always a straightforward way of avoiding such nesting of function calls: simply use a variable to hold the required intermediate return value/parameter.  For example, if you need to use the _forth function FetchChar() to fetch the first character from the extended address returned by the _forth function DisplayBuffer() in paged memory, you could execute the following statements:

 

static xaddr buffer_xaddress = DisplayBuffer( );

FetchChar( buffer_xaddress);

This code is correct, while nesting the call to DisplayBuffer() inside the parameter list of FetchChar() would be incorrect.

Initializing Variables

Caution:
RAM-Resident Variables & Arrays Must Be Initialized Within Functions

A common mistake made when creating application programs for embedded systems is the use of compile-time initialization for RAM-based quantities such as variables and arrays.  While this approach of initializing quantities outside of function definitions may work during program development, it fails when the device goes into production because the variables and arrays are not properly initialized when power is cycled. 

Only run-time initialization, i.e., initializations that are performed within functions (which are in turn called by the autostart program), will occur reliably in an embedded application.

Even users with battery-backed RAM in their systems should always perform initializations within functions.  This approach will avoid hard-to-diagnose field failures that result from corrupted data in a battery-backed RAM that is never re-initialized to valid values.

Feel free to call Mosaic Industries for help with this or other programming issues.

Compiling Programs

Compiling Multiple Source Code Files

When writing large programs it is often useful to break up the program into multiple source code files.  The Make Tool allows you to accomplish this in one of two ways.

    1. First, you can designate one of your source code files as the primary file, and insert into this file statements of the form,

 

#include “\path\filename”

to include the other source code functions, where \path is the standard DOS path specification that allows the file to be located.  The primary file must have a .C extension.

    2. Second, you can split your source code into several files that start with the same sequence of characters, such as:

 

CODE.C    // this is the primary file that you compile

CODE1.C   // CODE1.C and CODE2.C are subsidiary files that are automatically

CODE2.C   // compiled and linked when CODE.C is compiled

All of these files must have the .C extension.  Then, when you compile CODE.C using the Make Tool (by clicking on the Hammer icon), all of the files with a .C extension whose name starts with CODE (namely, CODE.C, CODE1.C and CODE2.C) will be compiled and linked together.  Note that the Make Tool cannot handle filenames that start with a numeral or filenames that contain the dash character; thus a source code file named 1CODE.C or MY-CODE.C cannot be compiled.

Finally, note that one and only one of the program files must include a function named main().

Controlling the Memory Map

Compilation Errors

Compilation Errors (from QED Software Manual)

Downloading and Installing a Program

Using Terminals other than TextPad

Editors and Terminals for the PC

This interactive style of programming and debugging is especially easy if you can switch rapidly between the editor and terminal programs on your PC.  If you have an IBM-compatible or clone PC, we recommend that you use the editor (either Write or Notepad) and terminal (named Terminal) that come with the Microsoft Windows™ software.  This inexpensive package gives your PC a simple yet very powerful menu-based operating environment.  You control the computer by pointing at menu entries and clicking a button on a movable mouse to select the desired options.  The operating system software lets you switch from editing mode to terminal communications mode by simply clicking on the appropriate program window.

A step-by-step guide to configuring the Windows terminal and editor for use with the QVGA Controller is presented below.

Of course, any text editor and terminal programs will allow you to program in QED-Forth as long as the terminal is properly configured.  We provide a simple public domain terminal called Kermit with each QED Product Design Kit and QED Developer Package; while it is fairly primitive compared to the Windows Terminal, it can be used.  The last section of this appendix provides some hints about the operation of Kermit.

Editors and Terminals for the Apple Macintosh (Forth only)

Editors and Terminals for the Apple Macintosh

Apple Macintosh users can use any of the popular available terminal programs such as Smartcom™ or Microphone™.  The editor can be the simple MockWrite desk accessory which operates in conjunction with any application.  Macintosh owners can also use the System 7 multitasking operating system to switch between editor and terminal programs.

Terminal Configuration for Smooth Downloads  -- Configuring other Windows Terminals (from QED Software Manual)

Appendix A Terminal Configurations for QED Communications A-1 (Getting Started C v31)

Terminal Configuration for Smooth Downloads A-1 (Getting Started C v31)

How to Configure the Microsoft Windows Terminal A-2 (Getting Started C v31)

Setting the autostart

[3u-d4-a] ???

An Additional Feature in the RECEIVE.HEX Function

The function named:

    RECEIVE.HEX    ( xaddr1 <text> --  )

accepts an Intel or Motorola hex dump and initializes memory locations on the QVGA Controller starting at the specified 32-bit address xaddr1.  In V3.1, if xaddr1 equals FFFFFFFFH (that is, a 32-bit -1), the memory storage addresses are as specified in the hex dump itself.  This may be convenient for some applications.

Using the Interactive Debugger

In the prior chapter, you gained experience using the debugging environment that lets you interactively execute any designated function with input arguments of your own choosing.  Now we’ll look more closely at the operation of the debugging environment, and explain how to use it to examine and manipulate the values of static variables, Forth Array elements, and memory locations.

The interactive debugging environment conveys several advantages.  First, you can test each function of a program individually without changing the main() function and recompiling.  This saves compilation and download time. Second, the environment makes it easy to test each function with a wide range of input parameters, allowing you to isolate bugs that might otherwise be missed until later in the program development cycle.  Such thorough function-by-function testing of a program facilitates more rapid development of reliable programs.

We’ll start by learning how to use the interactive environment to examine the values of static variables.  The explanation of how this works involves taking a brief high-level look at the interactive QED-Forth language that is built into the Handheld.  Understanding how QED-Forth operates will empower you to take full advantage of the debugging capabilities of the Handheld.

Overview of the Forth Language and Programming Environment

The QED-Forth interactive environment makes it easy to examine the contents of static variables.  A brief overview of how the Forth language works will help clarify the procedure.

The Forth Data Stack

Forth is a stack-oriented high level language that combines the interactive benefits of an interpreter with the speed of a compiler.  Unlike C, FORTH is implemented as a two-stack language.  In addition to the return stack that most languages use to keep track of function calls and returns, FORTH has a data stack that is used to pass parameters.  All arithmetic, logical, I/O, and decision operations remove any required arguments from the data stack and leave the results on the data stack.  This leads to postfix notation: the operation is stated after the data or operands are placed on the stack.  This is the same notation used by Hewlitt Packard’s RPN (reverse polish notation) calculators.

Unlike C, Forth uses spaces as delimiters to distinguish different keywords and tokens.  For example, a C compiler can easily parse the addition expression:

 

5+4

as three distinct tokens: 5, +, and 4.  But because the above expression was typed without any spaces, Forth would interpret the expression as a single token, assume it’s the name of a function, and would try to find it in its dictionary.  In Forth the expression must be entered as:

 

5  4  +

which includes the required spaces and uses postfix notation to add the numbers and leave the result on the data stack.

To see how this works, we’ll talk to the interactive QED-Forth interpreter on the Handheld.  To start, enter the terminal now: if the terminal program is already active, click on its window or hold down the “Alt” key and press “Tab” until the terminal announcement appears on your screen.  If you haven’t started the terminal program yet during this session, double-click on the Mosaic Terminal icon to start it up. Connect and power up your Handheld; pressing the Return key should cause QED-Forth’s ok prompt to appear in the terminal window.

To start, we’ll ensure that the current number base is decimal by typing the command,

DECIMAL

from the terminal.  With each character you type QED-Forth echoes the character in you terminal window.  The back arrow in the line above indicates that you pressed the Enter key which sends a carriage return character; but you won’t see it as an echoed character on your screen.  QED-Forth executes this command when the terminating carriage return is received.  Also recall that QED-Forth case-insensitive, so you can freely mix upper and lower case letters.  Now we can put some numbers on the QED-Forth data stack by typing :

5  7

followed by a carriage return. QED-Forth responds:

ok   ( 2 ) \ 5 \ 7

We have underlined QED-Forth’s response for clarity.  QED-Forth is showing a picture of its data stack.  The ( 2 ) means that there are two items on the stack.  Each of the items is listed, and items are separated by a \ character, which can be read as under.  So we could describe the stack right now as 5 under 7; the 7 is on top of the stack, and the 5 is under it. If there are more than 5 items on the stack, the stack print displays the number of stack items and the values of the top 5 items.

The stack print that shows what’s on the data stack is a feature of the debugging environment.  To disable the stack print, you could execute (that is, type at your terminal) the DEBUG OFF command.  It is not recommended that you do this, though; it’s very helpful to keep track of the items on the data stack while developing your program.

To multiply the numbers that are now on the stack, type the multiply operator which is a * character:

*

and QED-Forth responds:

ok  ( 1 ) \ 35

The QED-Forth * operator removes the two operands 5 and 7 from the stack, multiplies them, and puts the result of the multiplication on the stack.  To subtract 5 from the number on the stack, type:

5  -

which produces the response:

ok  ( 1 ) \ 30

The QED-Forth - (minus) operator takes the 35 and the 5 from the stack, subtracts, and puts the result on the data stack.

To print the result to the terminal, we could simply type the printing word:

.

(that’s right, the command is simply a dot, the period on your keyboard) which prints the response:

30   ok

The printing word . removes the 30 from the stack and prints it.  The stack is now empty, so QED-Forth does not print a stack picture after the ok.

Notice that throughout this exercise QED-Forth has been interpreting and executing commands immediately. This is because the Forth language is interactive.  The results of executing commands can be immediately determined.  If they are incorrect, the command can be changed to correct the problem.  This leads to a rapid iterative debugging process that speeds program development.  This interactive function execution has been harnessed to speed development of C programs for the Handheld.

QED-Forth Numeric Printing Functions

There are a variety of QED-Forth printing functions, and some related functions that set the current number base and clean up the data stack.  Here is a short list of useful functions that can be executed interactively:

 

Function

Description

.

Prints a 16 bit signed integer in the current number base

U.

Prints a 16 bit unsigned integer in the current number base

D.

Prints a 32-bit signed long in the current number base

PrintFP

Prints an ANSI-C floating point number

HEX

Sets the number base to hexadecimal

DECIMAL

Sets the number base to decimal

SP!

Clears all items off the stack without printing anything

Each of the printing routines removes a number from the data stack and prints it to the terminal.  Because characters are promoted to unsigned int in Forth, the . (dot) function is also used to print 8-bit character data.  The PrintFP function was specifically written to display floating point numeric output from C programs, as internally QED-Forth uses a non-ANSI floating point representation for its own floating point numbers.

The default QED-Forth number base after a COLD restart is DECIMAL.  The number base can be changed to hexadecimal by executing HEX.  All non-floating-point numbers typed at the terminal or printed by QED-Forth are converted using the current number base (corresponding to the most recent execution of DECIMAL or HEX).  Floating point numbers are always converted using the decimal number base.

Displaying the Values of Static Variables

Now that we understand how the Forth data stack works, the procedure for examining variables will make sense.  The examples presented here use code from the GETSTART.C program that we’ve already discussed in detail.  If you have already downloaded the program, you are ready to go.  If your board is presently running a multitasking application and you want to download a new file, type

WARM

to stop the program so that a new download file can be accepted.

If you have not yet compiled the GETSTART.C program and you want to do the exercises here, first compile it by opening \MOSAIC\DEMOS_AND_DRIVERS\MISC\C EXAMPLES\GETSTART.C in the TextPad editor, click on the Make Tool, and after the compilation, enter the Mosaic Terminal and use the “Send Text File” menu item to send GETSTART.DLF to the Handheld.  To run the program, type

main

at your terminal – this initializes all the pointers and variables.  After typing main, let’s type

Nap( )

to put the calculation task asleep; remember to type at least one space after the (.  This stops the variables from being updated in the background.

Let’s start by initializing the contents of the radius variable to 5 by interactively executing (typing) from the terminal:

SetRadiusAndArea( int 5)

Remember to type at least one space after the ( character and after int.  This function is defined in GETSTART.C as:

 

_Q SetRadiusAndArea( uint r)

{  radius = r;

area = CalcArea( r);

}

It assigns the specified input parameter to the unsigned integer radius variable, and assigns the corresponding circular area to the floating point area variable.

Now we can check the value of radius.  The following interactive command places the contents of the integer variable named radius on the Forth data stack:

int  radius

QED-Forth responds with:

ok  ( 1 ) \ 5

Because radius is defined as an unsigned integer, we use the unsigned integer printing routine named U. (U-dot) to remove the value from the Forth data stack and print it.  Type

U.

to print the radius an unsigned integer.  QED-Forth responds with:

5 ok

To speed things up, we can type the entire command sequence on one line so that QED-Forth immediately prints the result.  Type:

int  radius  U.

and QED-Forth responds with:

5 ok

To interactively examine the contents of the floating point area variable, type the command sequence:

float  area  PrintFP

and QED-Forth responds:

78.54 ok

which is indeed the area of a circle whose radius equals 5.

Extracting the Value Referenced by a Pointer

Sometimes C programs add an additional layer of indirection, referencing a value by means of a pointer.  An example of this technique appears in the GETSTART.C program in the form of the static variables radius_ptr and area_ptr; they are defined as:

 

static uint*   radius_ptr;

static float*  area_ptr;

In the InitVars() function near the end of the program, these pointers are initialized as follows:

 

radius_ptr = &radius;

area_ptr = &area;

In other words, radius_ptr holds the address of a variable that represents the radius, and area_ptr holds the address of a variable that represents the area.  Given the radius_ptr and area_ptr, we want to be able to extract the value of radius and area.  The following keywords can be executed interactively to accomplish this:

 

char*      int*      long*      float*

Note that there cannot be any spaces before the * in each keyword, and there must be at least one space after the * and before any subsequent number or variable name. 

For example, to print the radius you can type:

int*  radius_ptr  U.

The int* keyword fetches the 16-bit address from radius_ptr and from that location fetches the integer contents.  U. then prints the answer to the terminal.  Similarly, to print the area you can type:

float*  area_ptr PrintFP

The float* keyword fetches the 16-bit address from area_ptr and from the resulting location fetches the floating point contents. PrintFP then prints the result.

Signed versus Unsigned Numbers

Note that the type specifier used above does not specify signed versus unsigned numbers; rather, the printing function determines whether the number is interpreted as signed or unsigned.  For example, type the following two command lines from the terminal and see how QED-Forth responds:

65535   U.

65535   .

In the first instance, QED-Forth prints 65535, while in the second instance, QED-Forth prints -1 (we’re assuming that you have not changed the number base to HEX). The same binary pattern (in this case, all 16 bits of the number are set) can represent either 65535 or -1 depending on how the number is interpreted and printed.  Thus by choosing the printing function, you can control whether a number is displayed as a signed or unsigned quantity. 

Summary

In summary, to display the contents of a simple static variable, type a command of the form:

 

type  variable_name  print_function_name

where type is one of the following keywords:

 

char      int      long      float

To display the contents of a static variable that is pointed to by a pointer, type a command of the form:

 

type      pointer_name      print_function_name

where type is one of the following keywords:

 

char*    int*     long*     float*

Use Type Keywords To Interactively Call C Functions

The same family of familiar C type-declaration keywords that we used to fetch the contents of variables is also used to facilitate interactive calling of C functions.  Recall that these keywords are:

 

char    int     long    float

char*   int*    long*   float*

We have seen that these keywords are used in two different contexts while debugging.  In the prior chapter we used them to declare the type of an input parameter while interactively calling a function.  For example, we can interactively type from the terminal:

CalcArea( int 5)

where the int keyword is used with the same syntax as an ANSI-C function prototype to declare the input arguments to the called function. 

Second, we used the type keywords in this chapter to extract the value from a variable, as in the interactive QED-Forth command

int  radius  U.

which prints the contents of the radius variable as an unsigned integer. 

These two contexts for the use of the int keyword are related.  For example, to calculate the area corresponding to the current value of the radius variable, we can interactively execute:

CalcArea( int radius)

and QED-Forth prints the resulting floating point area in its summary of the return value.  The int keyword serves two complementary purposes here: it tells QED-Forth that the input parameter is a 16-bit integer, and it extracts the value of the radius variable so the variable is passed by value

When interactively calling a function, all parameters that are passed by value should be preceded by the appropriate type keyword.  However, when passing the address of a variable or a structure, simply state the variable or structure name without any type specifiers or & (address-of) operators. 

For example, the function prototype for the DimAndInitFPArray() function in GETSTART.C is:

 

Q void DimAndInitFPArray(float val,int rows,int cols,FORTH_ARRAY* array_ptr)

and the program includes the array declaration:

 

FORTH_ARRAY circle_parameters;

which declares circle_parameters as a FORTH_ARRAY structure in memory.  As we shall see, executing (typing) the name circle_parameters in QED-Forth leaves the address of the array structure on the stack, so there is no need for additional type declarators or & operators.  Thus to interactively dimension the array to have 10 rows, 2 columns and a initialization value of 12.34, we type from the terminal:

DimAndInitFPArray( float 12.34,int 10,int 2,circle_parameters)

To verify that this worked, you can execute:

PrintFPArray( circle_parameters)

which displays the contents of the newly initialized circle_parameters matrix.

Displaying the Values of FORTH_ARRAY Elements

The same type specifier keywords that let you examine static variables can also be used to examine any specified element in a two-dimensional FORTH_ARRAY. The syntax is parallel to what we have already used; the difference is that we now append the row and column indices in square brackets after the array name to specify which element should be fetched. 

For example, recall that circle_parameters is a FORTH_ARRAY that is dimensioned to hold 10 rows and 2 columns of floating point data.  To print the contents of the first element in the array at [row=0, col=0], we type:

float  circle_parameters[ 0, 0]  PrintFP

and QED-Forth prints the result.  While this array notation is not exactly like the standard C syntax, it is straightforward.  To print the element whose row index is 5 and whose column index is 1, type:

float  circle_parameters[ 5, 1]  PrintFP

As you might expect, there must not be a space before the [ character, and there must be at least one space after the [ character. This is because

 

circle_parameters[

is defined as a space-delimited QED-Forth function in the GETSTART.DLF file, as explained later in this chapter.

All of the keywords that we learned about above can be used to fetch the contents of appropriately dimensioned arrays.  Arrays that are dimensioned to hold character, integer, long, or float data are accessed using the char, int, long and float keywords, respectively, in front of the array name.  If for some reason you use a FORTH_ARRAY to hold 16-bit pointers , the char*, int*, long* and float* keywords can be used in a manner exactly analogous to the description in the earlier section of this chapter.

Assigning Values to Static Variables and FORTH_ARRAY Elements

You can interactively change the contents of any static variable or FORTH_ARRAY element using the following assignment keywords:

 

=char    =int    =long    =float

Each of these keywords expects to be preceded by the address of a variable or FORTH_ARRAY element, and expects to be followed by a valid number, variable name, or FORTH_ARRAY element specifier.  As expected, the value of the right hand side is assigned to the variable or array element on the left hand side of the assignment expression.

For example, to change the current value of radius to 22, simply type:

radius  =int  22

This syntax was designed to be similar to a C statement that assigns the value 22 to the radius variable.  As you might guess, =int is a single keyword defined in QED-Forth, so there cannot be any spaces between = and int. Similarly, the other tokens in the expression must be separated by spaces; thus there is at least one space after radius and at least one space before 22.

To set the current value of the floating point area variable to 1520. type:

area =float 1520.

To assign the current value of the area variable to element [ 0, 1] in the FORTH_ARRAY circle_parameters, you can execute:

circle_parameters[ 0,1] =float  area

To check that these operations actually worked, we can execute the following commands to examine the contents of the affected variables and array elements:

int radius  U.

float  area  PrintFP

float  circle_parameters[ 0,1]  PrintFP

Under the Hood of the QED-Forth Interactive Debugger

This section is for the curious among you; you need not read or understand this section to use the QED-Forth interactive debugger.  However, it will give you additional insight into the debugging environment.

Variable Declarations

In the example above, radius is defined in the GETSTART.DLF download file as a QED-Forth constant whose value is the address of the radius variable.  To see for yourself, use you editor to open the GETSTART.DLF file.  Select “Open” from the editor’s “file” menu, set the “List Files of Type” option to either “Text Files” or “All Files”, and double click on GETSTART.DLF in the \MOSAIC\DEMOS_AND_DRIVERS\MISC\C_EXAMPLES directory.  The top portion of the file is the hexadecimal dump of the compiled C code in the Motorola S2 record format.  Near the bottom of the file you’ll see some CONSTANT declarations.  Among them is the declaration:

 

008E03 CONSTANT radius

which defines radius as a QED-Forth constant that places the hexadecimal value 8E03 on the stack.  You can verify this by clicking on the Terminal window and typing:

HEX radius  U.

DECIMAL

from the terminal.  This command sequence instructs QED-Forth to print the hexadecimal address of the radius variable, and then return to decimal base.  Note that if you want to pass the address of the radius variable as a parameter to a function (also known as passing a pointer or passing by reference), you leave out the int keyword before radius in the parameter list.

The keyword int is actually a QED-Forth function that examines the next token in the input stream; if it is already a number such as 5 or 3.2, int simply converts it to the nearest integer.  If the next token is a variable address (such as radius), int extracts the 16-bit contents stored at the address.  To see this behavior for yourself, try the following commands at your terminal:

int  5  U.

int  5.45  U.

int  radius  U.

These three statements all yield identical results if the value of radius is still 5. 

Function Declarations

Returning to the GETSTART.DLF file that you opened in the editor, scroll to the area just above the list of CONSTANT definitions and you will see a set of lines starting with the : (colon) character.  In Forth, the : character marks the start of a new definition (function or subroutine), and the ; (semicolon) marks the end of the definition.  These are the function definitions that tell QED-Forth the names and execution addresses of each function in GETSTART.C that was preceded by the _Q declarator.  Among these functions you will find some familiar ones including:

 

SetRadiusAndArea(

CalcArea(

DimAndInitFPArray(

PrintFPArray(

The body of each of these Forth definitions defines the compilation address and invokes the routine CALL.CFN (meaning call-C-function). CALL.CFN accepts an optional list of comma-delimited parameters terminated by a closing ) and then sets up the stack frame and calls the function.

So when you type the interactive command

CalcArea( int radius)

with a terminal enter key, here’s what happens:

    1. When QED-Forth accepts the carriage return, it starts interpreting the command line that has been entered.  It looks for the first space-delimited token, and it finds the token:

 

CalcArea(

    2. It looks in its dictionary, and sure enough, it finds that this token has been defined; the definition was compiled when the GETSTART.DLF download file was sent to the Handheld.

    3. When QED-Forth executes the CalcArea( token, it executes the CALL.CFN routine which starts looking for a terminating ) character, and processes any tokens that are present.

    4. The next space-delimited token found is int, which looks for the next token (in this case, radius). Because radius is not a number, int assumes that it is a variable and extracts the 16-bit contents from the address that is left on the Forth data stack by radius.  The contents are left on the Forth data stack.

    5. The terminating ) is found, so the CALL.CFN routine pushes the items on the Forth data stack onto the C stack in the proper order to make a legal C stack frame, and then executes the CalcArea() function as defined in the C program at the specified execution address.

    6. When the CalcArea() function returns, QED-Forth traps its return value from the 68HC11’s registers and prints the value using integer and floating point formats.

FORTH_ARRAY Declarations

Near the bottom of the GETSTART.DLF file you can find the definition of circle_parameters[ that facilitates examining and modifying any element of this array.  The QED-Forth definition is:

 

: circle_parameters[

  circle_parameters  DO[]

;

As described above, the : character marks the start of a new definition, and the ; marks the end of the definition.  The body of the definition is simple: the constant circle_parameters leaves the base address of the FORTH_ARRAY structure on the stack, and DO[] does the rest of the work.  DO[] is defined in the QED-Forth kernel; it searches for a row index followed by a comma, and a column index followed by a terminating ] character.  Then it passes the specified row, column, and array parameter field address to the Forth function named [] (brackets) which places the 32-bit extended address of the array element on the stack.  This extended address can be used as the argument to the familiar keywords that we have discussed such as char, int, long, float, =char, =int, =long, =float, etc.  Thus all of the following are legal debugging commands:

float circle_parameters[ 3,0]  PrintFP

circle_parameters[ 2,1] =float  area

circle_parameters[ 5,0] =float 345.

Some of you may have noticed that CalculationTask[ is also declared to QED-Forth as a potential FORTH_ARRAY in the GETSTART.DLF download file; yet we know that CalculationTask is a task identifier, not a FORTH_ARRAY. The reason for this is that Make Tool always declares the last variable allocated in the common RAM as a potential FORTH_ARRAY; it does this because there it can’t determine the allocated size of the last variable.  The extra definition of CalculationTask[ does no harm (as long as we don’t try to use it improperly).

Summary

The Make Tool calls the QCC.EXE executable program to create the QED-Forth debugging declarations that appear at the bottom of the .DLF download file.  This program has to decide whether each compiler symbol in the .OUT file is a callable function, a variable, or a FORTH_ARRAY.  The Make Tool identifies callable functions by detecting the _pascal? tag that the compiler places there in response to the _Q specifier, and in response prints the functionname( definition into the .DLF file.  The Make Tool identifies variables by detecting whether the corresponding address lies in the common RAM area, and in response prints a QED-Forth CONSTANT declaration into the .DLF file.  Finally, it tentatively identifies FORTH_ARRAYs by checking the size of each variable; if there are exactly 18 bytes allocated to one item in the common RAM, it decides that the associated name should also be declared as a FORTH_ARRAY by printing the name[ definition in the .DLF download file.  To be safe, the Make Tool always declares the last variable as a FORTH_ARRAY because it cannot be sure of its allocated size.

Other Useful QED-Forth Functions

QED-Forth is a complete language that includes over a thousand pre-defined functions, all of which reside in ROM on the Handheld.  Many of these functions are declared in the header files in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory, and so are callable from C.  The names and descriptions of these functions are detailed in the Control C Glossary in the documentation package.  But there are also additional routines described there that are useful while debugging; these allow you to:

  Modify the contents of EEPROM on the 68HC11 processor.

  Dump the contents of a specified region of memory in hex and ascii format using the DUMP command.

  Specify a new baud rate for the serial port to speed downloads using the BAUD1.AT.STARTUP command.

  Configure the Handheld to execute a specified program each time a reset or restart occurs using the AUTOSTART or PRIORITY.AUTOSTART command.

  Dump out a replica of the board’s program memory space in Intel Hex or Motorola S2 record format to archive your production code.

In sum, the versatile QED-Forth language enhances the power of Control C by providing many operating system functions as well as an interactive debugging environment that speeds program development and testing.

The Handheld Kernel vs. Prior Kernels

The Handheld, QCard and QScreen products use a QED-Forth operating system kernel denoted as V4.4x, where the ‘x’ may take on any numeric value.  There are several minor differences between V4.4x and the V4.0x kernel used on the QED Board, Panel-Touch Controller, and QVGA Controller products. Briefly, five functions have been added to the V4.4x kernel, and 19 device functions have been removed. The removed functions are device drivers associated with hardware that is not implemented on the Handheld/QCard/QScreen products.  In addition, the V4.4x kernel boots up at a default serial baud rate of 19,200 baud, compared to 9600 baud on prior kernels.

Table 4‑2   lists the new functions, and Table 4‑3 lists the removed functions.  Descriptions of the new functions are provided in the glossary below and in the function reference document that accompanies the Handheld, QCard and QScreen products.

Table 4‑2      Functions added to V4.4x kernel

C Name:

Forth Name:

Buffer_To_SPI( )

BUFFER>SPI

Bytes_To_Display( )

BYTES>DISPLAY

Calc_Checksum( )

CALC.CHECKSUM

Clear_Boot_Vector( )

CLEAR.BOOT.VECTOR

Set_Boot_Vector( )

SET.BOOT.VECTOR

Forth programmers do not need any additional software to access the new functions, as they are built into the kernel. 

C programmers must include the files named v4_4update.c and V4_4update.h to gain access to the five new functions.  These files are located in the

\Mosaic\Fabius\Include\Mosaic\v4_4Update

directory in the software distribution CD.  Simply #include both the v4_4update.h and v4_4update.c files in one of your source files, and also #include v4_4update.h in any other source files that use these new kernel routines.

The TO.FLASH and PAGE.TO.FLASH and PAGE.TO.RAM routines are now smart with respect to the memory map.  The Handheld Flash has memory at pages 10-17 that swaps with RAM on pages 18 through 1F.

Table 4‑3      Summary of functions deleted from V4.4x.

C Name:

Forth Name:

InitPIA( )

INIT.PIA

PIAStore( )

PIA.C!

PIAFetch( )

PIA.C@

PIAChangeBits( )

PIA.CHANGE.BITS

PIAClearBits( )

PIA.CLEAR.BITS

PIASetBits( )

PIA.SET.BITS

PIAToggleBits( )

PIA.TOGGLE.BITS

ClearHighCurrent( )

CLEAR.HIGH.CURRENT

SetHighCurrent( )

SET.HIGH.CURRENT

PPA_ADDRESS

PPA

PPB_ADDRESS

PPB

PPC_ADDRESS

PPC

FastSetDAC( )

(>DAC)

FastAD12Multiple( )

(A/D12.MULTIPLE)

FastAD12Sample( )

(A/D12.SAMPLE)

SetDAC( )

>DAC

AD12Multiple( )

A/D12.MULTIPLE

AD12Sample( )

A/D12.SAMPLE

InitAD12andDAC( )

INIT.A/D12&DAC

Summary of Modified Memory Map Functions

The Kernel’s internal memory map functions have been modified to be aware of the Handheld, QCard and QScreen’s memory.  In the “standard map”, the Handheld has flash at pages 4-7 that swaps with RAM on parallel pages 1-3, plus flash at hex pages 10-17 that swaps with RAM on parallel hex pages 18-1F.  In the “download map”, flash and RAM are swapped: flash is present on pages 1-3 and 18-1F, and RAM is present on pages 4-6 and 10-17.  The C development environment transparently handles the loading of program code into flash, so C programmers typically do not have to be concerned with these issues. 

The STANDARD.MAP, DOWNLOAD.MAP, TO.FLASH, PAGE.TO.FLASH and PAGE.TO.RAM routines are now aware of the additional memory in the memory map. 

A set of functions makes it easy for Forth programmers to manage the downloading of code into RAM and the transfer of the compiled code to flash.  The PAGE.TO.FLASH and PAGE.TO.RAM utilities can copy between parallel RAM and flash pages.  The TO.FLASH function can program flash anywhere it appears in the Handheld memory map. 

The great majority of Forth applications compile to less than 96 KBytes of code, which fits on pages 04, 05 and 06.  For these applications, the new functions ENABLE.DOWNLOAD and ALL.TO.FLASH greatly simplify the downloading process. Simply insert the command ENABLE.DOWNLOAD at the top of the first file to be downloaded to the Handheld.  This function makes sure that any previously downloaded code is transferred to RAM, and calls DOWNLOAD.MAP to ensure a RAM-based memory map that enables compilation of code.  At the end of the last file to be downloaded, insert the command ALL.TO.FLASH.  This copies the compiled code in pages 4, 5 and 6 to flash, sets the STANDARD.MAP, and calls SAVE so that the RESTORE command can be used to recover after a crash or a COLD restart during the development process.

Program Development in Forth

Sending Your Programs to the Handheld

The Handheld accepts serial commands from a terminal which is typically a personal computer (PC).  QED-Forth’s onboard interpreter, compiler, and assembler compile your program into executable code in the Handheld’s memory, and the code can be immediately executed by simply stating the name of a routine.  This encourages a productive iterative programming style.

If you wanted, you could program an application by typing in your application program one line at a time from a terminal.  For all but the shortest programs, this would be a tedious process.  Fortunately, there is a better way.  All you need is a text editor program and a terminal emulation program that run on your PC.  The text editor allows you to create and modify text files and save them on the computer’s disk drive.  The terminal program allows you to send and receive characters via the PC’s RS232 serial port to communicate with the Handheld. Thus you can rapidly edit your source code into files which can be downloaded (transmitted) to the Handheld.  Ideally, you should set up your PC so that you can switch between the editor and terminal modes quickly and easily.

To send a program to the Handheld, edit the “source code” (that is, the QED-Forth commands and any relevant comment statements) into a text file and save the file on the PC’s disk.  Then use the “File®Send File” feature of the terminal program to send the program file to the Handheld.  If you discover errors while debugging the program, simply edit the file and re-transmit all or part of it to the Handheld.  If your file contains the ANEW command described later in this chapter, the old erroneous version of your program will be automatically forgotten by the Handheld when the new version is transmitted to the board.

You have complete flexibility in programming the Handheld.  You can send programs to the board using the file transfer method, and you can also use the terminal interactively to compile and execute short commands or manage debugging operations.  Many terminals allow you to “record” your debugging session as a text file.  This can be a useful technique; you can later edit the recorded file to save the most worthwhile aspects of the debugging session.

PAUSE.ON.KEY and XON/XOFF Handshaking

“Handshaking” refers to a communications protocol in which the two communicators let each other know when they are able to receive and send data.  Hardware handshaking involves RS-232 signals with names such as CTS (clear to send) and RTS (ready to send), and is not implemented by QED-Forth (although I/O ports could be used to implement hardware handshaking if desired).

Software handshaking involves transmitting characters to halt transmission when one of the communicators is busy.  This is accomplished either by waiting for a prompt character (e.g., waiting for the linefeed character as recommended above) or using the control characters XOFF (ascii 19) and XON (ascii 17) to request that transmission be halted and resumed, respectively.  Both of these types of software handshaking help to smooth communications between a terminal and the Handheld.

Although the QED-Forth interpreter does not directly implement the XON/XOFF protocol (see the description of the XMIT.DISABLE user variable in the Glossary), it is still useful to configure your terminal for XON/XOFF handshaking.  This helps to ensure smooth transfer when the Handheld is printing or dumping data to the terminal.  Smooth data transfer can be achieved by placing the QED-Forth word named PAUSE.ON.KEY in routines that dump data from the Handheld to the terminal.

The definition of PAUSE.ON.KEY is:

PAUSE.ON.KEY

Check whether a character has been received; if not, continue executing the word that called PAUSE.ON.KEY.  If a character was received, go into a waiting loop until the next character is received, at which time execution of the word that called PAUSE.ON.KEY continues.  If a carriage return is received, execute ABORT; if a . is received execute QUIT (which leaves the data stack intact and enters the QED-Forth interpreter).

If PAUSE.ON.KEY is edited into the inner loop of a QED-Forth word that dumps data, you can suspend the flow of that data by typing any character from the terminal, and you can resume the flow by typing a second character.  To abort the output, type a carriage return. 

Interestingly, if your terminal is configured for XON/XOFF handshaking, it sends an XOFF character if it can’t keep up and wants QED-Forth to stop sending data (for example, if the terminal’s data buffer is about to overflow).  If QED-Forth is running a routine such as DUMP that includes PAUSE.ON.KEY, receipt of the XOFF character will cause the output to be suspended, and receipt of the next character (XON) will cause the output to be resumed.  Thus the terminal will automatically control the output flow from the Handheld, preventing the loss of data during the transfer.

The kernel word PAUSE.ON.KEY is coded into the following QED-Forth words that can print potentially large amounts of data:

 

DUMP 

DUMP.INTEL

DUMP.S1

DUMP.S2

M.

M..

M.PARTIAL

WORDS

So if you enable the XON/XOFF handshaking option in your terminal, the terminal should reliably receive the data without missing any characters.

Using ANEW, ON.FORGET, and AXE for Dictionary Management

Using ANEW

FORTH is a language that encourages interactive and incremental programming and debugging.  You fully test low level words, and build up to higher level words.  Often you will discover a bug in a word that has been defined, and will want to re-compile a new version of the word to fix the error.  The interpreter always finds the most recently compiled version of a word, so you need not worry about entering multiple definitions of a word.  But after many re-definitions memory space is wasted as the dictionary becomes cluttered with unneeded buggy definitions.  The QED-Forth word ANEW is the best solution to this problem. 

The action of ANEW is based on the standard FORTH word FORGET which is explained here.  If a definition for a word called GARBAGE is compiled and then you decide to re-compile it, you can execute the command

FORGET GARBAGE

before re-compiling.  This changes some user variables so that the interpreter can no longer find any of the words that have been compiled since GARBAGE was defined.  Consequently, GARBAGE and all subsequent words are “forgotten”. FORGET resets the definitions pointer DP to point to the first byte in the code field of GARBAGE.  Thus the next definition you enter will over-write the original definition.  FORGET also sets the name pointer NP to point to the first byte of the header of GARBAGE in the dictionary.  This will cause the header for the next word entered to write over the original header for GARBAGE.

But there is a problem with using FORGET: it does not reset the variable pointer VP to the value it held before GARBAGE was defined.  So if GARBAGE or any of the words compiled after it advanced VP, this variable space will not be recovered, thus wasting memory.

The word ANEW is a more sophisticated and easy to use word for dictionary cleanup.  Here’s an example showing the use of ANEW:

 

ANEW DEBUG.SESSION

: GARBAGE   ( -- )

.” I dont do much”

;

VARIABLE MYVAR

There is a spelling error in the word “don’t” in GARBAGE, so the error is corrected and the source code is re-loaded as:

 

ANEW DEBUG.SESSION

: GARBAGE   ( -- )

.” I don’t do much”

;

VARIABLE MYVAR

There is now only one definition each of MYWORD and MYVAR in the dictionary;  ANEW took care of forgetting the previous definitions and properly resetting NP, DP, and VP.  To use it, just put ANEW followed by a “marker” name of your choice (usually one that describes the general function of the code you’re compiling) at the top of the source code file that you download.  Then each time you download a new version of the file, ANEW will search the dictionary for the marker name (in this case, DEBUG.SESSION), and intelligently forget back to that point.  It will leave the marker name in the dictionary so that future ANEW commands can find it.

Using ANEW <marker.name> as the first command in each source code file provides easy and automatic cleanup of the dictionary during debugging.  If the memory map is altered at the start of a source code file, the ANEW command should follow rather than precede the code that changes the memory map.  Setting the memory map is described later in this chapter.

How ANEW Works

You need not understand how ANEW works to use it.  This description is for those who are interested in operational details.

ANEW searches the dictionary for the specified marker word.  If the word is not found, ANEW defines the marker name as a word whose action is to store the current value of VHERE into VP in the user area.  Subsequent ANEW commands search the vocabulary for the specified marker name and, finding the marker, execute it to reset VP, and FORGET the words back to the marker which properly resets NP and DP.  ANEW then re-defines the marker word so that future ANEW commands can find it.

Using ON.FORGET for Heap Recovery

The one area of memory that ANEW cannot recover is heap memory associated with forgotten words or data structures.  You can accomplish this by defining a word having the (non-unique) name ON.FORGET.  ANEW and FORGET both check the names of all words being forgotten; if the name is ON.FORGET, the definition is executed before being forgotten.  For example, the following code creates and dimensions an array and provides for recovery of the heap space when the code is forgotten:

 

ANEW EXAMPLE

ARRAY: MY.ARRAY

7 1 2 ‘ MY.ARRAY DIMENSIONED     \ 1 dimension, 7 2-byte elements

: ON.FORGET    ( -- )

‘ MY.ARRAY DELETED

;

When this code is compiled, QED-Forth will report that ON.FORGET is not unique; this is expected and is not a problem.  On subsequent execution of ANEW EXAMPLE or even FORGET EXAMPLE, MY.ARRAY will be forgotten, and the ON.FORGET word will be executed before it is forgotten.  This will delete MY.ARRAY to recover any heap space that had been allocated to the array.

Using AXE to Conserve Dictionary Space

FORTH is a hierarchical language that encourages the definition of many short, easily debugged words.  While this eases development and makes source code more readable, it can fill up the name area of the dictionary.  Each word’s header requires 7+WIDTH bytes, where WIDTH is the number of characters that are saved in the header. 

To conserve dictionary space, the names of intermediate words that do not have to be accessed in the final application can be AXEd.  For example, assume that the routine INTERMEDIATE.WORD is defined.  After the other words that call INTERMEDIATE.WORD are compiled, the command

AXE INTERMEDIATE.WORD

removes the header of INTERMEDIATE.WORD from the name list and leaves its definition intact.  All of the words that call INTERMEDIATE.WORD are unaffected.  Attempts to invoke INTERMEDIATE.WORD by name will now be met with the ? error message because the name is no longer in the dictionary. 

AXE can only be used if the name area is not write-protected.  It fails if the name pointer NP has been reset by the programmer (using an NP X! command) since INTERMEDIATE.WORD was defined, or if you have been defining words into several vocabularies since the definition of INTERMEDIATE.WORD.  AXE cannot detect all of the conditions that cause problems, so caution is required when using it.

In many applications programs, only a few words need to be accessed by the final user. Using AXE to prune the excess headers from the dictionary conserves memory.

Using the Debugging Tools

Summary of the Debugger

QED-Forth’s debugger can be used to debug high level and assembly coded routines.  The debugger allows you to:

    Trace program flow

    Single-step through a program or routine

    Install break points by simply compiling a call to the BREAK routine

    Enter a “break” mode while debugging that allows you to inspect or modify memory locations

    Install a customized diagnostic routine that is executed after each program instruction, and

    Print the contents of the machine registers after each high level and/or assembly instruction.

The debugger is configured by the logical values of four user variables: TRACE, DEBUG, SINGLE.STEP, and DUMP.REGISTERS.  Each of these can be turned ON or OFF by the programmer.  If TRACE is ON, QED-Forth compiles a trace instruction before every compiled command.  When the traced words execute, the trace instruction checks the DEBUG flag and, if it is ON, prints the stack picture at that point in the program and the name of each traced command.  If SINGLE.STEP is ON, the trace instruction stops after each command and enters the BREAK mode which is a FORTH-style interpreter that lets the programmer examine and alter variables, stack items, etc.  If DUMP.REGISTERS is ON, the trace instruction prints the contents of the registers so you can see how each instruction affects the machine registers.  This is especially useful when debugging assembly-coded routines.

Those who need even more sophisticated debugging tools can define a customized diagnostic action and install it (using the word IS.TRACE.ACTION) as the first action that is executed by each compiled trace instruction.

A Sample Debugging Session

Let’s look at an example of a buggy definition and use the debugger to find and correct the fault.  Suppose a word is defined to calculate the sum 1+2+3 as

 

ANEW DEBUG.SESSION

: 1+2+3  ( -- n )

  0 LOCALS{ &sum }

  3 1 DO

    &sum I + TO &sum

  LOOP

  &sum

;

1+2+3 .

3   ok

Execution of the definition shows that it calculates 3 instead of the 6 that is expected.  Let’s say that we don’t know what’s wrong, and we aren’t fully sure how the DO LOOP is working, so we decide to TRACE the definition.  This is accomplished by setting the user variable TRACE to true, and re-compiling the definition:

 

TRACE ON

ANEW DEBUG.SESSION

: 1+2+3  ( -- n )

  0 LOCALS{ &sum }

  3 1 DO

    &sum I + TO &sum

  LOOP

  &sum

;

Because TRACE is ON, the compiler inserts a trace instruction before every command in 1+2+3.  At execution time, each compiled trace instruction checks to see if DEBUG is ON. If so, it prints the current stack picture and the name of the currently executing command.  Thus we get a step-by-step picture of the execution of the word 1+2+3.  In the printout below, the first column is the name of each command which is printed as it is executed.  The second column is a stack print in the standard format, with the number of items followed by a list of the items (a maximum of 5 are displayed) separated by the \ character.  The third column is commentary added to this document to explain what is occurring.

1+2+3 [ 0 ] execute the word

0 [ 1 ] \ 0 0 is placed on stack

LOCA___ [ 0 ] LOCALS{ executes

3 [ 1 ] \ 3

1 [ 2 ] \ 3 \ 1 limit and index for DO are on stack

DO [ 0 ] start of the loop

&0 [ 1 ] \ 0 local #0 = &sum leaves its value

I [ 2 ] \ 0 \ 1 loop index = 1 is on stack

+ [ 1 ] \ 1 add

TO [ 0 ] update &sum

LOOP [ 0 ] start loop again

&0 [ 1 ] \ 1 &sum

I [ 2 ] \ 1 \ 2 loop index = 2

+ [ 1 ] \ 3 add

TO [ 0 ] update &sum

LOOP [ 0 ] the loop has finished too early!!

&0 [ 1 ] \ 3 leave the answer on the stack

; ok [ 1 ] \ 3 the word is finished

In examining the printout, we see that the loop only executes twice, for I = 1 and I = 2.  This trace suggests that instead of 3 1 DO ... LOOP in the definition of 1+2+3, we should have used 4 1 DO ... LOOP.  A corrected version of the routine could now be recompiled.

Note that traces of 16-bit local variables print & (which by convention is usually the first character in a 16-bit local variable’s name) followed by a numeric identifier for the local.  Each 32-bit locals is traced as D& (indicating a double-size quantity) followed by the local’s numeric identifier.  An identifier is used because the names of the locals are not available at the time the trace is performed; they are forgotten as soon as the definition is compiled.  Also note that no trace instruction is compiled for words that are removed from the input stream by another command.  For example, the command TO removes the next word from the input stream, so the local that appears after TO in the definition does not appear in the trace.

TRACE can handle nested definitions.  If another word that calls 1+2+3 is defined and if DEBUG is ON, a trace of all the commands in 1+2+3 is printed when 1+2+3 executes in the higher level word.  If TRACE is ON during the compilation of both 1+2+3 and the higher level word, the output is indented to indicate the nesting of the words.  Try it to see how it works.

The BREAK Mode and Single Step Execution

Assume that a trace of a routine is being printed, and you notice that the problem is that there are too few items on the data stack.  You can type any key to enter the BREAK mode, put the needed item on the stack, and resume the trace where it left off. 

The BREAK mode is a version of the QED-Forth interpreter that displays a BREAK> prompt at the start of every line.  When in this mode, you can execute any FORTH command, examine or modify any memory location, change the stack contents, or even compile new words.  In short, you can do anything that can be done in the standard QED-Forth interpreter.  Executing a single carriage return alone on a line exits the BREAK mode and continues the trace in progress.  BREAK is entered if:

  6.  The kernel word BREAK is compiled into a definition.

  6.  Any key is typed from the terminal while a word is being traced, or

  6.  The user variable SINGLE.STEP is ON, which causes the BREAK mode to be entered after each traced instruction.

The complete register state of the 68HC11 is automatically stacked before BREAK is entered, so a CALL to the BREAK instruction can even be compiled into assembly coded routines.  When BREAK is exited, the registers are restored to the state that they had before BREAK was called.

To exit the BREAK mode while allowing the trace to continue, type a carriage return alone on a line.  Executing ABORT (or causing any error that in turn executes ABORT) exits the BREAK mode and returns control to the standard QED-Forth interpreter, and also halts any trace that is in progress.

We can again execute 1+2+3 and, while the trace is printing, type any key.  If the terminal immediately sends the character (some terminals only send the character after delaying a while!), then the BREAK mode will be entered.  The contents of the stack or of memory locations can be examined and/or altered.  Each time a command line followed by a carriage return is entered, the commands are executed and the BREAK> prompt is printed at the start of the next line. Typing a carriage return alone on the line resumes the trace. 

The variables that control the debugger can be altered from the BREAK interpreter while a trace is in progress.  For example, while in the BREAK mode SINGLE.STEP could be turned ON to enter the BREAK mode after each succeeding line of the trace.  Or DEBUG could be turned OFF to rapidly complete the execution of the word without printing the remainder of the trace.

Assume that we don’t want a full trace of the word 1+2+3, but we are curious about the value that the command I is leaving on the stack on each pass through the loop.  This can be accomplished by editing the word BREAK into the definition just after the I command and recompiling with TRACE OFF.  When 1+2+3 is executed the BREAK mode will be entered after each execution of I and the contents of the stack can be examined and modified if desired.  A single carriage return continues execution until the next BREAK or until the end of the routine is encountered, and typing ABORT terminates the execution of 1+2+3 and returns to the interpreter.

Printing the Register Contents

If the user variable DUMP.REGISTERS is ON, each trace instruction prints a 1-line summary of the register contents.  All register contents are displayed in hexadecimal base, regardless of the value in the user variable BASE.  For example, to trace a simple assembly language routine that adds the value 3 to the top item on the data stack, execute

 

TRACE ON        \ tell QED-Forth to compile TRACE instructions

CODE 3+   ( n -- n+3 )

00 IND,Y LDD    \ put n into D; Y is the data stack pointer

0003 IMM ADDD   \ add 3 to n; result still in D

00 IND,Y STD    \ put n+3 on data stack

RTS             \ return

END.CODE

This word can now be executed with DUMP.REGISTERS ON to see the effect that each assembly instruction has on the contents of the registers:

DUMP.REGISTERS ON

ok

5 3+  [ 1 ] \ 5

PC= 92A1 SP= 87F7 CC=   D8  D=    4 IX= 929B IY= 8AFE

LDD  [ 1 ] \ 5

PC= 92AA SP= 87F7 CC=   D0  D=    5 IX= 929B IY= 8AFE

ADDD  [ 1 ] \ 5

PC= 92B3 SP= 87F7 CC=   D0  D=    8 IX= 929B IY= 8AFE

STD  [ 1 ] \ 8

PC= 92BC SP= 87F7 CC=   D0  D=    8 IX= 929B IY= 8AFE

RTS  ok  [ 1 ] \ 8

The address in the program counter (PC) advances 9 bytes with each instruction.  Each opcode sequence in the definition requires 3 bytes, and each trace instruction requires 6 bytes.  The return stack pointer (SP), index register X (IX), and index register Y (IY, used as the data stack pointer) are unchanged by this simple routine.  The contents of the condition code register (CC) and of the double accumulator (D) change as the top stack item (5) is loaded into D, and then 3 is added to this quantity.  Note that although the data stack pointer IY is unchanged, the contents of the top stack position which is pointed to by IY are modified.

In short, the debugging environment gives you the ability to trace the execution steps in a word, allowing examination of the contents of the stack, registers, or memory locations at any point during the execution of high level or assembly coded words. 

Specifying a Customized Trace Action

The capabilities of the debugging system can be further expanded.  The programmer may install a customized diagnostic routine as the first action that is performed by each compiled trace instruction.  The diagnostic routine must have no net effect on the data or return stacks, and must be compiled while TRACE is OFF; if trace is ON while the trace action is being defined, an infinite loop of calls to the trace routine is initiated.  Installation is accomplished by placing the extended code field address of the diagnostic routine on the stack and executing IS.TRACE.ACTION.  The default trace action (set upon a COLD restart) is NO.OP which is a do-nothing routine.  The NO.OP routine can be installed as the trace action at any time by executing DEFAULT.TRACE.ACTION.

The customized trace action can be used to reduce the time spent localizing a hard-to-find bug.  If we know that a particular condition is closely associated with the onset of the error during a program’s execution, the trace action can be used to turn DEBUG ON when the condition is detected, triggering the trace printout.

For example, assume that a complex program contains a mysterious bug, and we are trying to locate it.  Further assume that we know that the bug causes a particular variable called THE.VARIABLE to be set equal to 0.  One way to locate the problem would be to compile the program with TRACE ON and then execute the entire program with DEBUG ON.  The disadvantage of this is that a great deal of traced output must be examined.  A more efficient method is to write a word that tests the variable in question and, if it equals 0 (which signals that the bug has just occurred), turns DEBUG ON:

 

TRACE OFF      \ make sure trace is off while defining trace action

: CHECK.FOR.ERROR        ( -- )

THE.VARIABLE @  

IF DEBUG ON

ENDIF ;

This word is then installed as the trace action by executing:

CFA.FOR CHECK.FOR.ERROR  IS.TRACE.ACTION

Now the program is compiled with TRACE ON, and executed with DEBUG initially OFF so that the program runs at near full speed and no traced output is written to the screen:

 

TRACE ON

... compile buggy program here ...

 

DEBUG OFF

... execute buggy program

As soon as the bug manifests itself, the trace action word turns DEBUG ON which initiates the printout of all trace information.  This shows us where the bug is occurring in the program.

Other Handy Debugging Words

Two kernel words are very useful during debugging sessions: PAUSE.ON.KEY and DUMP. PAUSE.ON.KEY, described earlier in this chapter, can be compiled into any word to allow you to suspend (with any keystroke) or ABORT (with a carriage return) its operation.  This is especially useful when you are uncertain of how the word will function.  For example, if you are uncertain as to whether a BEGIN ... UNTIL loop in a word will terminate properly, put a PAUSE.ON.KEY command into the loop so that the word can be paused or aborted with a keystroke if something goes wrong.

DUMP expects an extended address under a count on the stack.  It prints the contents of count bytes in memory starting at the specified address.  The contents are printed in hexadecimal base regardless of the current number base, and the ascii equivalent contents are listed in a separate column.  DUMP can be used to examine compiled code, check the contents of arrays, examine variables, and make sure that memory locations are being modified properly.

<< Previous | Next>>


Home|Site Map|Products|Manuals|Resources|Order|About Us
Copyright (c) 2012 Mosaic Industries, Inc.
Your source for single board computers, embedded controllers, and operator interfaces for instruments and automation