This appendix contains the following sections:
Introduction
ANSI-C Extensions
Memory Type Qualifiers
Pointers
Absolute Variable Allocation
SFR Registers
Function Qualifiers
Assembly Interface
Built-in (intrinsic) Functions
Library Routines
Compiler Invocation
Memory Models
Libraries
Controls or Pragmas
Compiler Optimizations
This appendix explains how you can migrate your C-51 application from the Keil, Franklin or Archimedes C-51 compiler to the TASKING C-51 compiler (cc51).
There are two major areas of differences between the two compilers which are the cause of most of the changes you will have to deal with. First of all the C language extensions present in both compilers to support special 8051 family features. And secondly the compiler output. The Keil compiler produces an object file as output whereas the TASKING compiler generates an assembler file. The Keil compiler uses controls to steer the behavior of the compiler. The TASKING compiler behavior is steered with the more commonly used options, making command lines both shorter and clearer.
In the TASKING compiler all extra keywords for the 8051 extensions of ANSI-C have an underscore prefix. The most frequently occurring differences in keywords between the Keil compiler and the TASKING compiler can be resolved by using preprocessor definitions. The header file keil.h contains these definitions. This file can be 'included' for all C source files using the command line option "-Hkeil.h".
Both compilers have the same memory type qualifiers, which are used in the same place in the grammar so a simple preprocessor definition suffices. For this purpose the definitions listed below are included in keil.h.
/* memory type keywords */ #define data _data #define bdata _bdat #define idata _idat #define pdata _pdat #define xdata _xdat #define code _rom
The TASKING compiler does not support generic (3-byte) pointers, since it is considered to be too costly both in execution time and memory usage. Therefore, pointers declared without a specification of the referred memory type will point to the default memory type (which depends on the selected memory model) being either 1 or 2 bytes. This rule is also valid for library functions using pointers. Sometimes you might want to make an exception, e.g. to keep the format strings of the printf() function and related library functions in rom. This is also supported, as is explained in section 3.10 Strings.
The way of specifying the address for an absolute variable is also different for the two compilers, as is shown below.
/* Keil compiler */ /* TASKING compiler */ char var _at_ 0x80; char var _at( 0x80 );
The TASKING way makes it easy to make a preprocessor define to convert the ANSI-C extension. This is particularly useful when you want to compile your program with a compiler for your host. Especially for this purpose the include file cc51.h has been included in the package.
As with absolute variable allocation the TASKING compiler uses a different way of specifying the address of an SFR register than the Keil compiler, as is shown in the following example:
/* Keil compiler */ /* TASKING compiler */ sfr PSW = 0xd0; _sfrbyte PSW _at( 0xd0 ); sbit OV = PSW ^ 2; _sfrbit OV _atbit( PSW, 2);
However, you will rarely need to specify an SFR register, because for most 8051 derivatives a register include file is delivered with the package.
The Keil 'sfr16' type is not supported by the TASKING compiler, because the order of the few 16-bit wide SFR registers is little endian whereas the 8051 itself is treated big endian.
For the TASKING compiler the extra qualifiers for functions defined for the ANSI extension appear in the syntax before the function name and parameter list. This is conform the ANSI syntax for assigning type and other qualifiers to an object (variable or function). In contrast these attributes appear after the function name and parameter list in the syntax for the Keil compiler.
/* Keil compiler */ int func( char c ) small interrupt 1 using 2 { <function_code> } /* TASKING compiler */ _small _interrupt(1) _using(2) int func( char c ) { <function_code> }
Note that for the TASKING compiler 'reentrant' is a separate model and not, as for the Keil compiler, a function attribute. So, for the migration from the Keil compiler to the TASKING compiler you should use the _reentrant model qualifier for every function with the reentrant qualifier. You should ignore any model qualifier you were using in combination with the reentrant qualifier:
/* Keil compiler */ int func( char c ) large reentrant /* TASKING compiler */ _reentrant int func( char c )
The function qualifier used by both compilers to make parameter passing for a function according to the PL/M-51 convention is placed in both grammars before the function identifier. So, the preprocessor definition included in keil.h, as shown below, is sufficient for an easy migration on this point.
/* function qualifier keyword */ #define alien _plmprocedure
The passing of parameters to a function via registers is exactly the same in both compilers. The name and offset for parameters passed in memory is incomparable between the two compilers.
The two compilers use almost the same registers for the return value of a function, as shown in the following table.
Return Type | Keil Compiler | TASKING Compiler |
bit | Carry | Carry |
char / 1-byte pointer | R7 | A |
int / 2-byte pointer | R6-R7 | R6-R7 |
long | R4-R7 | R4-R7 |
float | R4-R7 | Floating point stack |
generic pointer | R1-R3 | - |
Table G-1: Function return types
Both compilers have several built-in (intrinsic) functions. However, only four of them have the same objective. For these four a preprocessor definition, which is included in the header file keil.h as listed below, will establish the migration.
/* names inline functions */ #define _crol_ _rol #define _cror_ _ror #define _testbit_ _testclear #define _nop_ _nop()
For the other built-in rotate functions of the Keil compiler, namely _irol_, _lrol_, _iror_ and _lror_, there are no direct replacements.
The TASKING compiler has two other built-in functions namely _getbit and _putbit, which can be used to manipulate bits in an arbitrary bitaddressable byte without the need to declare a separate bit identifier for each bit.
The prototypes of the library routines of the TASKING compiler package are conform to the ANSI standard. This is not the case for the libraries included in the Keil compiler package. However, in most of these cases the Keil library uses character parameters where the TASKING library uses integers, but due to automatic type conversions generated by the compiler this will usually not create problems.
Both compiler packages allow you to change the I/O mechanism for all I/O functions by changing one function for input and one for output. For the Keil libraries these are getkey() and putchar() and for the TASKING libraries these are _ioread() and _iowrite() respectively. The latter two get a parameter with a stream number passed, thus allowing you to support several independent I/O streams, each with its own mechanism.
The Keil compiler has three memory models, namely small, compact and large. The TASKING compiler has four models, the first three namely small, aux and large respectively are equivalent to the Keil models. The fourth TASKING model is reentrant.
The TASKING compiler always keeps the large virtual stack required for reentrancy in external memory, whereas the Keil compiler keeps it in the default memory determined by the used memory model.
Since the TASKING compiler has a reentrant model, it also has a library to go with it. The parameter passing to the functions included in this library use the reentrant convention, that is via the virtual stack if there are not enough registers.
Both compiler packages contain a library for each memory model. They both use the same naming conventions, thus the following shows the names of equivalent libraries.
Compiler Model |
Keil Compiler |
TASKING Compiler |
Small | c51s.lib | c51s.lib |
Compact/Auxpage | c51c.lib | c51a.lib |
Large | c51l.lib | c51l.lib |
Reentrant | - | c51r.lib |
Table G-2: Libraries
The Keil compiler package also contains a floating point library for all three memory models. In contrast the TASKING package contains two floating point libraries, which can be used independent of the selected memory model. The most commonly used floating point library (called float.lib) keeps the floating point stack in external ram (XDATA), whereas the other one (called floats.lib) keeps it in internal ram (IDATA). The IDATA version contains only the most basic floating point operations. In general, the smaller one, with respect to data memory usage, is comparable with the small model version of the Keil package (c51fps.lib), and the other one is comparable with the large model version of the Keil package (c51fpl.lib).
For the Keil compiler all compiler controls can also be used as a pragma within the C source code. The TASKING compiler has some equivalent notation for most of these directives, either as a compiler command line option or a C source pragma or both, or as an assembler directive. The table below shows how to convert Keil directives to an equivalent TASKING notation.
Keil | TASKING | Comment |
aregs noaregs | no translation | |
asm endasm |
asm endasm |
compiler pragma compiler pragma |
code | assembly is always generated, use -s for mixing with source code | |
compact | -Ma | compiler option |
debug nodebug | -g | compiler option |
define | -D | compiler option |
disable | no translation | |
eject | eject | assembler directive |
interval | use assembly | |
intpromote nointpromote | handled automatically by compiler | |
intvector | use assembly | |
large | -Ml | compiler option |
listinclude |
-li listinc |
compiler option or compiler pragma |
maxarg |
-a arglist |
compiler option or compiler pragma |
noamake | no translation | |
nocond | no translation | |
noextend |
-U_CC51 -Hcc51.h | compiler options |
object noobject |
object noobject |
assembler directive assembler directive |
objectextend | default behavior | |
optimize |
-O optimize |
compiler option or compiler pragma see following paragraph |
order | -Ot | compiler option |
pagelength | pagelength | assembler directive |
pagewidth | pagewidth | assembler directive |
preprint | -E | compiler option |
assembler directive | ||
regfile | -C | compiler option |
registerbank | registerbank | assembler directive |
regparms | -Or | compiler option |
rom small rom compact rom large |
-rs -rm -rl |
compiler option compiler option compiler option |
save | save | assembler directive |
restore | restore | assembler directive |
small | -Ms | compiler option |
src | -o | compiler option |
symbols | use linker map file |
Table G-3: Controls, pragmas and options
Both C compilers allow you to customize the compiler optimizations. The Keil compiler uses the optimize control or pragma and the TASKING compiler uses the -O option or the optimize pragma.
The TASKING compiler allows you to switch on or off most optimization mechanisms independent of other optimizations. This in contrast with the Keil compiler which only allows you to select a certain level of optimization except for optimization seperatly on speed or size.
The following table shows which TASKING optimizations options are comparable to the extra optimizations introduced for each Keil optimize level. Some of the Keil optimizations, however, cannot be switched off in the TASKING compiler, for example, constant folding (Keil level 0), data overlaying (Keil level 2), etc.
Keil Compiler |
TASKING Compiler |
size | F |
speed | f |
0 | p |
1 | p |
2 | |
3 | h |
4 |
w // and a #pragma see 4.4 Pragmas for switch statements |
5 | c, s |
6 | l |
Table G-4: Optimization
The TASKING compiler offers you many more optimizations. Refer to the description of the -O option for a detailed description.