3: Coding Your Application
Software development in C uses the Codeblocks-based Mosaic IDE Plus Integrated Development Environment. This chapter explains how to properly write C code for the Mosaic IDE Plus™. This section describes include files, accessing Wildcard IO modules, using pointers and 32-bit absolute addresses in the 9S12 (HCS12) processor's memory space, calling Forth routines, using lookup tables, and locating objects in non-volatile EEPROM memory.
The basics
Every project compiled in the Mosaic IDE Plus should have the following lines at the top of every .C file:
#include <mosaic\allqed.h> // this include statement should appear at the top of each source code file.
This file provides access to the operating system functions on the controller board, including device drivers for all of the "native I/O" on the PDQ line of controllers. These drivers control the Timer/Counter, Pulse-Width Modulation (PWM), Analog to Digital (ATD), serial channels, and multitasker available on the HCS12 processor. These functions are documented in the C Function Glossary (A-H), and prototypes are located in the C:\MosaicPlus\c\libraries\include\mosaic
directory.
Initializing memory
There are two ways to initialize variables in the C language; You can set the initial value of a variable when you declare it, or you can have line of code which sets the value at runtime. When programming for the PDQ Board you should not use the first method. This is an example of the wrong way to initialize memory:
int wrong = 1;
The correct way to do this is is:
int correct; correct = 1;
The difference here is that the second example compiles instructions which which will execute on the CPU of the PDQ board at run time. You can also group all of your initializations into a single routine, and call it from main() like this:
int correct; int temperature; void InitializeVariables( void ) { correct = 1; temperature = 98; } int main( void ) { InitializeVariables(); ... return 0; }
Wildcards
Most applications include hardware beyond the "native I/O" present on the HCS12 processor. You may have one or more Wildcards installed on your controller, or you may be using a touchscreen equipped product which is powered by the GUI Toolkit. Pre-coded add-on drivers are provided for all of the Wildcards and GUI products from Mosaic.
Mosaic controllers use a unique mechanism to provide add-on drivers. Traditional C uses classic C libraries. The Mosaic system generates a standard header file plus a set of directives and S-records that place the executable code into flash memory on the controller when you download your program with a new library added.
Using a C library
Using a library in C is very simple. All you have to do is #include the header file for the driver in your source file, and all the rest will be handled for you! All you need to do is put this at the top of your C code:
#include "library.h"
Then you can call any of the functions the library provides with no hassle. Mosaic's Codeblocks-based IDE Plus Integrated Development Environment takes care of linking the object code for you. When you compile your code using the IDE's GNC C Compiler (GCC), the driver file will automatically be included in the generated .DLF file. You can see a list of available c libraries by looking in the C:\MosaicPlus\c\libraries\include
folder.
Using a Forth library
To use a Forth library, simply including the following line in your source code file after initializing the memory map using DEFAULT.MAP:
#include "C:\MosaicPlus\forth\libraries\firmware\library.fin"
Remember that instead of "library.fin" you will want to include the real name of the driver listed below, such as "waim.fin" for the Analog I/O Wildcard.
Wildcard drivers
The following is a list of available libraries that act as a driver for specific Wildcards. Only the base name is listed here. To include a driver from a C source code file, simply #include
the name below with ".h" appended to the end, and the IDE Plus will compile and link the required driver files.
Base Name | Wildcard |
---|---|
waim | Analog I/O Wildcard™ |
wcfm | Compact Flash Wildcard™ |
wda247 | 24/7 Data Acquisition Wildcard™ |
wgps | GPS Wildcard™ |
wkpd | Keypad Display Wildcard™ |
wtcm | Thermocouple Wildcard™ |
wuam | UART Wildcard™ |
wusb | USB Wildcard™ |
wwifi | EtherSmart Wildcard™ and WiFi Wildcard™ |
Forth-callable routines ( _Q )
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 QED-Forth operating system also makes it easy to interactively execute any designated function in your program. By simply preceding a function definition or prototype with the _Q keyword, you can ensure that the function will be interactively callable from your terminal.
For example, the following function calculates the Pythagorean theorem:
float _Q pythag( int a, int b ) { int sum = a*a + b*b; printf("\nBefore the Square root the Sum is %d, the Answer is %g\n", sum, sqrt( sum ) ); return sqrt( sum ); }
Simply add this _Q function inside a working program (Hello World works as a good template.) Compile your code and load it onto your controller. Then type:
DECIMAL
to put the board in decimal base. If the board is in the default HEX
mode, the results will not make sense. Now simply type the name of the function and parameters like this:
pythag( 2, 4 )
The board will respond by running the function, and displaying the return value
Before the Square root the Sum is 20, the Answer is 4.47213 Rtn: 0x408F 0x1BB8 = 0x408F1BB8 =fp: 4.4721 OK
Note that the last line printed displays both the hexadecimal and floating point values returned from the function. After the =fp:
characters we see 4.4721
, our correct answer.
Spaces are important to the QED-Forth interpreter which processes this command; make sure that there is no space between the function name pythag
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.
See Also → Your First C Program Using Mosaic IDE Plus
Debugging Programs
Using lookup tables
There is much more paged memory than common RAM available on Mosaic controllers. In large applications, it may be convenient to locate a lookup table, which does not change at runtime, in paged memory along with the code. This conserves common RAM and makes efficient use of the available memory on the board.
Open the sample project to follow along with this section:
With the code open, scan through until you find the lookup table:
const _rom float sinLUT[TABLE_LENGTH]
The const
keyword tells C that nothing is allowed to modify this array. The _rom
keyword is the most important; it puts this array in non-volatile flash memory. Build the code and send it to your Mosaic controller using the Terminal, and then type in the Terminal window:
main
to run the program. The entire sine table will print out, as well as two memory addresses. The address known to C is displayed, as well as the full address of the lookup table. Notice that the full address displays the page, while C does not understand this extra digit. If you wish, you can remove the _rom
keyword, rebuild, and again send and run the program. You will notice that the address changes from 0x8*** to 0x2***. Compare these values against the Memory Map and you will see that address 0x8000 and above is in paged memory.
_rom
array is only guaranteed to work within the same C file. To get around this problem, simply use the safeGetLUT() function that is provided in the Sine Lookup Table source code file.
You can also type:
printtable( )
from the console to generate the C source code for this lookup table. Notice that it takes longer to print out this data. This is because each entry is calling sin()
directly. Calling main is much quicker, demonstrating the utility of using this lookup table to boost runtime execution speed.
Memory allocation for C strings
When a string literal is declared it is generally stored in common memory – the area of RAM that is accessible regardless of the current memory page selected. Even if a string literal is not assigned to a declared character array, it still is stored as a constant in common memory. Consequently, if an application needs very large string literals or a large number of string literals, for example if it involves a lot of user interaction and many separate calls to printf
, then the necessary string literals may end up occupying a significant portion of the common memory space.
A solution to this problem is to store strings in flash ROM, and only copy a string to RAM when it is needed. The Forth operating system provides a scratchpad area of common memory called pad for each application task that is intended for temporary data manipulation such as this. A set of macros described in this section makes it simple to declare a string in flash ROM, copy it to pad, and reference it, thereby avoiding additional consumption of common memory space for each string declared.
SAVE_STRING | LOAD_STRING | LOADED_STRING | LOAD_STRING_TO |
Declaring and storing a string literal
The first step is to declare the string literal, generally at the top of your main application source code outside of any functions, using SAVE_STRING to store it in flash ROM:
1: #include <mosaic\allqed.h> 2: 3: SAVE_STRING( greeting, "Greetings, current date is 20%02d-%02d-%02d.\n\n" );
Loading a string literal into memory
At the point in your code where a string literal is to be used, it must first be loaded into the Forth task's pad using LOAD_STRING. Note that the size of the Forth task's pad places a limit on the size of string literals that may be used, and a string larger than pad will be truncated when loaded into pad. At this time, only medium and large tasks have a pad available, so this method may not be used within small tasks. Currently the size of pad in medium and large tasks is 128 bytes/characters, including the Null character at the end of the string.
After a string is loaded into pad, it is referenced using LOADED_STRING. No other operations should be performed between loading and referencing the string, particularly no Forth functions should be called, as any function may make use of pad and thus overwrite the previously-loaded string.
5: int main() 6: { 7: int year, month, day; 8: 9: ReadWatch(); 10: year = WATCH_YEAR; 11: month = WATCH_MONTH; 12: day = WATCH_DATE; 13: 14: LOAD_STRING( greeting ); 15: printf( LOADED_STRING, year, month, day ); 16: 17: return 0; 18: }
Loading a string literal to a location other than pad
This method should only be used if there is a reason that the Forth task pad is unavailable at the time a string literal needs to be loaded. Note that allocating a character array of a certain size as a local variable will cause a stack overflow and memory corruption.
LOAD_STRING_TO allows you to specify where in common memory to place the string, along with the size of the destination memory area. The address given must be in common memory, and the size must not define a range that goes beyond common memory.
5: int main() 6: { 7: int year, month, day; 8: char local_buffer[ sizeof(greeting) ]; 9: 10: ReadWatch(); 11: year = WATCH_YEAR; 12: month = WATCH_MONTH; 13: day = WATCH_DATE; 14: 15: LOAD_STRING_TO( greeting, local_buffer, sizeof(greeting) ); 16: printf( local_buffer, year, month, day ); 17: 18: return 0; 19: }
Using 16-bit and 32-bit pointers
The GNU C (GCC) compiler targeted to the 9S12 (HCS12) microcontroller typically treats pointers as 16 bit values. Extra steps have to be taken in order to get a 32-bit extended address of a symbol, instead of just getting its regular C 16-bit pointer. Mosaic drivers and kernel (operating system) functions understand 32-bit pointers, and supplying a regular C pointer using the address of (&) operator would be insufficient if the data or function being pointed to were not in common memory (i.e., not a variable in RAM or EEPROM). You can obtain the 32-bit extended address of any symbol, regardless of type or whether it is in common or paged memory, by using the MAKE_XADDR() macro. Here is a code snippet from the sine lookup table example above:
const _rom float sinLUT[TABLE_LENGTH] = { ... }; xaddr sinLUT_xaddr; float _Q safeGetLUT( int degrees ) { float value; // MAKE_XADDR() saves the 32bit address of sinLUT into sinLUT_xaddr // which we can use to access our _rom lookup table regardless of // which page we are on. MAKE_XADDR( sinLUT ); // We pass the full 32bit address of sinLUT[] to FetchFloat // We use simple arithmetic to offset from the base of the array. value = FetchFloat( sinLUT_xaddr + degrees * sizeof(float) ); // Print our results printf("\nsin(%3d) = %f\n", degrees, value); // Also return our results return value; }
When MAKE_XADDR( sinLUT )
is called, the full address of sinLUT
is saved into sinLUT_xaddr
. In other words, the 32-bit address is stored into a symbol whose name has the suffix _xaddr
added to the name passed to the MAKE_XADDR() macro. Before calling MAKE_XADDR() the symbol sinLUT_xaddr
must be declared in the source code. After invoking MAKE_XADDR()
the full address is known; this address is passed to the operating system function FetchFloat(). Examine this output from main()
, generated by the "Sine Lookup Table", for example,
The C address is 0x 80e5 The full address is 0x 580e5
C is not "page smart", but thanks to MAKE_XADDR()
the program knows exactly which page the data is on (page 5), enabling the call to FetchFloat()
to access the data in paged memory.
Locating items in EEPROM
Non-volatile EEPROM (Electrically Eraseable Programmable Read Only Memory) provides a convenient place to store calibration constants and other values that rarely change. EEPROM is in common memory. Thus, variables placed in EEPROM are persistent and un-writable by usual means, and their addresses are directly available to code on all pages of memory, just like regular variables. When you initialize EEPROM variables in a declaration, they will be properly initialized at download time. These variables may be read like any other, but attempts to change them with a standard C assignment statement will fail. To write to EEPROM at runtime, use the functions StoreEEChar
, StoreEEInt
, StoreEELong
, StoreEELong
, and ToEEPROM
as described in the C Function Glossary (A-H). The following declarations show how to create a single byte or integer "variable" located in EEPROM:
char _eeprom myeepromvar; int _eeprom myeepromvar;
The _eeprom
tag may actually be placed anywhere on the line.
Once an EEPROM variable is declared, you must initialize it at runtime within a routine called by your main
.
To initialize or set this value, simply use StoreEEChar or StoreEEInt like this:
StoreEEInt( 1000, &myeepromvar );
Reading from this EEPROM variable is the same as reading from a normal (RAM-resident) int variable.
Using function pointers
Sometimes, it is useful to refer to functions anonymously by loading a pointer to the function into a variable. When the calling code might not know what function it is calling by name, using interchangeable function pointers is a useful strategy. Mosaic controllers rely on function pointers in a variety of ways including invocation of function call-backs from GUI Toolkit button press events, and activating a function as a task’s infinite-loop action routine. It is generally safe to use the standard native C syntax for function pointers to be used by C. Here is a simple example of this technique:
float (*myfunc_ptr)(int, char); // create a standard function pointer … … { … … myfunc_ptr=&SomeFunction; // set the pointer to point to SomeFunction return myfunc_ptr( 0x23, ‘c’); // execute SomeFunction using its pointer }
The reason that this is safe is that the pointer returned is that of the "trampoline" in common memory (as described here) and not that of the original function in paged memory. This layer of abstraction is the primary method that C uses to ensure that the function pointer works regardless of which page the function is on.
You can avoid the use of the intermediate trampoline function by using the method described in the prior section to create an extended address pointer that points to the real function. You can obtain the real 32-bit extended address by simply using MAKE_XADDR()
and defining a symbol with the same name as the function with _xaddr
appended as a suffix. At runtime the code will initialize this 32-bit symbol to point to the function in paged memory. Note that the 32-bit extended address cannot be used directly as a C function pointer because GCC supports only 16-bit pointers.
Specifying absolute addresses
Somtimes a program needs to create a variable or function that points to a specific place in memory, thus completely bypassing the linker’s memory management. This is quite rare in practice, so look carefully at other alternatives before creating absolute symbols. Always feel free to contact Mosaic Industries for support and advice on how to implement your memory needs. Many absolute symbols are already defined by the system libraries such as those that point to the processor registers. To reference a specific memory location, which must be specified as 16 bits, use the following syntax:
*(volatile unsigned char *)(0x1234)
This example refers to 1 memory containing 1 byte at address 0x1234.
See also → 4: Additional Information for Advanced Users