This chapter offers hints on how to speed up an application and how to store persistent data at run time.
The file RabbitBIOS.c is a wrapper that permits a choice of which BIOS to compile. A modular design has many of the configuration macros in separate configuration libraries. The BIOS file and configuration libraries are located in LIB\BIOSLIB. Table 19-1 lists the new files and gives a brief description of their content.
To create a user-defined BIOS, begin with a copy of STDBIOS.C to modify. It is prudent to save STDBIOS.C as is and rename the modified file.
The Dynamic C GUI offers an option for hooking a user-defined BIOS into the system. See the description of the “Advanced... Button” for details on using this GUI option. You will need to consider the configuration files and associated macros described in Table 19-1.
There are a number of methods that can be used to reduce the size of a program, or to increase its speed. Let’s look at the events that occur when a program enters a function.
The function saves IX on the stack and makes IX the stack frame reference pointer (if the program is in the useix mode).
The function sets up stack corruption checks if stack checking is enabled (on).
The program notifies Dynamic C of the entry to the function so that single stepping modes can be resolved (if in debug mode).
The last two consume significant execution time and are eliminated when stack checking is disabled or if the debug mode is off.
When the PC is connected to a target controller with Dynamic C running, the normal code and debugging features are enabled. Dynamic C places an RST 28H instruction at the beginning of each C statement to provide locations for breakpoints. This allows the programmer to single step through the program or to set breakpoints. (It is possible to single step through assembly code at any time.) During debugging there is additional overhead for entry and exit bookkeeping, and for checking array bounds, stack corruption, and pointer stores. These “jumps” to the debugger consume one byte of code space and also require execution time for each statement.
At some point, the Dynamic C program will be debugged and can run on the target controller without the Dynamic C debugger. This saves on overhead when the program is executing. The nodebug keyword is used in the function declaration to remove the extra debugging instructions and checks.
nodebug int myfunc( int x, int z ){
...
}
If programs are executing on the target controller with the debugging instructions present but without Dynamic C attached, the call to the function that handles RST 28H instructions in the vector table will be treated as a NOP by the processor when in debug mode. The target controller will work, but its performance will not be as good as when the nodebug keyword is used.
If the nodebug option is used for the main() function, the program will begin to execute as soon as it finishes compiling (as long as the program is not compiling to a file).
Use the directive #nodebug anywhere within the program to enable nodebug for all statements following the directive. The #debug directive has the opposite effect.
Assembly code blocks are nodebug by default, even when they occur inside C functions that are marked debug, therefore using the nodebug keyword with the #asm directive is usually unnecessary.
The built-in I/O functions (WrPortI(), RdPortI(), BitWrPortI() and BitRdPortI()) can be generated as efficient in-line code instead of function calls. All arguments must be constant. A normal function call is generated if the I/O function is called with any non-constant arguments. To enable in-line code generation for the built-in I/O functions check the option “Inline builtin I/O functions” in the Compiler dialog, which is accessible by clicking the Compiler tab in the Project Options dialog.
Data that will never change in a program can be put in flash by initializing it in the declarations. The compiler will put this data in flash. See the description of the const, xdata, and xstring keywords for more information.
If data must be stored at run-time and persist between power cycles, there are several ways to do this using Dynamic C functions:
User Block - Recommended method for storing non-file data. Factory-stored calibration constants live in the User block for boards with analog I/O. Space here is limited to as small as (8K-sizeof(SysIDBlock)) bytes, or less if there are calibration constants. For specific information about the User block on your board, open the sample programs userblock_info.c and/or idblock_report.c. The latter program will print, among other things, the location of the User block.
WriteFlash2 - This function is provided for writing arbitrary amounts of data directly to arbitrary addresses in the second flash.
Battery-Backed RAM - Storing data here is as easy as assigning values to global variables or local static variables. The file system can also be configured to use RAM.
The life of a battery on a Rabbit board is specified in the user’s manual for that board; some boards have batteries that last several years, most board have batteries that come close to or surpass the shelf-life of the battery. If it is important that battery-backed data not be lost during a power failure, know how long your battery will last and plan accordingly.
The User block is an area near the top of flash reserved for run-time storage of persistent data and calibration constants. The size of the User block can be read in the global structure member SysIDBlock.userBlockSize. The functions readUserBlock() and writeUserBlock() are used to access the User block. These function take an offset into the block as a parameter. The highest offset available to the user in the User block will be
SysIDBlock.userBlockSize-1
if there are no calibration constants, or
DAC_CALIB_ADDR-1
if there are.
See the Rabbit designer’s handbook for more details about the User block.
See the Dynamic C Function Reference Manual for a complete description of WriteFlash2().
There is a function available for writing to the first flash, WriteFlash(), but its use is highly discouraged for reasons of forward source and binary compatibility should flash sector configuration change drastically in a product. For more information on flash compatibility issues, see technical notes TN216 “Is your Application Ready for Large Sector Flash?” and TN217 “Binary and Source Compatibility Issues for 4K Flash Sector Sizes” at Rabbit’s website: rabbit.com.
Static variables and global variables will always be located at the same addresses between power cycles and can only change locations via recompilation. The file system can be configured to use RAM also. While there may be applications where storing persistent data in RAM is acceptable, for example a data logger where the data gets retrieved and the battery checked periodically, keep in mind that a programming error such as an uninitialized pointer could cause RAM data to be corrupted.
xalloc() will allocate blocks of RAM in extended memory. It will allocate the blocks consistently from the same physical address if done at the beginning of the program and the program is not recompiled.
Customers with programs that are near the limits of root code and/or root data space usage will be interested in these tips for saving root space. For more help, see Technical Note TN238 “Rabbit Memory Usage Tips.” This document is available by choosing Online Documentation from the Help menu of Dynamic C or at at: rabbit.com.
Increasing the available amount of root code space may be done in the following ways:
Enable Separate Instruction and Data Space
A hardware memory management scheme that uses address line inversion to double the amount of logical address space in the base and data segments is enabled on the Compiler tab of the dialog. Enabling separate I&D space doubles the amount of root cod and root data available for an application program.
This will cause C functions that are not explicitly declared as “root” to be placed in xmem. Note that the only reason to locate a C function in root is because it modifies the XPC register (in embedded assembly code), or it is an ISR. The only performance difference in running code in xmem is in getting there and returning. It takes a total of 12 additional machine cycles because of the differences between call/lcall, and ret/lret.
Increase ROOT_SIZE_4Ki
The macro ROOT_SIZE_4K determines the beginning logical address for the data segment.
Root code space can be increased by increasing ROOT_SIZE_4K in StdBIOS.c. The default is 0x3 when separate I&D space is on, and 0x6 otherwise. It can be as high as 0xB.
When separate I&D space is on, ROOT_SIZE_4K defines the boundary between root variable data and root constant data. In this case, increasing ROOT_SIZE_4K increases root constant space and decreases root variable space.
When separate I&D space is off, ROOT_SIZE_4K determiness the boundary between root variable data and the combination of root code and root constant data. Note that root constants are in the base segment with root code. In this case, increasing ROOT_SIZE_4K increases root code and root constant space and decreases root data space.
Compile out floating point support
Floating point support can be conditionally compiled out of stdio.lib by adding #define STDIO_DISABLE_FLOATS to either a user program or the Defines tab page in the Project Options dialog. This can save several thousand bytes of code space.
Reduce usage of root constants and string literals
Shortening literal strings and reusing them will save root space. The compiler automatically reuses identical string literals.
These two statements :
printf (“This is a literal string”);
sprintf (buf, “This is a literal string”);
will share the same literal string space whereas:
sprintf (buf, “this is a literal string”);
will use its own space since the string is different.
Use the far keyword to directly declare variables in xmem
See Section 4.3 and Chapter 14 for more information on the far keyword.
Turn off selected debugging features
Watch expressions, breakpoints, and single stepping can be selectively disabled on the Debugger tab of Project Options to save some root code space.
Place assembly language code into xmem
Pure assembly language code functions can go into xmem.
#asm
foo_root::
[some instructions]
ret
#endasm
The same function in xmem:
#asm xmem
foo_xmem::
[some instructions]
lret ; use lret instead of ret
#endasm
The correct calls are call foo_root and lcall foo_xmem. If the assembly function modifies the XPC register with
LD XPC, A
it should not be placed in xmem. If it accesses data on the stack directly, the data will be one byte away from where it would be with a root function because lcall pushes the value of XPC onto the stack.
Increasing the available amount of root data space may be done in the following ways:
Enable Separate Instruction and Data Space
A hardware memory management scheme that uses address line inversion to double the amount of logical address space in the base and data segments is enabled on the Compiler tab of the dialog. Enabling separate I&D space doubles the amount of root code and root data available for an application program.
Decrease ROOT_SIZE_4Kii
The macro ROOT_SIZE_4K determines the beginning logical address for the data segment.
Root data space can be increased by decreasing ROOT_SIZE_4K in StdBIOS.c. At the time of this writing, RAM compiles should be done with no less than the default value (0x6) of DATAORG when separate I&D space is off. This restriction is to ensure that the pilot BIOS does not overwrite itself.
When separate I&D space is on, ROOT_SIZE_4K determines the boundary between root variable data and root constant data. In this case, decreasing ROOT_SIZE_4K increases root variable space and descreases root constant space.
When separate I&D space is off, ROOT_SIZE_4K determines the boundary between root variable data and the combination of root code and root constant data. Note that root constants are in the base segment with root code. In this case, decreasing ROOT_SIZE_4K increases root data space and decreases root code space.
Use xmem for large RAM buffers
xalloc() can be used to allocate chunks of RAM in extended memory (xmem) but its use is no longer necessary for data objects which exist for the program’s lifetime. It is, however, preserved for backwards compatibility.
Using the far keyword is easier and more efficient than using xalloc(). Consider the following code:
far char my_buffer[BUFFER_SIZE];
int main() {
far char *p;
p = my_buffer;
... // access xmem directly
}
Large buffers used by Dynamic C libraries are already allocated from RAM in xmem.
i. The macro DATAORG was deprecated in favor of ROOT_SIZE_4K starting with Dynamic C 10.21. ROOT_SIZE_4K equals DATAORG/0x1000.
ii. The macro DATAORG was deprecated in favor of ROOT_SIZE_4K starting with Dynamic C 10.21. ROOT_SIZE_4K equals DATAORG/0x1000.