Using the IDE
In this Chapter we’ll explore the QCard Controller’s tools for writing, editing, downloading and debugging your C and Forth language application programs. You’ll learn:
- How to efficiently use the editor and compiler to write and compile both short and long programs;
- Coding and file-naming conventions;
- How to access the QCard Controller’s onboard functions; and,
- How to interactively debug your programs.
Writing programs
Using the editor/compiler
In the prior Chapter we introduced the Mosaic IDE (Integrated Development Environment) and the tools you can use to edit, compile, and download your instrument control program on the QCard Controller Single Board Computer (SBC). The textpad toolbar provides buttons for compilation and debugging.
Sylistic conventions
Code comments
At the top of the GETSTART.C
source code file are some comments that tell what the program does. Single- or multi-line comments can be enclosed in the standard
/* */
delimiters. The double-slash
//
token means that the remainder of the line is a comment. Note that the editor colors all comments differently to make it easy to distinguish comments from source code. C keywords are also colored differently than user-defined routines. You can change the default colors if you like.
Style conventions
The example programs on your CD-ROM follow several stylistic conventions. Here is a brief summary:
Macros and constants are spelled with CAPITAL_LETTERS
.
Variable names are spelled with small_letters
.
Function names use both capital and small letters, with capital letters indicating the start of a new subword within the function name. For example:
void SaveCircleParameters(void)
To minimize the need to skip from one file to another, we have decided not to group all #define
statements in a header file that is separate from the program being compiled. Rather, the #define
statements are defined close to where they are used in the program file that is being compiled.
File naming conventions
For backward compatibility with DOS and Windows 3.1, all filenames have 8 or fewer characters. C source code files have the .C extension, and header files have the .H extension. When you use the Make Tool utility to compile a source code file with the filename,
NAME.C
several files with the extensions shown in Table 4-1 are created:
Table 4-1 Files Created by the C Compiler.
FILENAME.ext | Description |
---|---|
NAME.C | Source code text file created by you, the programmer |
NAME.A11 | Assembled output text file created by C11 compiler |
NAME.O11 | Object code binary file created by ASM11 assembler |
NAME.LCF | Linker command text file created by CC or CCM batch file |
NAME.S | S-record (raw download ascii file) created by linker |
NAME.DLF | Final download file created by CC or CCM batch file; includes S records and definitions for QED-Forth |
NAME.MAP | Map file listing created by linker |
NAME.MEM | Symbols map file |
NAME.USE | Memory usage summary |
NAME.BAK | Backup file sometimes created by the editor |
While this list may seem overwhelming, you won’t have to worry about most of these files. You’ll create your NAME.C
and .H
source code and header files in a directory of your choice, run the automated Make Tool by clicking on the Make icon, and send the resulting NAME.DLF
download file to the QCard Controller using the Terminal program. In fact, unless you tell it otherwise, the editor’s "File" menu will typically show you only files with the .C
and .H
extensions ("Source Files"); you won’t have to wade through the files with the other extensions. Similarly, the Mosaic Terminal typically lists only files with the .DLF
extension, so it will be easy to select the download file to send to the QCard Controller.
Using function prototypes
This stylistic convention deserves its own section. We strongly urge that you define or prototype each function before it is called. If the compiler generates a warning that a function has been called without a prototype, we recommend that you check your source code and insert the required function prototype, or move the definition of the function so that it is defined before it is called.
A prototype is a declaration that specifies the function name and the types of its return value and input parameters. For example, the following is a valid function prototype:
_Q float CalcArea( unsigned int radius);
This declaration specifies that CalcArea()
is a function that expects one unsigned integer input and returns a floating point value. As discussed below, the _Q
tags the function as one that is interactively callable during debugging. The CalcArea()
function can then be defined later in the source code file. If a function is prototyped in one file and defined in another, add the extern
specifer before the prototype.
You can preface any function prototype with the _Q
tag if you want to interactively call the function from the terminal. The QCard Controller’s onboard operating system maintains a list, called the dictionary, of the names of functions tagged with the _Q
so that it can recognize them when you send a command line from your terminal.
Defining or prototyping a function before it is called allows the compiler to help find parameter passing errors, and it also prevents unnecessary promotion of parameters that can render the code slower and defeat the QCard Controller’s interactive function-calling capability. To avoid unwanted promotion and runtime errors, each and every parameter in the function prototype or function definition must be preceded with a type specifier. For example, leaving out the unsigned int
keywords in the prototype for CalcArea()
above would lead to promotion of the input parameter, possibly resulting in a runtime error message from the compiler or linker.
Using function prototypes and definitions that explicitly specify the type of each and every input and output parameter results in more readable and reliable code.
Accessing the standard (kernel) library functions
The command
#include < mosaic\allqed.h >
near the top of the GETSTART.C
file is a preprocessor directive that includes all of the relevant header files for the QCard Controller, and all of the standard C header files (such as stdio.h
, math.h
, float.h
, string.h
, etc.) We strongly recommend that this statement be placed at the top of each C program file that you write. It gives you access to all of the pre-coded library routines that reside on the QCard Controller. These routines let you control the A/D converters, digital I/O, serial ports, real-time clock, and many other useful functions. The ALLQED.H
file also gives you access to the QCard Controller’s multitasking and paged memory capabilities, as well as the standard ANSI C library functions including printing and string conversion routines such as printf()
and sprintf()
. Including these files is very efficient; it generates almost no additional runtime code while giving you access to very powerful capabilities.
You can call any of these functions from within your C code. There is one limitation however:
_forth
.Many functions that are callable from C are actually of the _forth
type. This includes functions that are in the kernel on the QCard Controller, or are part of software distributions such as the Graphical User Interface (GUI) Toolkit. A call to one of these _forth
functions may not be made from within the parameter list of a call to another _forth
function.
There is always a straightforward way of avoiding such nesting of function calls: simply use a variable to hold the required intermediate return value/parameter. For example, if you need to use the _forth
function FetchChar()
to fetch the first character from the extended address returned by the _forth
function DisplayBuffer()
in paged memory, you could execute the following statements:
static xaddr buffer_xaddress = DisplayBuffer( ); FetchChar( buffer_xaddress);
This code is correct, while nesting the call to DisplayBuffer()
inside the parameter list of FetchChar()
would be incorrect.
Initializing variables
RAM-Resident Variables & Arrays Must Be Initialized Within Functions
A common mistake made when creating application programs for embedded systems is the use of compile-time initialization for RAM-based quantities such as variables and arrays. While this approach of initializing quantities outside of function definitions may work during program development, it fails when the device goes into production because the variables and arrays are not properly initialized when power is cycled.
Only run-time initialization, i.e., initializations that are performed within functions (which are in turn called by the autostart program), will occur reliably in an embedded application.
Even users with battery-backed RAM in their systems should always perform initializations within functions. This approach will avoid hard-to-diagnose field failures that result from corrupted data in a battery-backed RAM that is never re-initialized to valid values.
Feel free to call Mosaic Industries for help with this or other programming issues.
Compiling Multiple Source Code Files
When writing large programs it is often useful to break up the program into multiple source code files. In addition, if your program grows large enough, it may not fit on a single page of flash memory. The Make Tool allows you to divide your source code into separate files that will each be loaded onto a separate page of flash memory, and to make functions in each source code file available from within all the others.
One of your files must be the primary source code file, and it should contain the main()
function; for example, the primary file may be called code.c
. Other source code files must be named using a suffix added to the name of the primary source code file, and all must have the file extension .c
. For example, your three source code files could be named:
- code.c This is the primary file that you compile.
- code1.c CODE1.C and CODE2.C are subsidiary files that are automatically compiled and linked when CODE.C is compiled.
- code2.c
Because filenames are translated internally to an 8-character representation plus file extension, it is a best practice for the base filename (the part before the file extension) to be no longer than 8 characters. Note also that the Make Tool cannot handle filenames that start with a numeral; thus a source code file named 1CODE.C
cannot be compiled.
Finally, a header file must be created, and it must contain function prototypes declared using the extern
keyword for each function that needs to be called in a source code file other than the one in which it is defined. It must also contain extern
variable declarations for any global variables defined in one source code file that must be accessible from within other source code files. For instance, the header file could be called code.h
. Each source code file must then have the following statement near the beginning:
#include "code.h"
And, code.h
would look like this:
#ifndef CODE_H #define CODE_H extern int hamsterCount; extern RESOURCE pf; extern double hamsterVelocity( double age, double time ); extern _Q int Convert12( int channel ); #endif // #ifndef CODE_H
To compile multiple source code files, open the primary file in Mosaic IDE. If it is already open among other files, select its tab to bring up the primary source code file. Then under the Tools menu, select Multi-Page Make. This will compile each source code file to a separate page of flash memory and properly link all function calls.
Finally, note that one and only one of the program files must include a function named main()
.
Using the interactive debugger
In the prior chapter, you gained experience using the debugging environment that lets you interactively execute any designated function with input arguments of your own choosing. Now we’ll look more closely at the operation of the debugging environment, and explain how to use it to examine and manipulate the values of static variables, Forth Array elements, and memory locations.
The interactive debugging environment conveys several advantages. First, you can test each function of a program individually without changing the main()
function and recompiling. This saves compilation and download time. Second, the environment makes it easy to test each function with a wide range of input parameters, allowing you to isolate bugs that might otherwise be missed until later in the program development cycle. Such thorough function-by-function testing of a program facilitates more rapid development of reliable programs.
We’ll start by learning how to use the interactive environment to examine the values of static variables. The explanation of how this works involves taking a brief high-level look at the interactive QED-Forth language that is built into the QCard Controller. Understanding how QED-Forth operates will empower you to take full advantage of the debugging capabilities of the QCard Controller.
Overview of the Forth language and programming environment
The QED-Forth interactive environment makes it easy to examine the contents of static variables. A brief overview of how the Forth language works will help clarify the procedure.
QED-Forth Numeric Printing Functions
Displaying the values of static variables
Now that we understand how the Forth data stack works, the procedure for examining variables will make sense. The examples presented here use code from the GETSTART.C
program that we’ve already discussed in detail. If you have already downloaded the program, you are ready to go.
Extracting the Value Referenced by a Pointer
Signed versus Unsigned Numbers
Summary
In summary, to display the contents of a simple static variable, type a command of the form:
type variable_name print_function_name
where type is one of the following keywords:
char int long float
To display the contents of a static variable that is pointed to by a pointer, type a command of the form:
type pointer_name print_function_name
where type is one of the following keywords:
char* int* long* float*
Use type keywords to interactively call C functions
The same family of familiar C type-declaration keywords that we used to fetch the contents of variables is also used to facilitate interactive calling of C functions. Recall that these keywords are:
char int long float char* int* long* float*
We have seen that these keywords are used in two different contexts while debugging. In the prior chapter we used them to declare the type of an input parameter while interactively calling a function. For example, we can interactively type from the terminal:
CalcArea( int 5)↓
where the int
keyword is used with the same syntax as an ANSI-C function prototype to declare the input arguments to the called function.
Second, we used the type keywords in this chapter to extract the value from a variable, as in the interactive QED-Forth command
int radius U.↓
which prints the contents of the radius variable as an unsigned integer.
These two contexts for the use of the int
keyword are related. For example, to calculate the area corresponding to the current value of the radius variable, we can interactively execute:
CalcArea( int radius)↓
and QED-Forth prints the resulting floating point area in its summary of the return value. The int
keyword serves two complementary purposes here: it tells QED-Forth that the input parameter is a 16-bit integer, and it extracts the value of the radius variable so the variable is passed by value.
When interactively calling a function, all parameters that are passed by value should be preceded by the appropriate type keyword. However, when passing the address of a variable or a structure, simply state the variable or structure name without any type specifiers or &
(address-of) operators.
For example, the function prototype for the DimAndInitFPArray()
function in GETSTART.C
is:
Q void DimAndInitFPArray(float val,int rows,int cols,FORTH_ARRAY* array_ptr)
and the program includes the array declaration:
FORTH_ARRAY circle_parameters;
which declares circle_parameters
as a FORTH_ARRAY
structure in memory. As we shall see, executing (typing) the name circle_parameters
in QED-Forth leaves the address of the array structure on the stack, so there is no need for additional type declarators or &
operators. Thus to interactively dimension the array to have 10 rows, 2 columns and a initialization value of 12.34, we type from the terminal:
DimAndInitFPArray( float 12.34,int 10,int 2,circle_parameters)↓
To verify that this worked, you can execute:
PrintFPArray( circle_parameters)↓
which displays the contents of the newly initialized circle_parameters
matrix.
Displaying the values of FORTH_ARRAY elements
The same type specifier keywords that let you examine static variables can also be used to examine any specified element in a two-dimensional FORTH_ARRAY
. The syntax is parallel to what we have already used; the difference is that we now append the row and column indices in square brackets after the array name to specify which element should be fetched.
For example, recall that circle_parameters
is a FORTH_ARRAY
that is dimensioned to hold 10 rows and 2 columns of floating point data. To print the contents of the first element in the array at [row=0, col=0], we type:
float circle_parameters[ 0, 0] PrintFP↓
and QED-Forth prints the result. While this array notation is not exactly like the standard C syntax, it is straightforward. To print the element whose row index is 5 and whose column index is 1, type:
float circle_parameters[ 5, 1] PrintFP↓
As you might expect, there must not be a space before the [
character, and there must be at least one space after the [
character. This is because
circle_parameters[
is defined as a space-delimited QED-Forth function in the GETSTART.DLF
file, as explained later in this chapter.
All of the keywords that we learned about above can be used to fetch the contents of appropriately dimensioned arrays. Arrays that are dimensioned to hold character, integer, long, or float data are accessed using the char
, int
, long
and float
keywords, respectively, in front of the array name. If for some reason you use a FORTH_ARRAY
to hold 16-bit pointers , the char*
, int*
, long*
and float*
keywords can be used in a manner exactly analogous to the description in the earlier section of this chapter.
Assigning values to static variables and FORTH_ARRAY elements
You can interactively change the contents of any static variable or FORTH_ARRAY element using the following assignment keywords:
=char =int =long =float
Each of these keywords expects to be preceded by the address of a variable or FORTH_ARRAY
element, and expects to be followed by a valid number, variable name, or FORTH_ARRAY
element specifier. As expected, the value of the right hand side is assigned to the variable or array element on the left hand side of the assignment expression.
For example, to change the current value of radius to 22, simply type:
radius =int 22↓
This syntax was designed to be similar to a C statement that assigns the value 22 to the radius variable. As you might guess, =int
is a single keyword defined in QED-Forth, so there cannot be any spaces between =
and int
. Similarly, the other tokens in the expression must be separated by spaces; thus there is at least one space after radius
and at least one space before 22
.
To set the current value of the floating point area variable to 1520. type:
area =float 1520.↓
To assign the current value of the area variable to element [ 0, 1] in the FORTH_ARRAY circle_parameters
, you can execute:
circle_parameters[ 0,1] =float area↓
To check that these operations actually worked, we can execute the following commands to examine the contents of the affected variables and array elements:
int radius U.↓ float area PrintFP↓ float circle_parameters[ 0,1] PrintFP↓
Under the hood of the QED-Forth interactive debugger
This section is for the curious among you; you need not read or understand this section to use the QED-Forth interactive debugger. However, it will give you additional insight into the debugging environment.
Summary
The Make Tool calls the QCC.EXE
executable program to create the QED-Forth debugging declarations that appear at the bottom of the .DLF
download file. This program has to decide whether each compiler symbol in the .OUT
file is a callable function, a variable, or a FORTH_ARRAY
. The Make Tool identifies callable functions by detecting the _pascal?
tag that the compiler places there in response to the _Q
specifier, and in response prints the functionname(
definition into the .DLF
file. The Make Tool identifies variables by detecting whether the corresponding address lies in the common RAM area, and in response prints a QED-Forth CONSTANT
declaration into the .DLF
file. Finally, it tentatively identifies FORTH_ARRAY
s by checking the size of each variable; if there are exactly 18 bytes allocated to one item in the common RAM, it decides that the associated name should also be declared as a FORTH_ARRAY
by printing the name[
definition in the .DLF
download file. To be safe, the Make Tool always declares the last variable as a FORTH_ARRAY
because it cannot be sure of its allocated size.
Other useful QED-Forth functions
QED-Forth is a complete language that includes over a thousand pre-defined functions, all of which reside in ROM on the QCard Controller. Many of these functions are declared in the header files in the \MOSAIC\FABIUS\INCLUDE\MOSAIC
directory, and so are callable from C. The names and descriptions of these functions are detailed in the Control C Glossary in the documentation package. But there are also additional routines described there that are useful while debugging; these allow you to:
- Modify the contents of EEPROM on the 68HC11 processor.
- Dump the contents of a specified region of memory in hex and ascii format using the
DUMP
command. - Specify a new baud rate for the serial port to speed downloads using the
BAUD1.AT.STARTUP
command. - Configure the QCard Controller to execute a specified program each time a reset or restart occurs using the
AUTOSTART
orPRIORITY.AUTOSTART
command. - Dump out a replica of the board’s program memory space in Intel Hex or Motorola S2 record format to archive your production code.
In sum, the versatile QED-Forth language enhances the power of Control C by providing many operating system functions as well as an interactive debugging environment that speeds program development and testing.
The QCard kernel versus prior kernels
The QCard and QScreen products use a QED-Forth operating system kernel denoted as V4.4x, where the ‘x’ may take on any numeric value. There are several minor differences between V4.4x and the V4.0x kernel used on the QED Board, Panel-Touch Controller, and QVGA Controller products. Briefly, five functions have been added to the V4.4x kernel, and 19 device functions have been removed. The removed functions are device drivers associated with hardware that is not implemented on the QCard/QScreen products. In addition, the V4.4x kernel boots up at a default serial baud rate of 19,200 baud, compared to 9600 baud on prior kernels.
Table 4-2 lists the new functions, and Table 4 3 lists the removed functions. Descriptions of the new functions are provided in the glossary below and in the function reference document that accompanies the QCard and QScreen products.
Table 4-2 Functions added to V4.4x kernel
C Name | Forth Name |
---|---|
Buffer_To_SPI( ) | BUFFER>SPI |
Bytes_To_Display( ) | BYTES>DISPLAY |
Calc_Checksum( ) | CALC.CHECKSUM |
Clear_Boot_Vector( ) | CLEAR.BOOT.VECTOR |
Set_Boot_Vector( ) | SET.BOOT.VECTOR |
C programmers must include the files named v4_4update.c and V4_4update.h to gain access to the five new functions. These files are located in the
\Mosaic\Fabius\Include\Mosaic\v4_4Update
directory in the software distribution CD. Simply #include both the v4_4update.h and v4_4update.c files in one of your source files, and also #include v4_4update.h in any other source files that use these new kernel routines.
Table 4-3 Summary of functions deleted from V4.4x.
C Name | Forth Name |
---|---|
InitPIA( ) | INIT.PIA |
PIAStore( ) | PIA.C! |
PIAFetch( ) | PIA.C@ |
PIAChangeBits( ) | PIA.CHANGE.BITS |
PIAClearBits( ) | PIA.CLEAR.BITS |
PIASetBits( ) | PIA.SET.BITS |
PIAToggleBits( ) | PIA.TOGGLE.BITS |
ClearHighCurrent( ) | CLEAR.HIGH.CURRENT |
SetHighCurrent( ) | SET.HIGH.CURRENT |
PPA_ADDRESS | PPA |
PPB_ADDRESS | PPB |
PPC_ADDRESS | PPC |
FastSetDAC( ) | (>DAC) |
FastAD12Multiple( ) | (A/D12.MULTIPLE) |
FastAD12Sample( ) | (A/D12.SAMPLE) |
SetDAC( ) | >DAC |
AD12Multiple( ) | A/D12.MULTIPLE |
AD12Sample( ) | A/D12.SAMPLE |
InitAD12andDAC( ) | INIT.A/D12&DAC |
Summary of modified memory map functions
The Kernel’s internal memory map functions have been modified to be aware of the QCard and QScreen’s memory. In the "standard map", the QCard has flash at pages 4-7 that swaps with RAM on parallel pages 1-3, plus flash at hex pages 10-17 that swaps with RAM on parallel hex pages 18-1F. In the "download map", flash and RAM are swapped: flash is present on pages 1-3 and 18-1F, and RAM is present on pages 4-6 and 10-17. The C development environment transparently handles the loading of program code into flash, so C programmers typically do not have to be concerned with these issues.