The IDE: Writing, Compiling, Downloading and Debugging Programs
In this Chapter we’ll explore the QVGA Controller’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 QVGA Controller’s onboard funcitons; and,
- How to interactively debug your programs.
Writing Programs
In the prior Chapter we introduced the Mosaic IDE and the tools you can use to edit, compile, and download your program.
Sylistic Conventions
Code Comments
At the top of the GETSTART.C
source code file are some comments that tell what the program does. 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 capital letters indicating the start of a new subword within the function name. For example:
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 3-1 are created:
Table 3-1 Enter your Table Caption here.
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 QVGA Controller 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 QVGA Controller.
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 QVGA Controller’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 QVGA Controller’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 QVGA Controller, 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 QVGA Controller. These routines let you control the A/D and D/A converters, digital I/O, serial ports, keypad, display, elapsed-time clock, real-time clock/calendar, and many other useful functions. The ALLQED.H
file also gives you access to the QVGA Controller’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:
_forth
.
Many functions at are callable from C are actually of the _forth
type. This includes functions that are in the kernel on the QVGA Controller, or are part of software distributions such as the Graphical User Interface (GUI) Toolkit or the ATA Flash Card Software Package. 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
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. In addition, if your program grows large enough, it may not fit on a single page of flash memory. The Make Tool allows you to divide your source code into separate files that will each be loaded onto a separate page of flash memory, and to make functions in each source code file available from within all the others.
One of your files must be the primary source code file, and it should contain the main()
function; for example, the primary file may be called code.c
. Other source code files must be named using a suffix added to the name of the primary source code file, and all must have the file extension .c
. For example, your three source code files could be named:
- code.c This is the primary file that you compile.
- code1.c CODE1.C and CODE2.C are subsidiary files that are automatically compiled and linked when CODE.C is compiled.
- code2.c
Because filenames are translated internally to an 8-character representation plus file extension, it is a best practice for the base filename (the part before the file extension) to be no longer than 8 characters. Note also that the Make Tool cannot handle filenames that start with a numeral; thus a source code file named 1CODE.C
cannot be compiled.
Finally, a header file must be created, and it must contain function prototypes declared using the extern
keyword for each function that needs to be called in a source code file other than the one in which it is defined. It must also contain extern
variable declarations for any global variables defined in one source code file that must be accessible from within other source code files. For instance, the header file could be called code.h
. Each source code file must then have the following statement near the beginning:
#include "code.h"
And, code.h
would look like this:
#ifndef CODE_H #define CODE_H extern int hamsterCount; extern RESOURCE pf; extern double hamsterVelocity( double age, double time ); extern _Q int Convert12( int channel ); #endif // #ifndef CODE_H
To compile multiple source code files, open the primary file in Mosaic IDE. If it is already open among other files, select its tab to bring up the primary source code file. Then under the Tools menu, select Multi-Page Make. This will compile each source code file to a separate page of flash memory and properly link all function calls.
Finally, note that one and only one of the program files must include a function named main()
.
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 QVGA Controller. Understanding how QED-Forth operates will empower you to take full advantage of the debugging capabilities of the QVGA Controller.
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 QVGA Controller. 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 QVGA Controller; 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 QVGA Controller.
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 QVGA Controller. 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:
- 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(
- 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 QVGA Controller. - 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. - The next space-delimited token found is
int
, which looks for the next token (in this case,radius
). Becauseradius
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 byradius
. The contents are left on the Forth data stack. - 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 theCalcArea()
function as defined in the C program at the specified execution address. - 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 QVGA Controller. 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 (faster!) baud rate for the serial port to speed downloads using the BAUD1.AT.STARTUP command.
- Configure the QVGA Controller to execute a specified program each time a reset or restart occurs using the 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.