Writing and Compiling Programs
In this Chapter we’ll explore the Mosaic IDE Plus™ tool suite for writing and editing your application program. You’ll learn:
- How to efficiently use the editor and compiler to write and compile programs;
- How to structure your project correctly from the beginning;
- Coding and file-naming conventions;
- How to access the PDQ Board’s onboard functions.
Using these techniques will speed the development of your instrument control applications using the PDQ Single Board Computer (SBC).
Writing your application program
Using the CODEBLOCKS editor and GNU C compiler
The Mosaic IDE Plus™ Users Guide details the IDE which you will use to edit and compile your program. Forth programmers will use only the text editor and the terminal, while C programmers will find the entire toolset useful. If you have not read this document, you may want to take a look at it now.
File management for large programs
It is often useful to break up an application program into multiple source code files. It is important that the compiled size of a single .C file not be larger than a single memory page. There isn't a direct conversion from lines of C code into compiled bytes, however a general limit of 150 lines per file will keep code size within reason. If your code overflows the boundary of a memory page, you will see one of the following error messages:
- Error: Ran out of room allocating section .text.3 object
- Error: region data is full …
- banked address [0:ae0f] (ae0f) is not in the same bank as current banked address
- m6811-elf-objalloc.exe has encountered a problem and needs to close.
All about header (.H) files
When you click Build→ Build, each .C file in your project is compiled separately. After this step, the resulting binary files are linked together to produce the final product, a .DLF file.
The purpose of a .H file is to notify the compiler of all the functions provided by a .C file, without explicitly knowing the body of these functions. This is also called a forward declaration. In the following example, the 'process.c' file has a line near the top to #include the 'data.h' file. When the compiler compiles process.c, it trusts that a function matching 'int AcquireData( int )' exists somewhere in the project because it is specified in the .H file. Without this .H file, the compiler will throw an error. At link time, the compiler assembles all the compiled code and verifies that the 'AcquireData()' function actually exists. If the function exists only in the .H file, but not in a .C file, you will receive a link error.
Writing multiple source code files
This section describes the steps a programmer can take to add additional source code files to a project.
- Open the project file in the Mosaic IDE Plus. This section assumes that a single file "main.c" already exists in the project
- Decide the purpose of your new file, and a good name for it. Generally, .C files group functions and variables together which work to accomplish a specific goal. In this example we will be adding a 'data.c' file
- Click File→ New File from the toolbar menu. Type in 'data.c' for the file name, and press 'save'.
- A prompt will appear asking: 'Do you want to add this new file in the active project'. Click 'Yes'.
- Click File→ New File again, and add 'data.h', Click 'Yes' to the prompt.
- Edit your data.c file. Add functions that accomplish your goal.
- Edit your data.h file. Before adding any forward delcarations, you must add a special structure to this file. This structure consists of 2 lines at the top of the file, and one at the end, something like this:
#ifndef _DATA_H_ #define _DATA_H_ // ... #endif
The keyword _DATA_H_ does not need to be similar to the filename, 'data.h', it can be any word. The first two lines must have the same keyword. It must also be unique throughout your entire project. The uniqueness of this keyword allows the compiler to only interpret this file once; preventing redefinition errors. This example keyword is in all capital letters because the C standard suggests that #define keywords be in capitals.
- Add forward declarations to your .H file. These declarations consist of the first line of a C function, with no function body:
int AcquireData( int channel );
- Add extern declarations for your global variables. These are similar to a variable declaration, with the added keyword 'extern':
extern int data_point;
- Add #define constants. These are usually used for numbers that are read-only at runtime, but may need to change as you continue to write your application:
#define UTILIZED_INPUT_PINS (5)
- Add #include "data.h" to any file that needs to use variables or functions provided by 'data.c':
#include "data.h"
With these steps accomplished, you have added a new file, data.c, to your project. When you click Build → Build, this new file will be compiled and linked together with your main.c file.
Note that only one of the .C program files may include a function named main().
Multiple source code example
The following files are a mock example of a data acquisition program using good coding practices. The program is split into 5 files. The files data.c
and process.c
have matching .h
files, while main.c
does not:
data.h
#ifndef _DATA_H_ #define _DATA_H_ // Forward declaration of all functions in the accompanying .C file int AcquireData( int channel ); #define UTILIZED_INPUT_PINS (5) #endif
data.c
#include <mosaic\allqed.h> #include "data.h" // Acquire data from input channel // This is a mock function int AcquireData( int channel ) { int return_value; return_value = 1; if( channel > UTILIZED_INPUT_PINS ) { return_value = UTILIZED_INPUT_PINS; } return return_value; }
process.h
#ifndef _PROCESS_H_ #define _PROCESS_H_ // Forward declaration of all functions in the accompanying .C file int ProcessData(int channel); int ConversionFormula( int input ); // This global variable is visible to all .C files in the project which // have #include "process.h" at the top extern int data_point; #endif
process.c
#include <mosaic\allqed.h> #include "process.h" #include "data.h" // This global variable is visible to all functions in this .C file // If other .C files need to use this variable, they must have #include "process.h" int data_point; int ProcessData(int channel) { // Mock function for acquiring and processing data int data; data = AcquireData( channel ); data = ConversionFormula( data ); return data; } int ConversionFormula( int input ) { // Mock conversion formula input = input + 1; return input; }
main.c
// Mosaic Industries sample application #include <mosaic\allqed.h> #include "process.h" // Here, main.c only has #include "process.h" and not "data.h" // This is possible because main.c does not directly call any of // the functions in data.c. void Welcome() { printf("\nWelcome to example program\n"); } void Results( int data ) { printf("Final data output: %d\n", data ); } int main() { int final_data; Welcome(); final_data = ProcessData(1); Results( final_data ); return 0; }
More about this example
- All of these files must be added to your project.
- Do not #include a .C file. This causes the C compiler to treat all of your source code as one large block that cannot be allocated across memory pages, causing the memory errors described above.
- The #ifndef, #define structure found at the beginning of the .H files prevents functions and variables from being defined twice. The name used in this structure must be unique throughout your project.
Stylistic conventions
Commenting your code
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; this comment marker works for either C or Forth source code, and can even be used to type comments at the terminal prompt. 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. To change the colors click Settings → Editor, then choose the "Colors" tab.
Coding style conventions
The example programs included with Mosaic IDE Plus 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 underscores or 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.
C language file naming conventions
C source code files have the .c extension, and header files have the .h extension. When you use the Mosaic IDE Plus to compile a source code file with the filename,
NAME.c
several files with the extensions shown in Table 4-1 are created:
Files Created by the C Compiler | |
---|---|
FILENAME.ext | Description |
NAME.c | Source code text file created by you, the programmer |
.deps\NAME.d | List of header file dependencies for each source code file |
.objs\NAME.o | Compiled object code from first stage of compiling each source code file |
asms\NAME.s | Assembly output text file created by the C compiler for debugging |
NAME.dlf | Final download file to be loaded on a PDQ Board; includes S-records of compiled code and initialization data, and definitions for interactive debugging |
NAME.elf | The ELF binary file is the primary output of the compilation and linking process. It is in the GNU ELF32 format which is readable by various third party software packages and by the GNU tools. The build system extracts various sections from this file to form the download file. |
NAME_symbols.txt | A symbol table holding the final memory addresses of C variables and functions at the end of linking, which may be useful for debugging. See the --syms option for objdump for ELF files for details. |
NAME_function_sizes.txt | A list of the total size of code compiled from each source code file, as a portion of the PDQ Board memory page size. Keeping the total amount of code in each file smaller than the size of one page will prevent linker errors. |
NAME.cbp | Codeblocks Project File; includes information about every C file, and general project options, in XML format |
NAME.layout | Saves information about which project files were open at previous close |
Intermediate files deleted during normal compilation | |
NAME_shifted.elf | This file is only useful for symbol table generation. More |
NAME_prelink1.elf | This is the elf that is generated from prelinking all objects- internally the file is not yet linked, but rather contains ALL code that will end up in the final target. This is the first link stage. |
NAME_prelink.elf | This is generated from the _prelink1 file. more |
NAME_code.txt | Binary S-record file extracted from the final elf target. For the PDQ Board this file contains only the target executable code; for the PDQ Board Lite it contains both executable code and also the initialization data for initialized variables. |
NAME_data.txt | Binary S-record file extracted from the final elf target. For the full PDQ Board only, this file contains an image of the RAM (.data) section of initialized variables to be initialized into RAM at program startup. |
NAME_forthcalls.txt | A Forth file which gets bundled into the .dlf file which holds headers for _Q functions and _QV variables that may be used for interactive debugging |
NAME_post.txt | This file contains all the text that is appended to the download file. Text can be added to this section using the DLF_APPEND() macro. It becomes part of the download file (.DLF). |
NAME_pre.txt | This file contains all the text that is to be placed at the beginning of the download file. Text can be added to this section using the DLF_PREPEND() macro. It becomes part of the download file (.DLF). |
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 GNU C automated Make Tool by clicking Build → Build, and send the resulting NAME.dlf
download file to the PDQ Board 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 PDQ Board.
Using C 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:
float _Q 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 inter-actively 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 specifier before the prototype.
You can preface the function name in any function prototype with the _Q tag if you want to interactively call the function from the terminal. The PDQ Board’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.
Prototype and Declare the Parameter Types of Every Function
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) C 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 PDQ Board, and all of the standard C header files. 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 PDQ Board. These routines include all the I/O drivers for 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 PDQ Board’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.
The names and contents of the header files included by allqed.h are as follows:
Standard C Header Files | Contents |
---|---|
ctype.h | Functions useful for testing and mapping characters |
math.h | mathematical functions for double precision numbers |
stdio.h | defines variable types, macros, and functions for file and character I/O |
string.h | defines a variable, macro, and several functions for manipulating strings |
QED Kernel Header Files | Contents |
---|---|
ANALOG.h | Onboard ATD (Analog To Digital) driver routines |
ARRAY.h | Routines that dimension, access and manipulate Forth Arrays in paged memory |
COMPILER_UTIL_MACROS.h | Macro definitions that interface the PDQ Kernel, and driver libraries |
HCS12REGS.h | Macro definitions for the Freescale HCS12 (9S12) registers |
HEAP.h | Heap memory manager functions |
INTERRUPT.h | Interrupt identifiers and functions to facilitate posting interrupt handlers |
IOACCESS.h | Memory access primitives for the Wildcard and Smart I/O modules |
MEMORY.h | Memory access functions for paged memory, flash and EEPROM |
MTASKER.h | Multitasking executive and elapsed-time clock routines |
NUMBERS.h | Formatted number-to-string and print routines |
NUMUTIL.h | Pure C library of numerical utilities |
OPSYSTEM.h | Operating system functions for initialization, autostarting, and error handling |
PWM.h | Pulse Width Modulation I/O (PortP) driver routines |
SEGMENTS.h | Facilitates the integration of Forth segments, typically device driver libraries |
SERIAL.h | RS232/RS485, SPI, and IIC serial I/O driver routines |
TIMERIO.h | ECT (Enhanced Capture Timer, PortT) driver routines for timer-controlled I/O |
TYPES.h | Useful type definitions |
USER.h | Declarations of USER_AREA and TASK structures, and user variable definitions |
UTILITY.h | Defines macros such as MIN(), MAX(), ABS(), TRUE and FALSE |
WATCH.h | Routines that set and read the battery-backed real-time smart watch |
Do not nest functions!
You can call any of these functions from within your C code. There is one limitation however:
There are several reasons for this limitation:
- The Forth operating system will not allow a function call to be included as a parameter in the function invocation of another Forth function call. Further, many functions that are callable from C are actually defined in the QED-Forth Kernel or a QED-Forth Kernel-extension library. This includes functions that are in the kernel on the PDQ Board, or are part of software distributions such as the Graphical User Interface (GUI) Toolkit. A call to one of these functions may not be made from within the parameter list of a call to another such function.
- The C compiler does allow functions to be used as parameters in another function call, but doing so greatly increases the stack space needed for the function, making run time errors owing to stack overflow much more likely. Many function calls in C, particularly calls to math functions, use the runtime stack very greedily, often setting up temporary buffers on the stack. Nesting such functions piles up those buffers on the stack, while calling them sequentially does not.
There is always a straightforward way to avoid nesting function calls: simply use a variable to hold the required intermediate return value/parameter. For example, if you need to use the Kernel function StoreChar() to store at xaddress 0x0F8000 the contents fetched by FetchChar() from xaddress 0x0F8100 in paged memory, you could execute the following statements:
static char contents = FetchChar( (xaddr) 0x0F8100 ); StoreChar( contents, (xaddr) 0x0F8000 );
This code is correct, while nesting the call to FetchChar()
inside the parameter list of StoreChar()
would be incorrect.
Initializing variables
Unlike the older textpad based Mosaic IDE, the GCC-based Mosaic IDE Plus for the PDQ Board supports compile-time initialization of variables, and properly carries out the initialization each time that main (the top level program) is executed. For example, if your source code contains the statements:
static int myvar = 0x1234; static int otherVar;
then each time that main() is executed, myvar will be initialized to 0x1234, and otherVar will be zeroed. Careful attention to this issue is still warranted, however, as improper variable initializations are a major source of bugs in embedded systems programs.
Compiling programs
The Mosaic IDE Plus is the main software you will use to compile programs. The IDE Plus uses "projects" to manage your source code files. All files added to a project will be compiled at build time.
To compile a project that is already open, simply click Build from the toolbar menu, and select Build. This command will execute a build, and display all output in the "Build log" tab at the bottom of the IDE Plus window. For more information about compiling programs with the Mosaic IDE Plus, see Using the IDE Plus.
See also → Mosaic IDE Plus