|
Chapter 3
« Previous | Next »
Your First Program
This Chapter will get you started using the Control-CForth
language to program your Handheld. It will guide you through the installation
of the Mosaic IDE, an integrated editor, compiler,
and terminal, and you'll start up and talk with your controller. You'll also:
Compile and download your first program using an example multitasking program that performs calculations using floating point
math, stores data in arrays in the Handheld's extended memory space, and prints
the results to the terminal;
Selectively execute any program function using the Handheld's on-board debugging environment (called QED-Forth);
Use the Handheld's built-in operating system to access extended memory;
Use the terminal to interact with a running multitasking application; and,
See how easy it is to set up a multitasking application.
The Mosaic IDE, which includes the
Control-C Compiler, a full-featured editor and communications terminal,
is provided on an installation CD-ROM. To install it onto your PC, first
insert the Installation CD-ROM into your CD Drive. If the installer does not
launch automatically, browse to your computer's CD drive using the 'My
Computer' icon and double click on 'Setup.exe' to manually launch the
installer.
We recommend that you use the default installation
directory ("C:\Mosaic\") and choose 'Typical Setup' when asked. If you wish to
install into a different directory, you may type in any pathname provided that
it does not contain any spaces. The 'Custom' setup option can be used if
another version of either TextPad (the editor used within the Mosaic
IDE), the Mosaic Terminal (previously called QED-Term), or an
earlier version of the Mosaic IDE has already been installed. However, the
Mosaic IDE requires all of its components to work properly. Please
contact us if you have any questions.
When the installation is complete, you will need to
restart your computer unless you are installing onto a Windows 2000 machine.
Be sure to choose 'Yes' when asked to restart — if you don't, the installation
may not complete properly. If you choose 'No' and restart later we recommend
that to assure a full restart you fully shutdown your computer and then turn it
back on; lesser restarts don't always restart fully.
You are now ready to talk with your Handheld!
Familiarize yourself with the locations of the power and
serial connectors as shown in Chapters 1 and 2. After finding them follow
these steps to turn on your system and establish communications with it:
1. Connect your PC serial cable to the Handheld. You
can use the 10-pin-socket-to-DB9 cable to connect to the Handheld's internal
serial header (on the Processor Board with the instrumetn open and the two
halves connected with the high-density interconect cable) or you can use the
DB25-to-DB9 cable to connect to the DB25 connector on the bottom of the
Handheld. Connect the female end of the 9-Pin serial communications cable to
your computer terminal's RS-232 serial communications port. You can use any of
your PC's COM ports. COM2 is usually available, but some PCs only have COM1
available. If your computer does not have an RS-232 serial port, low cost
USB-to-RS-232 serial cables are available; contact Mosaic Industries for
details.
2. Power up your PC computer or terminal.
3. You should check the
configuration of your Windows communications drivers:
1. On your PC go to the device manager dialog
box by double clicking "System" in the "Control Panel", clicking the "Hardware"
tab, and clicking the "Device Manager" button.
2. In the list of devices open up the list of
"Ports" and double click on "Communications Port (COM2)". (If COM2 is tied up
with another service, such as a fax/modem, you may want to use COM1 instead.)
3. You'll now have a dialog box called
"Communications Port (COM2) Properties". Click the general tab and make sure
you have these settings:
|
|
|
|
Baud Rate
|
19200
|
|
Data Bits
|
8
|
|
Parity
|
None
|
|
Stop Bits
|
1
|
|
Flow Control
|
Xon/Xoff
|
4. We recommend that you use Mosaic Terminal,
the terminal program that comes with the Mosaic IDE. You can start the
terminal by double clicking the Mosaic IDE executable (the primary application
of the Mosaic IDE) and choosing the terminal toolbar button which looks like
this:

(The appearance of this and other toolbar icons may
change in subsequent versions of the Mosaic IDE.) You can also start the
terminal by double clicking the application "MosaicTerminal.exe".
The terminal starts using COM2 by default at a speed of
19200 baud, no parity, and 1 stop bit. Xon/Xoff flow control is enabled, and
the file transfer options are set so that the terminal waits for a linefeed (^J)
character before sending each line (this is very important). You can use any
other terminal program, but it must be configured with these same settings. If
you use another terminal program, you must specify that it use the linefeed
character as its prompt character. It might be denoted as LF, ^J, ascii
decimal 10, or ascii hex A.
If your PC is set up to use a COM port other than COM2
Mosaic Terminal will respond with a warning box saying "Invalid port number".
If so, just go to the Mosaic Terminal menu item "Settings→Comm→Port"
and choose the COM port you chose when configuring Windows in step 3. above.
5. Verify the baud rate of the Mosaic Terminal by
going to "Settings→Comm→Baud Rate" and making sure that 19200 is
selected. This is the baud rate used to communicate with the QScreen
Controller. If the baud rate is incorrect, garbled characters may appear in
the terminal when you try to communicate with the controller.
6. Plug the Handheld's power adapter into a 110 VAC
outlet. European users will need a power transformer for changing European 220
VAC to 110 VAC. Insert the power supply output plug into the power jack on the
side of the Handheld and turn it ON by pressing the ON/OFF keypad button in the
lower right corner of the keypad.
1. If no application program is running you
should now see the following response:
Coldstart
QED-Forth V4.4x
2. If there is a demonstration program running
and if you hit the enter key while the cursor is in your terminal window you
should see the Handheld respond with,
ok
The serial communications responses indicate that your
Handheld is now working!
If Something Doesn't Work
If no message appears on your terminal there's something
wrong, so:
1. Verify that power is being properly applied to the
controller.
2. Verify that the serial cable is properly connected.
3. Check the terminal configurations of the Mosaic
Terminal (using the menu item "Settings → Comm"), and recheck the
communications properties of the Windows communications port.
4. Perform a "special cleanup".
Doing a
"Special Cleanup"
If you ever need
to return your Handheld to its factory-new condition, just do a Special
Cleanup: With the power on, install jumper J11 on the processor board,
install and remove the reset jumper, J12, then remove J11. This procedure will
remove any application programs and reinitialize all operating system
parameters to their factory-new condition. Note that this procedure removes
any demo programs too.
If you still are having trouble, contact us.
Using the Editor and
Compiler
The Mosaic IDE has two main components, the TextPad editor, which includes the Control-C compiler, and the
Mosaic Terminal serial terminal program, both of which you'll find in the
default directory "C:\Mosaic\":
TextPad is a fully featured and highly
configurable text and program editor. You'll use it to write and compile your code. All
of the functions of the C compiler tools are available through the controls in
TextPad. You can launch TextPad from the 'Mosaic IDE' group in the 'Programs'
section of your Windows 'Start' menu. For convenience, you may want to place a
shortcut to it on your desktop or on your Windows Taskbar.
Mosaic Terminal is a serial communications
terminal that allows you to interactively control your controller over its
RS-232 interface. You'll also use it to download your
compiled C programs Forth source
code for compilation into the memory of the Handheld. Mosaic Terminal
may be launched from the 'Mosaic IDE' group within 'Start→Programs', but it is also available from
within TextPad, either from the 'Tools' dropdown menu or by clicking the
terminal icon on TextPad's toolbar.
You can type characters directly into the terminal
window, and they will be accepted by the Handheld's line editor and interpreter.
This mode of interaction is convenient when debugging or typing short code
fragments. If you are sending source code to the Handheld, it is best to
create a file first. The file can be saved to disk to provide a record of your
work, and the terminal program can be used to download the file to the
Handheld. You can also use the terminal to record your debugging sessions and
save them as a file on disk.
The TextPad Tool Bar 
Along with the standard tools you expect in a text editor
you'll find some custom tools available in the toolbar that you'll use to
compile and download your programs. Each of these tools is also accessible in
the 'Tools' dropdown menu of TextPad. For C programmers the Debug icon
calls the C compiler and assembler only, the Single-Page Compile icon
performs a standard build of your program, and the Multi-Page Compile
icon performs a multi-page memory model build of your program. Forth
programmers won't need to use these tools. Both C and Forth programmers will
find the Terminal icon useful; it launches the Mosaic Terminal
program. Each of these tools is described in more detail below.
The 'Debug Tool' Finds Syntax Errors in C Programs
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 by the Handheld's resident operating
system as they are downloaded. The Debug
Tool, designated by the "Magnifying Glass" icon, invokes the compiler and
assembler only — it does not produce downloadable code. Use it to quickly
check the syntax of a program and find compilation errors without performing
the full build which would be needed to download the program into the microcontroller.
The 'Single-Page Compile Tool' Compiles and Makes a
Downloadable Single-Page C Program
The Single-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, designated by the
"Single-Page" icon, performs a standard, single-page memory model build of your
program. If you are compiling a program whose compiled size does not exceed 32
Kbytes of memory you should use this mode for fastest execution. Although this
program size is sufficient for many applications, you may need to use the
multi-page build if your application grows beyond 32 K. If you see the
following warning printed in the compiler output, then you must switch to the
multi-page memory model build (using the Multi-Page Make icon):
WARNING: Input section
".doubleword" from 'progname.o11' is not used !
The 'Multi-Page Compile Tool' Compiles and Makes a
Downloadable Long C Program
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, designated by the "Multi-Page" icon, invokes the C compiler's
multi-page build mode. Programs compiled in this mode may be many pages in
length limited only by the amount of FLASH installed in the Handheld. It is always
a good programming practice to break large projects into multiple smaller
source code files for organization, and the multi-page build also uses this
organization for distributing the compiled program across multiple 32 Kbyte
pages. Thus, no source code file may contain more than 32 Kbytes worth of
compiled source code or the above warning will be issued and the program will
not run. A more detailed description of this behavior is available in Chapter 4.
You may wonder why there are
both "Single-Page" and "Multi-Page" compile modes. The reason is that C
function calls between pages take just a little longer to execute (calls to
functions on a different page take 49 microseconds while those on the same page
or to common memory take only 11.5 or 13.75 microseconds, respectively).
Because most function calls are to functions on the same page or to common memory,
page changes are rare; the average execution speed of multi-page C applications
is still quite fast.
'Mosaic Terminal' Communicates with Your Product
The Terminal icon
launches the communications program, Mosaic Terminal. When you launch Mosaic Terminal
for the first time, check the communications settings (Settings→Comm) to
verify that the serial port is set correctly for your computer.
Now that we've learned about the
Handheld's hardware, established serial communications, and installed the
Mosaic IDE on the PC, it's time to compile, download and execute a C program.
We'll also explore the Handheld's on-board operating system and use it to
interactively debug a program.
In this section we'll be running
a simple program that does some computation and communicates its results on the
serial port. The program is one of several examples for use with the Control-C
IDE in the "\Mosaic\Demos_and_Drivers\Misc\C Examples" directory. Let's
compile, download, and run it.
Start TextPad, and open the
source-code file "getstart.c" from the C_Examples directory. You should see
the source code in an open window — browse through it to get a feel for it.
You'll see that the final routine in the file is called main(); it's the top-level executable program. The first routine
within main() that is called when the program starts is InitVars(). Note that in the run-from-place applications of embedded
systems it's important to initialize all variables with a run-time procedure
when the program starts. Variables that are initialized when the program is
compiled are not automatically initialized when the program runs; you should
have a runtime routine in your code that does that.
Clicking on the Single-Page
Compile icon will compile and produce a downloadable form of the program, named
"getstart.dlf". A new window named 'Command Results' will appear so that you
can watch the compilation process. When compilation has finished, you can
scroll the Command Results window up and look for warnings, which don't prevent
creating a valid download file, and errors, which do. You should see two
warnings near the beginning:
GETSTART.C(126): Warning:
Expression is always TRUE !
GETSTART.C(197): Warning:
Symbol 'unused_variable' is never used in function 'main' !
We deliberately inserted into
'main' a variable named unused_variable that is never used in the function. If you double click
on an error or warning line in the command results, TextPad will jump to the
corresponding line in the affected source file. Despite the warnings, the
program should have compiled successfully; the command results will end with:
Qcc-> Creating QED
Download File: getstart.dlf
Tool Completed
Successfully
You can quickly switch between
the Command Results window and your source code file either by hitting
Ctrl-Tab, or by clicking on the file tabs at the bottom of the TextPad window.
The file named "getstart.dlf" is
ready to be downloaded to the microcontroller using the Mosaic Terminal program.
If it is not already open,
launch Mosaic Terminal either from the 'Start' menu or using the TextPad
toolbar or dropdown menu. It's most convenient to use the Terminal icon on the
TextPad toolbar. You should be able to hit enter at the Mosaic Terminal prompt
and see the 'ok' response with the microcontroller plugged in and turned on.
If this is not the case, check your communications settings and cabling.
Now, select 'File→Send File'
from the Mosaic Terminal menu and enter the
"\Mosaic\Demos_and_Drivers\Misc\C Examples" directory, or wherever you compiled
the program. Set the file type to "Download Files (*.dlf)" and select "getstart.dlf". You will see various commands and hex data scrolling on
the screen as the file is downloaded to the microcontroller. When the download
is complete, the text will turn from gray to green to indicate that it is
finished. Now, it's time to run your program.
To execute the top level
function of your code, simply type 'main' and press enter,
main
The 'Enter' key is represented
by the symbol in the line above.
The getstart program will
respond with:
Starting condition:
The radius is 0; the
circular area is 0.
ok
While on its face that doesn't
seem a very impressive response, you're running your first program! This
particular example program uses multitasking. The program runs a background
task called CalculationTask
continuously, incrementing a radius variable and using it to compute a new
area. The program is running in its own task, leaving the communications task
free so you can continue to interact with the controller.
You will notice that you can hit
enter, and use the interactive debugging capabilities even though the program
is still running. For example, try executing the following function
interactively from the terminal:
Announce( )
Note that you must type the
space after the ( character. Each time
you execute this function you'll notice that the output is different, as the
radius is being continuously incremented by the background task. Now try
executing,
Nap( )
which puts the background
CalculationTask ASLEEP. If you again execute
Announce( )
several times, you will notice
that the radius and area are no longer being updated by the CalculationTask. To wake up the CalculationTask again, type
Wakeup( )
and notice that the calculation
is again being performed by the task.
You may want to stop the
program; in particular you'll need to stop it before attempting any new
downloads. This can be done most easily by simply entering 'warm' at the microcontroller's prompt. The warm restart causes
a soft reset to occur, terminating any background tasks that may be running.
After having run this program,
you may want to play with the other example programs in the
"\Mosaic\Demos_and_Drivers\Misc\C Examples" directory. We strongly recommend
that you compile these programs and work through the examples as suggested in
the text of this manual. This will provide you with a thorough "hands-on"
introduction to the Control-C programming environment.
We have seen how to
interactively call the main() function from
the terminal to execute our entire program; most C development environments let
you do this. But the Handheld's operating system makes it easy to
interactively execute any designated function in your program. By simply preceding
a function definition or prototype with the _Q
keyword (we chose "_Q"
as a unique keyword that suggests QED), you can ensure that the function
will be interactively callable from your terminal.
An example: Announce( )
Displays an Area and Radius
For example, to display a
summary of the current values of the radius and calculated circular area
variables, we would like to call the function Announce( ).
Using the editor, look near the
top of the GETSTART.C file and you'll
see that its definition is:
_Q void Announce(void)
{ printf("\nThe radius is %6u; the circular area is
%5.4g.\n",radius,area);
}
The void keywords indicate that the Announce( ) function does not return a value, and does not expect any input
parameters to be passed to it.
The _Q declarator instructs the compiler that we want to be able
to interactively call this function using the on-board QED-Forth interpreter.
The names and execution addresses of all functions defined with the _Q designator are placed in the .DLF
download file so that QED-Forth will recognize them and will be able to
interactively execute them.
The printf() function invoked in Announce( ) prints the specified string to the serial1 port. The
parameters of the printf() function are well
defined by the ANSI standard, and are described in many excellent texts.
Briefly, the \n is an escape sequence
that instructs printf to insert a newline
at the designated places in the string. The %
characters are formatting symbols that tell the compiler to substitute the
listed arguments (in this case, the radius and area) for the % sequences at runtime. The %6u
sequence tells the compiler to display the radius as an unsigned decimal number
with a minimum field width of 6. The %5.4g
sequence tells the compiler to display the area using either decimal or
exponential notation with a precision of 4 decimal places to the right of the
decimal point, and a minimum field width of 5.
The printf() function in Control-C differs from the ANSI standard in
one respect: the maximum length of a printed string is limited to 80 characters
instead of the standard 255 characters. This limitation also applies to the
related functions named sprintf() (which
writes a string to a buffer) and scanf() (which inputs a string). Of course, you can handle strings
longer than 80 characters by using multiple calls to these functions.
Interactively Calling
Announce( )
To interactively call this
function, simply type at your terminal
Announce( )
followed by a carriage return
(indicated by the arrow above). Spaces are important to the QED-Forth
interpreter which processes this command; make sure that there is no space
between the function name Announce and the opening parenthesis (, and there
must be at least one space after the opening parenthesis. If QED-Forth
does not recognize your command, it will repeat what you typed followed by a "?" character and wait for another command, so you can try
again. The case of the letters does not matter: you can use all uppercase, all
lowercase, or any combination when typing commands for the QED-Forth
interpreter.
After calling Announce( ), you should now see the message
The radius is 0; the
circular area is 0.
on your screen, except that the
printed values of the radius and area will correspond to the values they had
when you executed the "WARM" command to stop
the calculations. Then you will see an additional line of text starting with "Rtn:" that summarizes the return value of the function in several
formats, followed by the "ok"
prompt. Because the Announce() function has no
return value, the return value summary is not relevant. The "ok" prompt indicates that QED-Forth has successfully called
the function and is now ready to execute another command.
If you make a mistake while
typing a command, just type "backspace" or "delete" to erase as many characters
as necessary on the current line. Once you've typed a carriage return, though,
QED-Forth executes your command. You can't edit a command that was entered on
a previous line. If you type an incorrect command and then type a carriage
return, you may receive the "?"
error message which means that QED-Forth does not understand the command you
typed. If this happens, you can usually just re-type the command line and
continue.
Area Calculation
The next function defined in GETSTART.C is called IncrementRadius(). This simple function increments the radius variable, and
resets it to 0 when it exceeds the MAX_RADIUS constant. As described below, IncrementRadius() is called from the infinite loop in CalcForever(); this results in the radius taking on all integer values between
0 and 1000.
The next function defined in the
GETSTART.C file calculates the area of a circle; its definition is:
_Q float CalcArea(uint radius)
{ return PI * radius * radius;
}
As described above, the _Q designator flags this function as one that can be called
interactively. The "float" keyword declares that the function returns a
floating point value, and the parameter list tells us that the function expects
a single unsigned integer (uint) as its input.
(Note: uint and other useful type abbreviations and declarations are
defined in the TYPES.H header file in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory.)
To interactively test this
function with an input radius of 5, type at your terminal
CalcArea( int 5)
followed by a carriage return.
QED-Forth uses spaces as delimiters; consequently, you must type at least one space
after the ( character and after the "int"
keyword. You should see something like the following response at your
terminal:
Rtn: 17053 5242
=0x429D147A=fp: 78.54
This line summarizes the
returned value in several formats, including decimal or hexadecimal 16-bit
values, 32-bit hexadecimal, and floating point. Because the CalcArea() function returns a floating point (fp) value, the final number on the line, labeled
=fp: 78.54
is the relevant return value.
Indeed, 78.54 is the area of a circle that has the specified radius of 5. You
can execute the function with any integer input as the radius, and verify that
it returns the correct circular area. This capability enables interactive
testing of the function over its allowed range of input values. Such thorough
function-by-function testing of a program facilitates rapid development of
reliable programs.
In the next chapter the
interactive debugging process will be explored in more detail. You will learn
how to examine the values of static variables and Forth arrays, pass parameters
by value or by reference, generate hexadecimal and ascii dumps of memory
contents, and modify the contents stored in variables and Forth arrays.
Restrictions on the Use
of _Q
Nearly every function in the
many sample programs in the \MOSAIC\DEMOS_AND_DRIVERS\MISC\C
EXAMPLES directory is declared with the
_Q keyword to facilitate easy debugging. There are, however,
two restrictions associated with the use of the _Q
declarator.
First, a function defined using
the _Q keyword cannot use
...
(ellipsis) in its parameter
list; rather, the number of input parameters must be specified when the
function is defined. (If you try to define the _Q
function with an ellipsis as an input parameter, the compiler will issue a
warning and remove the _Q specifier,
so you will not be able to interactively call the function during debugging.)
The second restriction is that
the _Q function cannot be called via a function pointer if the
function accepts input parameters. In other words, do not use the _Q declarator if:
a. You need to call the
function using a function pointer; and,
b. The function accepts
input parameters.
This restriction does not affect
many functions. Any function declared using _Q
can always be called in the standard way (that is, by invoking the function
name followed by parentheses that contain any input parameters). Moreover, any
_Q function can be called indirectly via a function pointer
(by passing its name without any parentheses) if the function's input parameter
list is "void".
The Handheld's onboard operating
system, called QED-Forth, provides numerous run-time services, including
providing a heap memory manager. Using this memory manager we can access the
controller's extended memory.
1 Megabyte Addressable
Memory Space
The standard 68HC11 processor
can address 64 kilobytes of memory using 16-bit addressing. The Handheld
expands the address space to 1 Megabyte, addressing the lower 32 Kbytes of the
processor's memory space by means of a 5-bit "Page Latch" that selects one of
32 pages. The 32 pages times 32 Kbytes per page yields 1 Megabyte of
addressable memory. The upper 32 Kbytes of the 68HC11's address space is called
the "common memory". This address space is always accessible, regardless of
the contents of the Page Latch.
Available Common RAM
The ANSI C compiler supports the
standard 16-bit addressing scheme via its small memory model. It also
supports a medium memory model that allows functions to be called on any
specified page using a 24-bit address (16-bit standard address plus an 8-bit
page). All C variables and C arrays, however, must be accessible using a
simple 16-bit address. For practical purposes, this means that variables and C
arrays must reside in the Handheld's available 8 kilobytes of available common
RAM located at addresses 0x8E00 to 0xADFF. In multitasking applications, this
RAM is also used for task areas; each task requires 1 Kbyte of common RAM
area.
You are of course free to use
ANSI-standard C arrays located in the variable area in common RAM. These
arrays allow you to use standard C pointer arithmetic, and their use is
explained in all of the C textbooks. However, if you need to store a lot
of data, the available 8K of common RAM may not be sufficient. But don't worry
— you can still use all the memory.
Built-in Array Library
Routines Manage Access to Paged Memory
The FORTH_ARRAY routines that reside in ROM on the Handheld provide an
efficient means of accessing the large paged address space for storage of
data. The pre-defined DIM() macro makes it
easy to dimension a 2-dimensional array to hold signed or unsigned characters,
integers, longs, or floating point values. Other pre-defined library functions
handle storing, fetching, and copying data to and from the arrays. These
QED-Forth functions are callable from C, and provide access to a large contiguous
memory space that is very useful for real-time data storage and analysis.
Each array is referred to using
a named 16-bit pointer to a "parameter field" structure in common RAM. Once
the array has been "dimensioned", this structure holds the number of rows and
columns, data size, and a pointer to the QED-Forth heap where the array is
allocated. The ROM-resident heap manager allocates and deletes the arrays in
real time under the control of the C program, thereby maximizing the effective
use of available paged RAM.
This section introduces the use
of the arrays, and as we'll see in a later chapter, they are very useful for
storing data from the Handheld's A/D convertors. The header file named ARRAY.H in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory contains all of the function and macro
definitions that are used to access Forth arrays, including the DIM(), FARRAYFETCH() and FARRAYSTORE() macros that are mentioned in this section.
Declaring and
Dimensioning a FORTH ARRAY
Let's look at the example code
in the GETSTART.C file. Approximately 1/3 of the way into the file, you'll
find a section called "Array Dimensioning, Storing and Fetching". The first
command in this section is:
FORTH_ARRAY circle_parameters;
which declares a new FORTH_ARRAY named circle_parameters and allocates storage for the
structure in the variable area in common RAM. FORTH_ARRAY
is a struct
typedef (see the ARRAY.H file) that specifies how the dimensioning information for
the array is to be stored. Whenever we want to call a function to operate on
this array, we will pass the pointer
&circle_parameters
as an argument to the function.
After using #define directives to define some dimensioning constants, we
encounter the following function definition:
_Q void DimAndInitFPArray(float value,int rows,int
cols,FORTH_ARRAY* array_ptr)
{ int r,c;
DIM(float, rows, cols, array_ptr); //
dimension; allocate in heap
for(c = 0; c < cols; c++) // for each
column
for(r=0; r< rows; r++) // for each row
FARRAYSTORE(value,r,c,array_ptr); // store
in array
}
The function dimensions a FORTH_ARRAY and initializes all elements of the array to have a
specified floating point value. The inputs are the floating point value, the
number of rows and columns, and a pointer to the FORTH_ARRAY
structure in common memory. After declaring the automatic variables r and c, the DIM() macro is invoked to emplace the dimensioning information
in the FORTH_ARRAY structure, and allocate memory for the array in the heap.
The first parameter expected by DIM() is a type specifier; type definitions and abbreviations
are defined in the TYPES.H file in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory. Valid type arguments for DIM() include the following:
char unsigned char uchar
int unsigned int uint
long unsigned long ulong
float xaddr
The next two input parameters
expected by DIM() are the number of
rows and columns, and the final input parameter is a pointer to the FORTH_ARRAY structure. The nested for()
statements cycle through each row and column element in the array, calling the
macro FARRAYSTORE() to store the specified value into the array element. FARRAYSTORE() expects a floating point value, row and column indices,
and a pointer to the FORTH_ARRAY
as its inputs.
The starting "F" in the name FARRAYSTORE() means "floating point"; a parallel macro named ARRAYSTORE() is used for arrays that contain signed or unsigned char, int, or long data.
The SaveCircleParameters() function in the GETSTART.C file calls the macro FARRAYSTORE() to store the radius and area as floating point values in
their respective columns of the circle_parameters array. Then it increments the row_index variable, handling overflow by resetting the row_index to zero to implement a circular storage buffer.
The next function in GETSTART.C is called PrintFPArray() which prints an array of floating point values to the
terminal. Its definition is as follows:
_Q void PrintFPArray(FORTH_ARRAY* array_ptr)
{ int r, c;
putchar('\n');
for (r = 0; r < NUMROWS(array_ptr); r++)
// for each row
{ for (c = 0;c < NUMCOLUMNS(array_ptr);c++)
// for each col
printf("%9.4g ",FARRAYFETCH(float,r,c,array_ptr));
// min field width=9;precision=4;g=exp or decimal
notation
putchar('\n'); // newline after each row is printed
PauseOnKey(); // implement xon/xoff output flow control
}
}
As usual, the _Q declarator allows this function to be called interactively from the
terminal. PrintFPArray() expects a
pointer to a FORTH_ARRAY
as its input parameter, and uses 2 nested for()
statements to print the contents of the array one row at a time.
The printf() statement invokes the Forth library macro FARRAYFETCH() to fetch the contents of the array at the specified row
and column. FARRAYFETCH() returns the
value stored in the array; it expects a type specifier (used to cast the return
value to the required type), row and column indices, and a pointer to the FORTH_ARRAY as its inputs.
The %9.4g argument to printf() specifies that the number should be printed using either
decimal or exponential formatting (whichever displays better precision), with 4
digits to the right of the decimal point and a minimum field width of 9
characters. The putchar('\n') statement
inserts a newline character after each row is printed. The PauseOnKey() function is a handy library routine that serves 2
purposes:
It implements XON/XOFF output flow control to avoid
"inundating" the terminal with characters faster than the terminal can process
them, and
It allows the user to abort the printout by typing a
carriage return from the terminal.
For further details, please
consult the definition of PauseOnKey() in the
Control-C Glossary.
To see how the DimAndInitFPArray() function is called, scroll down to the function named CalcForever() in the GETSTART.C file. The first statement in the function is:
DimAndInitFPArray(0.0,CIRCLE_ROWS,CIRCLE_COLUMNS,&circle_parameters);
where 0.0 is the floating point
value to be stored in each element, the constants CIRCLE_ROWS
and CIRCLE_COLUMNS specify the number of rows and columns in the array, and &circle_parameters is a pointer to the FORTH_ARRAY.
Interactively Dimension,
Initialize and Print the Array
It is easy to interactively call
the functions that we've examined. The syntax that we'll type at the terminal
looks similar to an ANSI C function prototype, with one of the following type
declarators being used before input parameters that are passed by value:
char int long float
char* int* long* float*
When passing the address of a
variable or a structure, use only the name of the variable or structure,
without any additional declarators or &
operators. All of this is explained in detail in a later Chapter;
for now, the goal is see how easy it is to use the interactive function calling
tools.
For example, to interactively
dimension and initialize the circle_parameters array to have 10 rows, 2 columns, with each element
initialized to a value of 34.56, type the following line at your terminal:
DimAndInitFPArray( float
34.56,int 10,int 2,circle_parameters)
Remember to type at least one
space after the ( character, and after the float and int keywords.
QED-Forth will respond to your command with a line of text that summarizes the
return value of the function, followed by the "ok"
prompt. We can ignore the return value summary, because this function does not
return a value.
Now to verify that the
initialization was performed correctly, we can type at the terminal:
PrintFPArray( circle_parameters)
and, as always, we make sure
that there is a space after the ( character. Note that we do not use the & (address-of) operator before the circle_parameters argument; it turns out that circle_parameters has already been defined in QED-Forth as the base address
of the FORTH_ARRAY structure.
QED-Forth calls the function
which prints the contents of the circle_parameters array, and then summarizes the return information (which
we can ignore in this case). You can verify that the value of each array
element is the same one that you specified when you called the DimAndInitFPArray() function. (Slight differences in the values are due to
rounding errors in the floating point conversion and printing routines.) Using
this interactive method, you can test each function with a variety of
dimensioning and initialization information.
Many instrumentation and
automation applications can be logically conceived of in terms of a set of
distinct "tasks" that cooperate to solve the problem at hand. For example, a
program that manages a hand-held sensing instrument might have one task that
acquires sensory data, another that performs calculations to process the data,
and a third task that displays the results on a liquid crystal display.
Using the Handheld's built-in
multitasking executive confers significant advantages when designing real-time
systems. Breaking up a complex program into easily understood modular tasks
speeds debugging, improves maintainability, and prevents source code
modifications of one task from adversely affecting the required real-time
performance of another task.
The Task Activation
Routine
In a multitasking environment, a
"task" is an environment capable of running a program. After declaring
(naming) a new task (which also allocates a 1 Kbyte task area), its environment
is "built" by initializing its required stacks, buffers and pointers in the 1
Kbyte task area. Then the task is "activated" by associating it with an
"activation routine" that performs a specified set of actions.
A typical task activation
routine is the CalcForever() function in
the GETSTART.C file. Its definition is straightforward:
_Q void CalcForever(void)
// this infinite loop function can be used as a task activation
routine
{ DimAndInitFPArray(0.0,CIRCLE_ROWS,CIRCLE_COLUMNS,&circle_parameters);
while(1) // infinite loop
{ IncrementRadius(); // updates radius variable
area = CalcArea(radius); // updates area variable
if(radius%10 == 0) // on even multiples of 10...
SaveCircleParameters(); // save data in FORTH_ARRAY
Pause(); // give other tasks a chance to
run
}
}
The first thing that this function
does is to dimension and initialize the circle_parameters array. Then it enters an infinite loop that increments
the radius variable, calculates the corresponding circular area and stores it
in the area variable, and saves the radius and area in the circle_parameters array if the radius is an even multiple of 10. The
function calls Pause() on every pass
through the loop. Pause() is a multitasking
function that instructs the multitasking executive to change to the next task
(if any) in the round-robin task list. This enables "cooperative
multitasking", in which a task willingly lets other tasks run by executing Pause(). The other type of multitasking, also supported by the
Handheld, is "pre-emptive multitasking", in which an interrupt-driven timeslice
clock forces a task switch on a periodic basis.
In summary, the CalcForever() function is an infinite loop that manages the calculation
and storage of the radius and circular area. This function can be the
"activation routine" for a stand-alone task running in a multitasking
environment.
Declare, Build and
Activate a Task
The short section titled
"Multitasking" in the GETSTART.C file
demonstrates how easy it is to set up a task using the pre-defined macros.
First we declare the new task as:
TASK CalculationTask;
The TASK typedef allocates a 1 Kbyte task structure named CalculationTask in the common RAM.
The function SetupTask() builds and activates the new task; its definition is:
void SetupTask()
{ NEXT_TASK = TASKBASE; //
empty task loop before building
BUILD_C_TASK(HEAP_START,HEAP_END,&CalculationTask);
// private heap
ACTIVATE(CalcForever, &CalculationTask); // define
task's activity
}
The first statement empties the
round-robin task loop by setting the NEXT_TASK
pointer in the task's user area to point to the task's own TASKBASE. The next statement invokes the BUILD_C_TASK() macro which expects starting and ending addresses for the
task's heap, and the address at which the task is located. We have defined the
constants HEAP_START
and HEAP_END to specify a task-private heap occupying 1 Kbyte on page
0. The task base address is simply &CalculationTask. BUILD_C_TASK() sets up all of the stacks, buffers and pointers required by the task.
The final statement in SetupTask() invokes the ACTIVATE() macro which expects a pointer to the activation function
(which is CalcForever) and the TASKBASE address (which is &CalculationTask).
Multiple tasks can be declared,
built and activated in the same way.
Putting a Task Asleep
A "sleeping" task remains in the
round-robin task loop, but is not entered by the multitasking executive. The
status of a task can be changed from AWAKE
to ASLEEP and back again by simply storing the appropriate constant
in the user_status variable in the task's USER_AREA.
The USER_AREA is a task-private structure initialized by BUILD_C_TASK() that contains the pointers that a task needs to operate;
it is defined in the USER.H file in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory. The USER_AREA
structure is the first element in the TASK
structure.
The Nap() function in GETSTART.C is a simple function that puts the CalculationTask asleep:
_Q void Nap(void) // put calculation task asleep
{ CalculationTask.USER_AREA.user_status =
ASLEEP;
}
This function simply stores the ASLEEP constant into the user_status variable in the CalculationTask's USER_AREA structure. A similar function named Wakeup() stores the AWAKE
constant into user_status to wake up the task. We'll see how to use these functions in the next
section.
The main Function Gets Us
Going
The main() function is the highest level routine in the program. Its
definition is:
void main(void)
// Print starting area and radius, build and activate
CalculationTask.
{ int unused_variable; // an example of how warnings
are handled!
InitVars();
printf("\nStarting condition:");
Announce(); // print starting values of radius and
area
SetupTask(); // build and activate the CalculationTask
}
As you recall, the declaration
of the unused_variable was inserted to demonstrate how the Control-C IDE
highlights the source code line associated with compiler errors and warnings. InitVars() performs a runtime initialization of the variables used by
the program; this is very important, because compile-time initializations won't
ensure that variables are properly initialized after the program has run once,
or after the processor is restarted.
After initializing the
variables, main() announces the
starting values of radius and area and then calls SetupTask() to build and activate the CalculationTask. To execute the program, simply type at your terminal:
main
You'll see the following
message:
Starting condition:
The radius is 0; the
circular area is 0.
ok
The "ok" prompt lets you know that QED-Forth is ready to accept
more commands. We have set up a two-task application: the default startup task
(named the FORTH_TASK)
is still running the QED-Forth interpreter, and the CalculationTask that we built is running the CalcForever() activation routine. At any time we can monitor the
current values of radius and area by interactively calling the function:
Announce( )
Remember to type a space after the
( character, and you may have to type slowly so that the multitasking program
does not miss any of the incoming characters from the terminal. To view the contents
of the circular buffer array named circle_parameters, type the command:
PrintFPArray( circle_parameters)
To suspend the operation of the CalculationTask, type:
Nap( )
Now notice that successive
invocations of:
Announce( )
all show the same values of the
radius and area; this is because the CalculationTask is no longer updating them. To re-awaken the CalculationTask, simply type:
Wakeup( )
To abort the multitasking
program altogether and return to a single task running the QED-Forth monitor,
you can perform a "warm" restart by typing:
WARM
The QED-Forth startup message
will be displayed.
Of course, if you want to run
the program again, you can type main or any of the interactively callable
function names at any time. Remember to type
WARM
or
COLD
before trying to download
another program file; the Handheld can't run multiple tasks and accept a
download file at the same time. (Both WARM
and COLD re-initialize the system, but COLD performs a more thorough initialization and causes
QED-Forth to immediately "forget" the definitions of the C functions that were
sent over in the .DLF download file).
Autostarting Your Application
You can configure QED-Forth to automatically execute a
specified application program after every reset, restart, and ABORT. This
makes it easy to design a production instrument based on the Handheld; the
instrument will automatically perform its required function when it is turned
on or reset.
QED-Forth provides two functions named AUTOSTART and
PRIORITY.AUTOSTART that allow you to specify a startup routine. Both write a
pattern in memory that instructs QED-Forth to execute a user-specified
program. AUTOSTART stores the pattern in EEPROM which is inside the 68HC11
processor chip, and PRIORITY.AUTOSTART stores the pattern near the top of page
4 which is typically in PROM in a final turnkeyed system. The EEPROM-based
AUTOSTART function is convenient during program development and debugging, or
in the development of one-of-a-kind systems. But because the startup pattern
is stored in EEPROM inside the 68HC11, it is cannot be automatically
transferred with the application program to a different board.
The PRIORITY.AUTOSTART routine should be used for
PROM-based production systems. It installs the startup pattern in PROM, so
simply reproducing the PROM and plugging it into any Handheld turns that board
into a turnkeyed instrument controller. In other words, the startup instructions
are stored in the same PROM as the application program itself.
Let's assume that you want to want to run the main routine
every time you turn on, reset, or restart the Handheld.
The following command:
CFA.FOR main AUTOSTART
leaves the extended code field address (cfa) of main on
the stack. AUTOSTART then writes a pattern into EEPROM comprising a 16-bit
flag (equal to 1357H) followed by the 32-bit extended cfa of the specified startup
program. All subsequent resets and restarts will call the specified
application program after QED-Forth initializes the system.
To specify the startup vector so that it can eventually
reside in PROM, we would execute a different command:
CFA.FOR main PRIORITY.AUTOSTART
CFA.FOR main
PRIORITY.AUTOSTART
PRIORITY.AUTOSTART writes a pattern starting at 7FFAH on
page 4 comprising a 16-bit flag (equal to 1357H) followed by the 32-bit
extended cfa of the specified startup program. All subsequent resets and
restarts will call the specified application program after QED-Forth
initializes the system.
The priority autostart and autostart locations are checked
each time QED-Forth executes ABORT, which is called after every reset, COLD or
WARM restart, or error. ABORT first checks the priority autostart location at
7FFAH\4, and if 1357 is stored there it executes the program whose xcfa is
stored in the four bytes starting at 7FFCH\4. If the priority autostart
pattern is not present, or if the specified priority startup program finishes
executing and "returns", ABORT then checks the autostart pattern at AE00H in
common memory. If 1357 is stored there it executes the program whose 32-bit
xcfa is stored in the four bytes starting at AE02H.
To remove the autostart pattern or patterns, execute:
NO.AUTOSTART
NO.AUTOSTART
This command clears the priority startup pattern at
7FFAH\4 and the startup pattern at AE00H.
Summary
Now you've worked through the GETSTART.C program in detail. You know how to compile, download and
execute programs, perform simple floating point calculations, print formatted
strings and numbers to the terminal, dimension and access FORTH_ARRAYs in paged memory, define a multitasking application with
an interactive terminal interface, and autostart an application. That's pretty
good considering that this is your first C program on the Handheld!
An
Introduction to Programming in QED-Forth
The Handheld
accepts serial commands from a terminal which is typically a personal computer
(PC). QED-Forth's onboard interpreter, compiler, and assembler compiles 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 a Handheld 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
Mosaic IDE. The IDE's text editor allows you to create and modify text files
and save them on the computer's disk drive. Its 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 are
downloaded to the Handheld. The Mosaic IDE allows you to switch between the
editor and terminal windows quickly and easily.
How To Send
Your Programs to the Handheld
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 select 'File→Send File' from the Mosaic
Terminal menu 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 (as described in a
later Chapter of this manual), the old erroneous
version of your program will be automatically forgotten by the Handheld when
the new version is transmitted.
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. Mosaic Terminal also allows 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.
You download and
develop your programs in RAM. Once they are sufficiently debugged you can then
transfer them to Flash memory for permanent storage. QED-Forth programs are
generally not relocatable — they must execute from the same addresses they were
compiled to. But if they are transferred from RAM to Flash how is this
possible? The solution is to swap the RAM and Flash addresses. Programs are
downloaded and compiled into RAM using a download memory map, transferred
to Flash, then the memory map is changed to the run-time (or standard)
memory map placing the Flash memory at the same addresses that the RAM had
been located. The program then executes from the Flash just as it had from the
RAM.
Let's go through
an example of the steps required to compile and execute a QED-Forth program on
the Handheld. We'll use the RAM on memory pages 1-3 and the Flash on pages 4-6
of the standard memory map.
2. First
establish the download memory map by executing:
DOWNLOAD.MAP
The 'Enter' key
is represented by the ↵ symbol in the line above.
This command
can be typed interactively from the terminal program, or can be the first statement
in your download file. This swaps the RAM and Flash, placing the RAM on pages
4-6 and the Flash on pages 1-3.
2. Establish
memory locations for code and names on pages 4, 5, and 6. One valid way to do
this is by executing,
4 USE.PAGE
which is
equivalent to the following set of commands:
HEX
0000 04 DP X! \ code
starts at address 0 on page 4; 20 Kbytes available
5000 04 NP X! \ names
start at address 5000 on page 4; 12 Kbytes available
8E00 00 VP X! \
variables start at address 8E00 in common RAM
4A00 0F 7FFF 0F
IS.HEAP \ 13.5 Kbyte heap at addresses 4A00-7FFF on page F
Typically, the
USE.PAGE command or other equivalent commands are the first commands in the
source code file. To customize the locations of these memory areas, you can
include edited versions of these commands in your source code file. Make sure
these commands come before the first ANEW statement in your code. For example, to
locate up to 32 Kbytes of code on page 4, and up to 32 Kbytes of names on page
5, you can execute:
HEX
04 USE.PAGE \ set
default use.page locations
0000 05 NP X! \
move names section to page 5, leaving all of page 4 for code.
or,
equivalently,
HEX
0000 04 DP X!
\ code starts at address 0 on page 4; 32 Kbytes available
0000 05 NP X!
\ names start at address 0000 on page 5; 32 Kbytes available
8E00 00 VP X!
\ variables start at address 8E00 in common RAM
4A00 0F 7FFF 0F IS.HEAP
\ 13.5 Kbyte heap at addresses 4A00-7FFF on page F
2. Send
your code to the Handheld. Typically, the first statement of the code is an ANEW command which serves as a "forget marker" to
simplify re-downloading of code. For example:
ANEW MY.CODE
\ choose any name you want here
: HELLO.WORLD ( -- )
\ define a new routine called HELLO.WORLD
CR ." Hi Everyone!"
\ print a message to the terminal
; \ end the
definition
\ < all of your
source code goes here >
2. After
successfully compiling your code, transfer the code and names to flash using
the PAGE.TO.FLASH routine. For example, if all of your code and names are located on page
4, simply execute:
04 PAGE.TO.FLASH \
transfers page 4 RAM to page 1 in flash
If your code is
on page 4 and your names are on page 5, you would execute
04 PAGE.TO.FLASH \
transfers page 4 RAM to page 1 in flash
05 PAGE.TO.FLASH \
transfers page 5 RAM to page 2 in flash
The routine PAGE.TO.FLASH will print an error message if you pass it
an illegal page. The page parameter passed to the routine must be the source
page in RAM where the code has been compiled. PAGE.TO.FLASH transfers all 32 Kbytes of the source page
to the parallel page in flash memory. At this point two copies of your
code and names exist in memory: one copy is in RAM, and the other resides in
flash. But only the RAM version can be executed properly, because it resides
at the same pages at which it was compiled. The next step makes the
flash-resident copy of the code executable.
2. Re-establish
the standard memory map by executing
STANDARD.MAP
from your
terminal. This remaps pages 4, 5, and 6 so that they are addressed in the
Flash device, and maps pages 1, 2, and 3 to be addressable in the RAM. At this
point, the Flash-resident code (typically on page 4, which is now in Flash) can
be executed. The RAM in pages 1, 2, and 3 are available; they can be written
over or used by the application program.
2. Run
the program by executing any of the names that have been defined. You can also
execute an autostart command to cause a specified function to be automatically
called upon each restart. To place the autostart vector in EEPROM inside the
processor, execute the command:
CFA.FOR <name>
AUTOSTART
where
<name> is the name of the designated function. To place the autostart
vector in Flash memory, execute the command:
CFA.FOR <name>
PRIORITY.AUTOSTART
For most
applications, the PRIORITY.AUTOSTART option is preferable because it locates
the autostart vector with the code in the flash memory device.
2. (OPTIONAL)
To download an additional source code file after the prior 6 steps have been
executed, you need to transfer the code back to RAM, establish the download
map, download the next source code file, re-program the flash, and re-establish
the standard map. Assuming you left off after step 6, you would execute the
following:
NO.AUTOSTART \ if you
installed an autostart, undo it
4 PAGE.TO.RAM \ move
code from page 4 in flash to page 1 in RAM
5 PAGE.TO.RAM \ (only
needed if page 5 used); move page 5 flash to page 2 RAM
DOWNLOAD.MAP \ get
ready for download
<send your source
code file here>
4 PAGE.TO.FLASH \
copy updated page 4 code to flash
5 PAGE.TO.FLASH \
copy updated page 5 code to flash
STANDARD.MAP \
code is now executable at pages 4 and 5 in Flash
CFA.FOR <name>
PRIORITY.AUTOSTART \ optional
FORTH
EXAMPLE SESSION
Here's a
transcript of an entire session to show how easy it is to compile a Forth
program into flash. The program, HELLO.WORLD, prints a simple message to the terminal:
DOWNLOAD.MAP
4 USE.PAGE
ANEW MY.CODE
: HELLO.WORLD ( -- )
CR ." Hi Everyone!" ;
4 PAGE.TO.FLASH
STANDARD.MAP
HELLO.WORLD\ executes
the program!
You've compiled
your program, downloaded it into RAM, transferred it to flash, and executed it.
If you want to set up an autostart routine, follow the procedure as explained
above, or consult the appropriate chapter of this manual.
Initializing
the Memory Map
Before downloading
a new program you'll want to make sure that you're starting with a clean slate
from a known initialized condition. To do so, type the command,
COLD
followed by
a carriage return. QED-Forth executes the command as soon as it receives the
carriage return. The COLD command tells QED-Forth to perform a cold restart, initializing
the system to a known state and causing it to FORGET all user-added words. QED-Forth responds
with its cold startup message:
Coldstart
QED-Forth V4.4x
which tells
you the version number of the software on your board.
QED-Forth doesn't
care whether you use capital letters, small letters, or a combination of both
when you type a command or function name. It automatically performs case
conversion so that COLD, Cold, and even CoLd are treated as the same command.
To set up a memory
map that gives plenty of room for programming type the command
4 USE.PAGE
ANEW MY.ROUTINES
Be sure to type at
least one space between the words as shown above (for example, between the 4
and USE.PAGE).
The USE.PAGE
command configures the memory map so that the definitions of new words reside
on page 4 of the memory. The ANEW command followed by a name of your choice
creates a marker which facilitates re-loading your code during program
development. It is good policy to execute an ANEW command after setting the memory map
pointers.
If a bug or error
causes the COLDSTART message to be printed to your terminal while you are programming,
you should re-initialize the memory map with the USE.PAGE command. Consult another chapter
Output
Commands
First, let's have
QED-Forth print a statement. At your terminal, type
." Hi There!"
and QED-Forth
responds with
Hi There!
ok
We have underlined
QED-Forth's response for clarity. The command ." (pronounced dot-quote) tells QED-Forth to
print the characters until the next " (quote) character is encountered. The
space after the ."
is important because FORTH uses spaces to separate commands. To instruct
QED-Forth to print the message on a new line, execute the FORTH command CR (carriage return) before the print command,
as
CR ." Hi
There!"
and notice how
QED-Forth inserts an extra carriage return/linefeed before printing the message
at your terminal.
If you make a
mistake while typing a command, just type "backspace" or "delete" to erase as
many characters as necessary on the current line. Once you've typed a carriage
return, though, QED-Forth executes your command. You can't edit a command that
was entered on a previous line. If you type an incorrect command and then type
a carriage return, you may receive the "?" error message which means that
QED-Forth does not understand the command you typed. If this happens, you can
usually just re-type the command line and continue programming.
The Stack
In FORTH,
parameters are passed among routines on a push-down data stack (referred to as
"the data stack" or "the parameter stack" or simply "the stack"), and nesting
of subroutine calls is handled by a separate "return stack". Most
microprocessors are designed to handle stacks, so this scheme results in high
efficiency. In addition, stack-based parameter passing is a single organizing
principle that unifies and simplifies FORTH syntax.
All arithmetic,
logical, I/O, and decision operations remove any required arguments from the
stack and leave the results on the stack. This leads to "postfix" notation:
the operation is stated after the data or operands are placed on the stack.
To see how this
works, put some numbers on the data stack by typing
5 7
and QED-Forth
responds
ok ( 2 ) \
5 \ 7
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 stack is a feature of the debugging environment. To
disable the stack print, you could execute DEBUG OFF. 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 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 - (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 . (dot). A slightly fancier way to do it is:
CR ." The result is " .
which prints the
response
The result is
30 ok
The command CR causes the output to be printed on a new
line, then the specified message is typed, and then 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 FORTH 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.
Defining
New Words in QED-Forth
Let's define a
routine (called a "word" in FORTH) that increments the contents of a variable.
To create a variable named COUNTER execute
VARIABLE COUNTER
To initialize COUNTER to zero, type
0 COUNTER !
This command first
puts a 0 on the stack, then stating COUNTER leaves the address of COUNTER on the stack, and ! (pronounced "store")
stores the 0 into the COUNTER. To verify the contents of COUNTER, type
COUNTER @ .
and QED-Forth
responds with
0 ok
Here, COUNTER leaves its address on the stack, and the @ (pronounced "fetch") operator removes the
address from the stack, fetches the contents of the address, and leaves the
contents on the data stack. The printing word . (dot) then prints the contents.
To define a word
to add 1 to the contents of COUNTER, type
: INC.COUNTER (
-- )
1 COUNTER
+!
;
You have just
compiled a definition of a new QED-Forth word. The : (colon) tells QED-Forth to append the next
name it encounters (in this case, INC.COUNTER) to the name area of the dictionary. The
comment between parentheses is a "stack picture"; in this case the empty stack
picture reminds us that INC.COUNTER does not expect any inputs and does not
return any outputs on the stack. The remaining commands until the terminating ; (semicolon) form the body of the
definition. The definition puts an increment (1) on the stack, puts the
address of COUNTER on the stack, and calls +! (plus-store) which removes the increment and
address, and adds the increment to the current contents of the address, thus
increasing the value of COUNTER by 1. After receiving the terminating ; QED-Forth responds with the "ok" prompt
indicating that it has compiled (i.e., entered into the dictionary) the
definition; it has not executed the definition yet. To verify this, we could
type COUNTER @ .
just as we did above. But let's type the equivalent expression
COUNTER ?
and note that
QED-Forth responds with
0 ok
which tells us
that the counter is still initialized to 0. Note that the command ? has been pre-defined in the QED-Forth kernel
as:
: ?
@ .
;
So you see that
useful words are built up as combinations of lower level words in FORTH.
Useful sequences of commands (such as @ . ) are defined as higher level words with
descriptive names, thus building a lexicon of convenient and powerful words.
QED-Forth already has many useful functions; you'll define more to address the
unique requirements of your application.
Now we can execute
INC.COUNTER
and again check the contents of COUNTER to see how it is affected. Type
INC.COUNTER
COUNTER ?
and QED-Forth
responds
1 ok
INC.COUNTER increments the value of the variable by 1. This command can be
repeated to show that it always increments the variable. We'll use INC.COUNTER in the next section to demonstrate the
multitasking capability of QED-Forth.
A
Definition Using Floating Point Math and Local Variables
Let's define a
more interesting word that uses floating point math to sum the inverses of the
integers from 1 to 100. The text after the \ character on each line of the definition is
commentary that is ignored by QED-Forth.
: SUM.OF.INVERSES ( --
)
\ prints the sum of
inverses of integers from 1 to 100
ZERO LOCALS{
f&accumulator } \ define and initialize local variable
101
1 \ put loop limit and index on stack
DO \ start loop
I FLOT
1/F \ calculate next inverse
f&accumulator
F+ \ add it to accumulator
TO
f&accumulator \ update value in accumulator
LOOP
CR ." The sum
of the inverses is " \ announce answer
f&accumulator F. \ print answer
;
After entering
this definition, we can execute
SUM.OF.INVERSES
and QED-Forth
responds
The sum of
the inverses is 5.185 ok
The first line of
the word defines a "local variable" named f&accumulator and initializes it to 0.0. Because the name
of the local begins with "f&", QED-Forth recognizes that it is a floating
point (as opposed to an integer) quantity. The next line sets up a loop that
starts at 1 and stops after 100 is reached. The command I puts the current
loop index on the stack, FLOT (pronounced float) converts it to a floating
point number, 1/F inverts it, and then F+ adds it to the accumulator (floating point
math operators typically start with the letter F). The TO instruction updates the contents of the accumulator.
After the loop is finished, the print command announces the result, and F. (f-dot) prints the final value of the
accumulator. Notice how easy it is to program with floating point mathematics,
and how the use of the named local variable eliminates stack juggling and makes
the definition easy to read.
Multitasking
Let's set up a
task whose job it is to continually increment the variable COUNTER while we
simultaneously run the QED-Forth interpreter. Type in (or download a text file
containing) the following set of commands; their meaning and syntax will be
explained in detail in the "Multitasking" chapter.
: COUNT.FOREVER ( --
)
BEGIN
INC.COUNTER
PAUSE
AGAIN
;
HEX
9600 0 TASK: MY.TASK
0\0 0\0 0\0 MY.TASK
BUILD.STANDARD.TASK
CFA.FOR COUNT.FOREVER
MY.TASK ACTIVATE
DECIMAL
You are now
running two concurrent tasks: one task is the QED-Forth interpreter that you've
been using all along to execute your commands, and the second is a task that
increments the 16-bit variable named COUNTER. To verify this, type COUNTER
? and you'll see some
random number that changes each time you type the command as the background
task continually updates the value.
Let's look at what
we've done. First we defined an infinite loop in the word COUNT.FOREVER
that increments the
counter and executes PAUSE. PAUSE is the task-switching word. The remaining command lines define
and build a new task called MY.TASK and activate it so that it performs the
function COUNT.FOREVER. The QED-Forth interpreter is still running, and it executes
the PAUSE
task-switch word while it has spare time; namely, when it is waiting for serial
input from the terminal. Each time QED-Forth executes PAUSE, MY.TASK increments the value of COUNTER and itself executes PAUSE to return control to the QED-Forth
interpreter. Control passes back and forth between the tasks so quickly that
it seems as if the two tasks are executing simultaneously. We could add more
tasks to the round robin task list, and we could set up a timeslice clock to
force more frequent task switching. Those topics are described later in the
"Multitasking" chapter.
Working
with Matrices
QED-Forth makes it
easy to work with matrices, which are defined as 2-dimensional arrays of
floating point numbers. To create two matrices named MY.MATRIX and DESTINATION, type
MATRIX:
MY.MATRIX
MATRIX:
DESTINATION
To refer to a
matrix as a whole, we use the ' (tick) operator in FORTH; it is the apostrophe
character on your keyboard. For example, to dimension the matrix to have 2
rows and 3 columns, type
2 3 '
MY.MATRIX DIMMED
making sure to
separate all of the words by spaces. To load some data into the matrix, type
MATRIX
MY.MATRIX = 1. 2. 3. 4. 5. 6.
(The decimal
points in the numbers flag the numbers as floating point quantities.) Now we
can display the contents of the matrix using the matrix display word M.. by
typing
' MY.MATRIX M..
and QED-Forth
responds by printing the matrix as
MY.MATRIX =
1. 2. 3.
4. 5. 6.
ok
Let's transpose MY.MATRIX so that each row becomes a column and each
column becomes a row. To place the transpose of MY.MATRIX into the matrix DESTINATION and then display the result, execute
' MY.MATRIX '
DESTINATION TRANSPOSED
' DESTINATION
M..
and QED-Forth
responds by printing the transposed matrix as
DESTINATION =
1. 4.
2. 5.
3. 6. ok
There are a host
of other pre-programmed matrix transformations; for now, let's use matrices to
solve a common and important problem.
Solving
Simultaneous Equations
Many applications
require the solution of simultaneous equations with several unknown
quantities. This is easy to accomplish with QED-Forth. Suppose that we need
to solve the following equations:
1.07 x1 + 0.19 x2 + 0.23 x3 = 2.98
0.38 x1 + 1.00 x2 + 0.74 x3 = 3.48
0.12 x1 + 0.53 x2 + 1.20 x3 = 3.70
We can express
them as the matrix equation:
MX = R
where M is the
coefficient matrix, X is the matrix of unknowns, and R is the matrix of
right-hand-side constants (called the residue matrix in linear algebra). That
is,
1.07
0.19 0.23 x1 2.98
M = 0.38
1.00 0.74 X = x2
R = 3.48
0.12
0.53 1.20 x3 3.70
To solve this
matrix equation (that is, to compute the values of X) using QED-Forth, type the
following commands. The text after the \ character on each line is commentary that
explains what is happening; you need not type the comments.
MATRIX:
M \ create coefficient matrix
3 3 ' M
DIMMED \ dimension coefficient matrix
MATRIX M = 1.07
0.19 0.23 0.38 1.00 0.74 0.12 0.53 1.20 \ initialize coefficients
MATRIX:
R \ create residue matrix
3 1 ' R
DIMMED \ dimension residue matrix
MATRIX R = 2.98
3.48 3.70 \ initialize residue
MATRIX: X
\ create matrix of unknowns
' M ' R ' X
SOLVE.EQUATIONS \ solve the equations
' X
M.. \ print the results
QED-Forth
responds,
Matrix X =
2.099
0.8259
2.509
ok
With these few
lines of code, you've solved the set of linear equations with floating point
coefficients. Larger sets of equations with many more unknowns are handled
with very similar code.
In Case
Your Software Crashes
This section
demonstrates why you need not worry about "crashes" caused by programming
errors. QED-Forth gives you complete control over the board's hardware and
software. With all this power, programming "bugs" can sometimes cause the
processor to get lost so that it fails to respond to commands or forgets some of
the functions that you have defined. These situations are called "crashes".
The Handheld is designed so that you can always recover from software-induced
crashes. In other words, as long as you have not damaged the electronic
hardware on the board, you can restore the Handheld to a known functional
state.
For this
demonstration we will initialize the QED software and then add a word to the
dictionary to represent your application software. After putting the routine
in flash and saving the state of the dictionary, we will purposefully crash the
board, and then restore the dictionary to its prior state. This demonstrates
that you can recover from a crash without having to reload all of your
application program.
We begin by
initializing the board and setting up an appropriate memory map by typing:
COLD
DOWNLOAD.MAP
4 USE.PAGE
ANEW CRASH.DEMO
The COLD command initializes QED-Forth, DOWNLOAD.MAP sets pages 4-6 as RAM and pages 1-3 as
flash, and 4 USE.PAGE places the dictionary areas (where new definitions will be
compiled) on page 4. The ANEW command followed by a name of your choice
marks the point in the dictionary where we begin adding new code. It is good
policy to execute an ANEW command after setting the memory map pointers.
We now add a new
function to the dictionary by typing this definition at the terminal:
: TEST CR ."
Hello world..." ;
When executed, TEST prints a greeting to the terminal. We'll
use this definition to represent any custom code you might send to your
Handheld while developing your program.
Once the code has
been loaded into the Handheld, we execute the commands
4 PAGE.TO.FLASH
STANDARD.MAP
SAVE
by typing it at
your terminal. This moves the test routine into flash, changes pages 4-6 to
flash, and saves a set of relevant information describing the present state of
your dictionary. The information is stored in EEPROM (electrically erasable
PROM) inside the 68HC11 chip.
Now that our code
has been stored into flash, we can verify that our TEST word works by typing
TEST
at the terminal.
It should work. Now let's make a mistake by executing the following expression:
FORGET TEST
Oops! We just
lost the code we worked so hard on (Try to execute TEST and see what happens). Fortunately, we SAVEd the state of our machine after defining TEST. Executing
RESTORE
restores the prior
state of the dictionary, and TEST is available again (type TEST to see for yourself).
That was easy.
But what if a more serious bug occurs, for example one that erases the user
area where all of QED-Forth's key pointers are kept? To see what happens,
execute the following command:
HEX 8400 0 100
ERASE
To recover from
this crash you can simply reset the processor by pressing the reset switch. In
this case the processor realizes that the problem that caused the crash was
serious, and executes a COLD restart, so access to added definitions such
as TEST will
be lost. (Most resets cause a WARM restart that automatically preserves the
dictionary) Once again, you can recover the dictionary including the TEST definition by simply executing RESTORE.
The Special
Cleanup Mode
During software
debugging it is possible (though difficult) to crash your Handheld so that even
a reset will not re-establish communications. The following example demonstrates
this by diabolically revectoring the terminal input and output routines so that
no serial communications can occur. Type this command all on one line
at your terminal:
HEX 0\0 UKEY
X! 0\0 UEMIT X!
After executing
this expression, you will not be able to communicate with your system, even by
resetting it. The solution is to enter "Special Cleanup Mode" which resets all
critical operating system variables to their original factory-defined settings.
This is accomplished by installing Jumper J1 and then pressing the reset
button with the power ON.
Resetting or
re-powering your Handheld with Jumper J1 installed causes a COLD reset,
initializes all system variables and EEPROM locations to their factory settings,
and enters the QED-Forth interpreter. Once a special cleanup has been
performed, be sure to remove Jumper J1 to prevent performing a Special Cleanup
every time you turn on power. If you are using a graphics display you'll need
to re-establish the stored display configuration using the IS.DISPLAY routine. The Special Cleanup Mode also sets
the default baud rate of the primary serial port to 19200 baud; if you have
established a different baud rate as described in the next chapter, you'll need
to execute BAUD1.AT.STARTUP to re-establish your chosen baud rate.
Now that you can
communicate with your Handheld again, execute RESTORE to restore access to your dictionary which
contains the TEST routine.
« Previous | Next »
|