|
Chapter 2
<< Previous | Next>>
Using the Mosaic IDE
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 QScreen Controller. 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 QScreen Controller’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 QScreen Controller,
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 QScreen Controller. 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 four 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 Make icon performs a standard build of
your program, and the Multi-Page Make 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 ‘Compile Tool’ Finds Syntax Errors in C
Programs
The Compile Tool, designated by the “Debug” icon,
invokes the Control-C compiler – Forth programmers do not need to use it
because Forth programs are compiled as they are downloaded onto the QCard
Controller. The Compile Tool, designated by
the “Debug” 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 ‘Make Tool’ Compiles and Makes a Downloadable
Single-Page C Program
The Make Tool, designated by the “Make” 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 Make Tool, designated by the “Make” 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 (Multi-Page
Make icon):
WARNING: Input section
“.doubleword” from ‘progname.o11’ is not used !
The ‘Multi-Page Make Tool’ Compiles and Makes a
Downloadable Long C Program
Forth programmers will also not need the
Multi-Page Make Tool, designated by the “Multi-Page Make” icon, which assists
in compiling long C programs. The Multi-Page
Make Tool, designated by the “Multi-Page Make” 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 QScreen Controller.
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 “Multi-Page” and “Single 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
QScreen Controller'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 QScreen Controller’s on-board operating system and use
it to interactively debug a program.
Compiling 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 Make 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 QScreen Controller’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 Make Tool 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 QScreen Controller’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.
2 Megabyte Addressable
Memory Space
The standard 68HC11 processor
can address 64 kilobytes of memory using 16-bit addressing. The QScreen
Controller expands the address space to 2 Megabyte, addressing the lower 32
Kbytes of the processor’s memory space by means of a 6-bit “Page Latch” that
selects one of 32 pages. The 64 pages times 32 Kbytes per page yields 2
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 QScreen Controller’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 QScreen Controller
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 QScreen Controller’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 QScreen Controller’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
QScreen Controller, 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 QScreen Controller 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).
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, and define a multitasking application
with an interactive terminal interface. That’s pretty good considering that
this is your first C program on the QScreen Controller!
An
Introduction to Programming in QED-Forth
The QCard
Controller 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 QCard Controller’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 QCard Controller 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 QCard Controller. Thus you can rapidly edit your source
code into files which are downloaded to the QCard Controller. The Mosaic IDE
allows you to switch between the editor and terminal windows quickly and
easily.
How To Send
Your Programs to the QCard Controller
To
send a program to the QCard Controller, 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 QCard Controller. If you
discover errors while debugging the program, simply edit the file and
re-transmit all or part of it to the QCard Controller. 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 QCard when the
new version is transmitted.
You
have complete flexibility in programming the QCard Controller. 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 QCard Controller. 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 QCard. 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 QCard 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 QCard 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 QCard
while developing your program.
Once the code has
been loaded into the QCard, 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 QCard 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 QCard 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 QCard again, execute RESTORE to restore access to your dictionary which
contains the TEST routine.
<< Previous | Next>>
|