Your First C Program Using the Mosaic IDE Plus
Now that we’ve learned about the PDQ Board's hardware, established serial communications, and installed the Mosaic IDE Plus™ on the PC, it’s time to compile, download and execute a C program. We’ll also explore the PDQ Board’s on-board operating system (also called the “Kernel”) and use it to interactively debug a program.
Compiling a program
In this section we'll be running a C-language program on the PDQ single board computer (SBC). The simple program does some computation and communicates its results on the serial port. The program is one of several examples included with the Mosaic IDE Plus™. Let’s compile, download, and run it.
First we need to open the demo project. When we do this a copy of the original is made, and saved in the my_projects
folder (by default.) This way you can edit the demos as much as you want, and still keep the originals pristine. Click Project→ New Project, or click bubble #1 on the Quick Start page:
The "New from template" dialog will open.
Click "create" at the bottom of the window. The next dialogs allow you to choose a folder and project name. This document assumes the default values are chosen.
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 good practice to initialize all variables with a run-time procedure when the program starts.
Clicking Build→ Build will compile and produce a downloadable form of the program named getstart.dlf, where “dlf” stands for “download file”. The Messages pane at the bottom of the screen allows you to watch the compilation process. When compilation has finished, click
and look for warnings, which don’t prevent creating a valid download file, and errors, which do. You should see one warning:
getstart.c 245 warning: unused variable 'unused_variable'
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, the IDE 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:
You are using: 4 of 29 (decimal) Code Memory Pages. You are using: 0 of 16 (decimal) Resource Memory Pages. You are using: 3763 of 22527 (decimal) bytes of Common Memory (RAM). --> C:\MosaicPlus\my_projects\getstart\getstart.dlf <-- file has been created. Use Tools->Mosaic Terminal to send it to the target board. Process terminated with status 0 (0 minutes, 14 seconds) 0 errors, 1 warnings
The file named “getstart.dlf” is ready to be downloaded to the microcontroller using the Mosaic Terminal program.
Downloading and running the program
If it is not already open, launch Mosaic Terminal either from the ‘Start’ menu or by clicking Tools→ Mosaic Terminal from inside the Mosaic IDE Plus. 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 “C:\MosaicPlus\my_projects\getstart” 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.000000. 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 avaliable from the Project→ New Project menu. 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 Mosaic PDQ IDE environment.
Interactively debugging your program
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 PDQ Board’s operating system makes it easy to interactively execute any designated function in your program. By simply preceding a function’s name in its definition or prototype with the _Q keyword (we chose _Q as a unique keyword that suggests the QED operating system), 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 ) // print current radius and area { printf( "\r\nThe radius is %6u; the circular area is %5.4g.\r\n", radius, area ); }
The void keywords indicate that the Announce( ) function does not return a value, and does not expect any input parameters to be passed to it.
The _Q declarator instructs the compiler that we want to be able to interactively call this function using the on-board QED-Forth interpreter. The names and execution addresses of all functions defined with the _Q designator are placed in the .DLF download file so that QED-Forth will recognize them and will be able to interactively execute them.
The printf() function invoked in Announce( ) prints the specified string to the serial1 port. The parameters of the printf() function are well defined by the ANSI standard, and are described in many excellent texts. Briefly, the \n is an escape sequence that instructs printf to insert a newline at the designated places in the string. The % characters are formatting symbols that tell the compiler to substitute the listed arguments (in this case, the radius and area) for the % sequences at runtime. The %6u sequence tells the compiler to display the radius as an unsigned decimal number with a minimum field width of 6. The %5.4g sequence tells the compiler to display the area using either decimal or exponential notation with a precision of 4 decimal places to the right of the decimal point, and a minimum field width of 5.
For more information on C string formatting functions, see: http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html
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.
The Mosaic Terminal remembers the last several lines that you typed, so if you want to resend a line to the PDQ Board, you can repeatedly press the up-arrow key until you see the line you want to send. Hitting the Enter key sends the line to the PDQ Board.
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; // area of circle }
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 C:\MosaicPlus\c\libraries\include\mosaic directory.) PI
is a macro defined as:
#define PI 3.1415927f // a useful constant
The f
on the end of the floating point literal specifies that the literal is to be interpreted as a 32-bit IEEE single-precision floating point value, rather than a 64-bit double-precision value. Since the return value of the function is a 32-bit float
the extra precision is not necessary, and avoiding the double-precision value makes for smaller compiled code. Note that if you are using double-precision floating point math, the C library header file math.h
defines the double-precision value of Π as M_PI
.
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.
Restriction on the use of _Q
Nearly every function in the included sample programs is declared with the _Q keyword to facilitate easy debugging. There is, however, a restriction associated with the use of the _Q declarator. 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. While a properly defined function incorporating the ellipsis will run from the C environment, the interactive Forth debugging environment will not be able to execute it.
The _Q keyword cannot be used with main(). There would be no point, because main can always be executed by typing:
main ↓
An introduction to extended memory
The PDQ Board’s onboard operating system, called QED-Forth, provides numerous run-time services, including a heap memory manager. Using the memory manager we can access the controller’s extended memory.
1 Megabyte addressable memory space
The Freescale 9S12 (HCS12) processor on the PDQ Board can address 64 kilobytes of memory using 16-bit addressing, with up to 64 pages of size 16 Kbytes per page at addresses 0x8000-0xBFFF, resulting in 1 Mbyte of addressable memory. The processor’s PPAGE register, also called the PAGE_LATCH, selects the currently active memory page. The lower 32 Kbytes and upper 16 Kbytes of the HCS12’s address space (at 0x0000-7FFF and 0xC000-FFFF) is called the “common memory”. This common memory is always accessible, regardless of the page.
Available common RAM
The C compiler supports data (variable) accesses only in the standard 16-bit /64K native address space. Inter-page function calls and pointers are supported for the full 1 Mbyte address space. In other words, functions can be invoked on any page by specifying a 32-bit execution address including a 16-bit standard address plus a page, but all C variables, stacks and C arrays must be accessible using a simple 16-bit address. For practical purposes, this means that variables, stacks and C arrays must reside in the PDQ Board’s available 26 kilobytes of common RAM. The main 24K block of common RAM is located at 0x2000-7FFF, and an additional 2K block of common onchip RAM is at 0x0800-0FFF.
In multitasking applications, common RAM is also used for task areas; each task requires 1 Kbyte of common RAM area. It is highly recommended that task areas be allocated in the “onchip RAM” at 0x2000-3FFF or 0x0800-0FFF, as this optimizes the timing of stack assembly code operations. The C compiler preferentially places task areas into the onchip common RAM for you. (Forth programmers should place the TASK: or ALLOCATE.TASK: statements early in their program to achieve the same goal.)
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 amount of common RAM may not be sufficient. But don’t worry – you can use paged memory to store large amounts of data.
Built-in array library routines manage access to paged memory
The FORTH_ARRAY routines built into the operating system 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. The functions are described in detail in the C Glossary document.
Each array is referred to using a named 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 Flash-resident heap manager in the Kernel 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 PDQ Board’s Analog-To-Digital (ATD) converters. The header file named array.h in the C:\MosaicPlus\c\libraries\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, xaddr array_ptr ) { int r, c; DIM( float, rows, cols, ( FORTH_ARRAY* )( int )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, ( FORTH_ARRAY* )( int )array_ptr ); // store to array element }
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. Note that the last input is type xaddr
instead of FORTH_ARRAY*
. This is because the address is a 24 bit number. 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 C:\MosaicPlus\c\libraries\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; printf( "\r\n" ); for ( r = 0; r < NUMROWS( array_ptr ); r++ ) // for each row { for ( c = 0; c < NUMCOLUMNS( array_ptr ); c++ ) // for each column { // min field width=9;precision=4;f=decimal notation printf( "%9.4f ", FARRAYFETCH( float, r, c, array_ptr ) ); } printf( "\r\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 two 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.4f argument to printf() specifies that the number should be printed using decimal formatting, with 4 digits to the right of the decimal point and a minimum field width of 9 characters. The puts
("\r\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 C Function Glossary (A-H).
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.0f, CIRCLE_ROWS, CIRCLE_COLUMNS, ( xaddr )&circle_parameters );
where 0.0f 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 the next 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.
An introduction to multitasking
Many instrument design, automation and control application programs 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 PDQ Board’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 { //there are TICKS_PER_SECOND time slicer ticks every second by default //first we save what its current value is long prev_time = FetchTSCount(); DimAndInitFPArray( 0.0f, CIRCLE_ROWS, CIRCLE_COLUMNS, ( xaddr )&circle_parameters ); //start the timeslicer StartTimeslicer(); while( 1 ) // infinite loop { IncrementRadius(); // updates radius variable area = CalcArea( radius ); // updates area variable if( radius % 10 == 0 ) // on multiples of 10... SaveCircleParameters(); while( ( prev_time + TICKS_PER_SECOND ) > FetchTSCount() ) { //loop till the next second has arrived Pause(); // give other tasks a chance to run } prev_time = FetchTSCount(); //advance prev_time to current ticks } }
The first thing that this function does is to dimension and initialize the circle_parameters array. Then it enters an infinite loop that repeats exactly once every second. The loop 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 loop repetition is timed by using the Timeslicer. Note the call to StartTimeslicer(); without it the elapsed timeslice counts as read by FetchTSCount() would not change. After the work has been done, the inner while loop is responsible for keeping time. It calls Pause() repeatedly until the next second has arrived. 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 PDQ Board, 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 onchip com-mon RAM. The function SetupTask() builds and activates the new task; its definition is:
void SetupTask() { NEXT_TASK = TASKBASE; // important! empties task loop before building BUILD_C_TASK( CURRENT_HEAP, CURRENT_HEAP, &CalculationTask ); // uses default 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. Specifying CURRENT_HEAP,CURRENT_HEAP as the first two parameters tells BUILD_C_TASK to use the default heap located at pages 0x18 through 0x1C. (In a typical multitask-ing application, we advise that you declare a separate heap for each task to avoid contention.) 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 C:\MosaicPlus\c\libraries\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:
int main( void ) // Print starting values of area and radius, then build and activate // the CalculationTask. // Note: never declare main() with the _Q designator. // main() is always callable from Forth and C. // Make sure that there is only one main() function declared in your program. { int unused_variable; // another example of how warnings are handled! // Disable libc output buffering, which causes unexpected behavior on embedded systems. // If I/O buffering would benefit your application, see the Queued Serial demo. setbuf( stdout, NULL ); InitVars(); // runtime init of variables and pointers printf( "\nStarting condition:" ); Announce(); // print the starting values of radius and area variables SetupTask(); // build and activate the CalculationTask return( 3 ); // just a formality since main should be declared as an int }
The main() function is directly callable from the QED-Forth environment, so do not use the _Q declarator when defining main(). As you recall, the declaration of the unused_variable was inserted to demonstrate how the Mosaic IDE Plus™ 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 good style when programming embedded systems.
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.000000. 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. 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 call-able function names at any time. Remember to type
WARM↓
or
COLD↓
before trying to download another program file; the PDQ Board 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).
Using the shadow flash, write protection, and autostarting
The Mosaic IDE Plus™ does most of the work for you in configuring your C application program to run properly under the QED-Forth operating system. But knowledge of a few simple commands can increase your mastery of the environment. It is easy to invoke functions that automatically restore your program from shadow flash into the paged RAM at startup, to write protect your code so it can’t be corrupted by a processor crash, and to configure the PDQ Board to automatically run your top level program each time the controller is powered up or restarted. These functions are callable both from Forth and from your compiled C program. Because these system configurations are often performed interactively, it is useful to learn how to type commands at the QED-Forth terminal prompt so you can gain immediate control over the management of your program by the operating system.
Using the operating system: a primer on QED-Forth syntax
Unlike C, the Forth syntax is space delimited, and a function name can include any printable character. For example, many Forth function names incorporate the . (period), as in:
PRIORITY.AUTOSTART: main↓
which configures the operating system to automatically start the main function when the PDQ Board is powered up or restarted. The . and : characters in the PRIORITY.AUTOSTART: function name have no special meaning as they might in C; rather, they are just part of the function name.
Because QED-Forth is space delimited, each QED-Forth function name must be followed by at least one space, tab or carriage return. Forgetting to type the spaces after function names is a common mistake, and leads the operating system to respond with the ? error message, indicating that the function name was not recognized.
Typically, the numeric parameters passed to Forth functions precede the function name. Parentheses are not used; rather, the parameters are separated by spaces and are automatically pushed onto a data stack for use by the function. For example, to write-protect memory region 1 (which extends from pages 0x00 to 0x0F), we type at the terminal prompt:
1 WRITE.PROTECT↓
This passes the parameter 1 to the WRITE.PROTECT function, which is executed when the enter key is pressed. Note that there must be at least one space between the 1 and WRITE.PROTECT.
Unlike C, Forth is case insensitive. Forth function names may be typed in any combination of upper and lower case letters. Forth functions are often typed in all capital letters, but this is not mandatory.
With these simple principles in mind, we can use some simple commands that allow us to take advantage of the “shadow flash” that backs up the paged RAM, to write-protect or write-enable the paged RAM, and to configure the application program to automatically start when power is applied to the PDQ Board.
Using shadow flash to backup the paged RAM
As described in the previous chapter, the contents of the RAM on any or all of the pages 0x00 to 0x1D can be automatically loaded from an on-board shadow Flash chip each time the PDQ Board powers up or restarts. This effectively makes the paged memory nonvolatile because the stored contents are not lost when the board is powered down. The Mosaic IDE places commands to auto-mate this backup and restore functionality in the .dlf download file, but it is good to know how this works in case you want to optimize or modify the backup process.
After a compiled code image (such as an S-record created by the C compiler tool chain) is loaded into the paged RAM, it can be stored in the shadow flash using the STORE.PAGES command. This command expects two parameters: the starting RAM page, and the number of RAM pages to be stored into the shadow flash. The complementary LOAD.PAGES.AT.STARTUP command accepts a starting page, number of pages, and “area id” (1, 2, or 3) and configures the operating system to automatically restore the specified RAM pages from the shadowed flash when the board is powered up or restarted.
The Mosaic IDE Plus™ typically includes the following QED-Forth command after the S-record in the .dlf download file: SAVE.ALL
This convenient command is equivalent to:
0 0x18 STORE.PAGES 0 0x18 1 LOAD.PAGES.AT.STARTUP SAVE
The STORE.PAGES command tells the operating system to start at page 0 and store the contents of twenty four RAM pages (that is, pages 0x00 through 0x17) to shadow Flash. The LOAD.PAGES.AT.STARTUP command tells the operating system to restore the RAM contents of pages 0 through 0x17 (using area identifier 1) from shadow Flash upon each reset or restart. The third command saves the current memory map parameters in EEPROM such that a corresponding RESTORE or RESTORE.ALL command will restore the machine to the saved state. Executing RESTORE reverts to the SAVE'd memory map pointers, while executing RESTORE.ALL immediately reads the saved shadow Flash contents into RAM, and also restores the memory map pointers. The RESTORE.ALL command is equivalent to:
0 0x18 LOAD.PAGES RESTORE
Complete definitions of these functions can be found in the Interactive Debugger Glossary section of the PDQ C Glossary. Definitions and function prototypes of the corresponding C-callable functions can be found in the main section of the PDQ C Glossary.
You can also execute the shadow flash configuration commands interactively from your terminal. For example, if you want to additionally save and restore the contents of five pages of RAM at pages 0x18 through 0x1C, you could type:
0x18 5 STORE.PAGES↓ 0x18 5 2 LOAD.PAGES.AT.STARTUP↓
Note that we specified “2” as the area ID for LOAD.PAGES.AT.STARTUP to avoid reconfiguring the prior load behavior. As a result, the operating system will restore pages 0x00-17 and pages 0x18-1C at power up and restart. The operating system can manage up to 3 independent page regions (corresponding to area ID’s 1, 2, and 3) that are loaded from shadow Flash into RAM at startup.
To undo the effect of a LOAD.PAGES.AT.STARTUP command, simply execute the LOAD.PAGES.AT.STARTUP function with a valid starting page, setting the specified number of pages equal to zero, and passing in the area ID that you want to undo. For example, to nullify the prior command, you could execute:
0x18 0 2 LOAD.PAGES.AT.STARTUP
C-callable versions of many of the shadow Flash management functions are also available if you want to invoke them from within your C program. The function prototypes are documented in the C:\MosaicPlus\c\libraries\include\mosaic directory in the MEMORY.h and OPSYSTEM.h files:
extern int StorePages (int basePage, int numPages); extern void LoadPagesAtStartup (int start_page, int num_pages, int area_id);
Testing the Paged RAM Backup
If you’ve loaded the getstart.dlf download file into the PDQ Board and you want to test the Flash backup feature, power down your PDQ Board now, wait a number of seconds to ensure that the RAM contents are lost, then power it back up and type at your terminal:
RESTORE↓
Here’s what happened: The RAM contents were corrupted by the power down, but when the board powered up, the RAM contents were then restored by the LOAD.PAGES.AT.STARTUP command contained in the getstart.dlf file; this restore is effective whether or not the RAM has been write protected as described below. The RESTORE command that you typed completed the restoration by reinitializing the memory pointers to the _Q debugging headers, restoring them to the values they had when SAVE was executed during the getstart.dlf download. You should now be able to type main, Announce( ) and all the other functions described in the earlier sections, indicating that your code was successfully restored.
Write protecting the paged RAM
Keeping your compiled application code in Flash-shadowed paged RAM ensures that the contents are not lost when power is removed, but we also want the code to be safe from “errant writes” that can occur when a program crashes or invokes buggy routines. This is accomplished by “write protecting” the RAM so that it cannot be modified after it is loaded. “Write enabling” simply means undoing the protection so that the RAM can be written to. This section describes the operating system functions that write protect and write enable the paged RAM.
There are two write protection “regions”. Region 1 comprises the sixteen pages from 0x00 through 0x0F, and region 2 comprises the four pages from 0x10 through 0x13. Each region can be independently write protected or write enabled. The write protection functions are typically typed interactively at the terminal, although C-callable versions are available if you want to invoke them from your program. As usual for Forth commands, the input parameter is typed before the function name, and spaces separate all the entries on the command line.
For example, let’s assume that you have loaded the getstart.c program by using the terminal to send getstart.dlf to the PDQ Board, as described earlier in this Chapter. Your code resides in paged memory starting at RAM page 0x00 (in write protect region 1), and the debugging headers that enable you to interactively call the functions from the terminal are stored starting at RAM page 0x10 (in write protect region 2). To write protect the compiled code and the debugging headers, you could execute the following two commands:
1 WRITE.PROTECT 2 WRITE.PROTECT
or, equivalently, you could execute the single short-hand command:
WP.ALL
which write protects pages 0x00 through 0x13, inclusive.
Now, even if you try to overwrite the contents of pages 0x00-0x0F (region 1) or 0x10-0x13 (region 2), the original contents will remain until the write protection is removed. The LOAD.PAGES and LOAD.PAGES.AT.STARTUP functions mentioned in the prior section are smart enough to note the write protection status, write enable the RAM before restoring the RAM contents from shadow Flash, and restore the write protection status of the RAM when the processor is powered up or re-started. To undo the write protection, simply execute the commands:
1 WRITE.ENABLE 2 WRITE.ENABLE
or, equivalently, execute the single short-hand command:
WE.ALL
which write-enables pages 0x00 through 0x13, inclusive.
C-callable versions of the two fundamental write protection configuration commands are also declared in the C:\MosaicPlus\c\libraries\include\mosaic directory in the OPSYSTEM.h files, with function prototypes:
extern void WriteProtect (int ram_region_id); extern void WriteEnable (int ram_region_id);
The relevant functions are documented in the PDQ C glossary in the main glossary section and in the interactive debugger glossary section.
Autostarting your application
You can configure QED-Forth to automatically execute a specified application program after every reset, restart, or error-induced ABORT. This makes it easy to design a production instrument based on the PDQ Board; the instrument will automatically perform its required function when it is turned on or reset.
QED-Forth provides two functions named AUTOSTART: and PRIORITY.AUTOSTART: that allow you to specify a startup routine. Both write a pattern in memory that instructs QED-Forth to execute a user-specified program. AUTOSTART: stores the pattern at the top of page 0x37 in onchip Flash in-side the HCS12 processor chip, and PRIORITY.AUTOSTART: stores the pattern near the top of page 0x0F in both RAM and the shadow Flash memory. The choice of which version to use depends upon where you are locating your program code. The recommended area for compiled code is on pages 0x00-0x0F in write-protectable shadowed RAM; use of the PRIORITY.AUTOSTART: routine locates the autostart information in this same memory region. If you prefer to instruct the compiler to load code into onchip Flash, then the AUTOSTART: routine makes sense, as it locates the vector in onchip Flash.
Let’s assume that you want to want to run the top level main routine every time you turn on, reset, or restart the PDQ Board. The following command:
AUTOSTART: main↓
writes a pattern into onchip Flash at addresses 0xBFFA-BFFF on page 0x37 comprising a 16-bit flag (equal to 0x1357) followed by the 32-bit extended code field address (xcfa) of the specified startup program. All subsequent resets and restarts will call the specified application program after QED-Forth initializes the system.
To specify the startup vector so that it can eventually reside in write-protectable shadowed RAM, we would execute a different command:
PRIORITY.AUTOSTART: main↓
which writes a pattern starting at 0xBFFA on page 0x0F comprising a 16-bit flag (equal to 0x1357) followed by the 32-bit xcfa of the specified startup program. All subsequent resets and restarts will call the specified application program after QED-Forth initializes the system.
The priority autostart and autostart locations are checked each time QED-Forth executes ABORT, which is called after every reset, COLD or WARM restart, or error. ABORT first checks the priority autostart location at 0xBFFA\37, and if 0x1357 is stored there it executes the program whose xcfa is stored in the next four bytes. If the priority autostart pattern is not present, or if the specified priority startup program finishes executing and “returns”,ABORT then checks the autostart pattern at 0xBFFA\0F. If 0x1357 is stored there it executes the program whose 32-bit xcfa is stored in the next four bytes starting at 0xBFFC\0F.
To remove the autostart pattern or patterns, execute:
NO.AUTOSTART↓
This command clears all of the startup patterns in onchip flash, RAM and shadow flash.
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. You have learned how to automatically restore your program from shadow Flash into the paged RAM, write protect your code, and autostart the program. That’s pretty good considering that this is your first C program on the PDQ Board!
See also → Writing and Compiling Programs