This chapter contains the following sections:
Startup Code
Register Usage
Segment Usage
Stack
Heap
Floating Point
Interrupt Functions
Multiple Data Pointer Support
Assembly Language Interfacing
Reentrant Model / _reentrant Functions
Linking an Application
Troubleshooting
Linking Problems
Run-time Problems
When linking your C modules with the library, you must also link the object module, containing the C startup code. This file is called cstart.obj.
Because this module specifies the run-time environment of your C application, you might want to edit it to match your needs. Therefore, this module is delivered in assembly source in the file cstart.asm in the lib/src subdirectory. Typically, you will copy the template startup file to your own directory and edit it. The startup code contains preprocessor symbols that can be interpreted by mpp51 when you want to make your own version of the object file.
EDE is capable of generating the startup code automatically. To do this: open the Project | Project Options dialog, expand the Processor entry and select Startup Code. Enable the options Generate startup code (<project>_cstart.asm) and Add startup code (<project>_cstart.asm) to your project.
Table 7-1 shows all the macros (defines) that can be set for cstart.asm. The defines can be set using mpp51 command line option -Dmacro=value or within EDE in the Processor | Startup Code and Assembler | Macro Preprocessor pages of the Project | Project Options dialog.
Define | Default | Description |
CLR_EA | setting this define makes sure all interrupts are disabled at startup; by default the startup code will not clear the interrupt enable all bit (EA) | |
FLOATMEM | 0 | define the number of floats on the floating point stack |
HEAP | 0 | define the heap size |
MFLOAT | XDATA | define to IDATA when the floating point stack is in IDAT instead of XDAT (link floats.lib in that case instead of float.lib). |
MODEL | SMALL | define the memory model (SMALL, AUX, LARGE or REENTRANT) |
MON51 | NO | define to TASKING when using the TASKING Rom monitor define to RISM51 when using the Intel Rism51 monitor. |
P2 | 0 | set port P2 pins at startup; this defines the external RAM page to be used for paged data (_pdat) |
PROTECT | NO | define YES only for reentrant model with interrupt functions using the virtual stack |
RAMSIZE | 080H | size of internal (IDAT) memory (128-256 bytes) to be cleared at startup |
REGBANK | 0 | define the default register bank at startup |
STACKLENGTH | 20H | define a (minimum) stack length |
SYSCON | certain Infineon Technologies derivates have a SYSCON SFR which can be used to direct 8-bit MOVX instructions to internal XRAM. Defining this macro to the correct value will result in correct initialization of this SFR at startup | |
VSTACK | NO | define YES if at least one function is declared _reentrant while the memory model is not reentrant |
VIRT_STACK | 400H | define the virtual stack size (only for reentrant model) |
XDATSTART XDATEND |
0 0 | specify the start and end of the XDAT area to be cleared at startup |
Table 7-1: Macros used in cstart.asm
The startup code contains macro preprocessor symbols, so you must use mpp51 before asm51 when you want to make a new version of the object file:
mpp51 cstart.asm asm51 cstart noprint
In the C startup code an absolute code segment is defined for setting up the power on vector and the C-51 environment. The power-on vector contains a jump to the __START label, which is placed after all other interrupt vectors. The code space for all non used interrupt vectors may be occupied by small user code segments. When this is not wanted, you should allocate the space for all non used interrupt vectors in the startup code. Thus preventing link51 from using this area for a user code segment. If you are using interrupts, you should not allocate the space at the addresses of the interrupt vector, because the real interrupt vectors are loaded from the library. If you do allocate this space, link51 will warn you with the message: "CODE SPACE MEMORY OVERLAP".
The stack is defined in a segment called ?STACK, because link51 allocates this segment after all other IDATA segments. The public symbol __STKSTART must be present, because it is used by both a debugger and the library function exit(). The stack size can be controlled with the macro preprocessor symbol STACKLENGTH, which defaults to 32 bytes. Remember that there must be enough space allocated in this ?STACK segment for the stack, which grows upwards.
When using the reentrant model or when some functions are programmed _reentrant, a virtual stack is needed. The size of this stack (which is placed in external RAM) is defined by the preprocessor symbol VIRT_STACK. When not using the reentrant model, but some functions are programmed _reentrant, allocation of the virtual stack and initialization of the virtual stack pointer is forced by defining the preprocessor symbol VSTACK.
When using the reentrant model or when functions are programmed _reentrant, it may be needed to change the value of PROTECT in the cstart.asm file. In that case, please read section 7.10 , Reentrant Model / _reentrant Functions.
The heap size is also defined using a macro preprocessor symbol. The heap area is allocated in XDATA.
See section 7.5
,
Heap
,
for detailed information on heap management.
When using floating point in your application, you should define the size of a floating point stack.
See also section 7.6
, Floating Point.
All available internal RAM and external RAM must be cleared, because in C all non initialized static variables are defined to have a value of 0 at startup. This is done by the C startup code. The internal RAM size is defined with the macro preprocessor symbol RAMSIZE, which normally contains a value between 128 (e.g. 8051) and 256 (e.g. 8052, 80C552). The start address and end address of external RAM are defined with the preprocessor symbols XDATSTART and XDATEND. By default, no memory will be cleared by the startup code. When it is needed that memory is cleared at startup, this should be changed in the startup code.
The startup code also takes care of initialized C variables, residing in the different RAM areas. Each memory type has a unique name for both the ROM and the RAM segment. The startup code copies the initial values of initialized C variables from ROM to RAM, using these special segments and some run-time library functions. A special segment is used for strings in ROM, which are copied to the appropriate RAM segment, depending on the memory model used for the C modules. Therefore you must specify the memory model you are using to mpp51 before it processes the startup code. This can be done by defining the preprocessor symbol MODEL. The startup file uses the define MODEL to select the RAM area used as destination for the strings.
When everything described above has been executed, your C application is called, using the public label _?main, which has been generated by cc51 for the C function main().
When the C application 'returns', which is not likely to happen in an embedded environment, the program performs an endless loop, using the assembly label __STOP. When using a debugger, it can be useful to set a breakpoint on this label, indicating the program has reached the end, or the library function exit() has been called.
In all models cc51 uses the following 8051 registers for code generation: R0-R7, A, B, DPTR and PSW. When calling a user assembly routine from C, none of these registers need to be saved by the assembly routine, because these registers are used for temporary results only. When one of these registers has a temporary result, the compiler saves it on stack before the assembly language routine is called, and restores it afterwards.
cc51 uses the following registers for C function return types:
Return type | Register | Description |
bit | C | (carry) |
char | A | (accumulator) |
short/int | R6-R7 | (R6 high byte, R7 low byte) |
long | R4-R7 | (R45 high word, R67 low word) |
float | - | (Floating point stack) |
near pointer | A | (accumulator) |
far pointer | R6-R7 | (R6 high byte, R7 low byte) |
Table 7-2: Register usage
cc51 uses a large number of segments. This section contains a list of all possible segment names of a complete C application:
C51_BI user C bit type variables
CINIT_RAM_BI initialized C bit variables
BSEG AT xx absolute segments for variables placed using the _at() keyword
FP_BIT floating point data used by run-time routines
C51_DA user C variables residing in data
CINIT_RAM_DA initialized C variables residing in data
C51_BA user C variables residing in bitaddressable data
CINIT_RAM_BA initialized C variables residing in bitaddressable data
DSEG AT xx absolute segments for variables placed using the _at() keyword
DRSEG floating point data used by run-time routines
DBRSEG bitaddressable floating point data used by run-time routines
C51_I user C variables residing in idat
CINIT_RAM_DI initialized C variables residing in idat
?STACK last IDATA segment, for stack allocation
CINIT_RAM_ST for small model only, string area
ISEG AT xx absolute segments for variables placed using the _at() keyword
CSEG AT 0H absolute code segment for power on vector and startup code.
STARTUP C Startup Code
C51_PR user C functions
C51LIB_PR C library functions
?C51RTL_PR run-time library functions
LIB_FP floating point run-time routines
CSEG AT xx absolute code segments for interrupt vectors
C51_CO user C variables residing in rom, switch tables and 'romstrings'
CINIT_ROM_BI initial values of user initialized C bit variables
CINIT_ROM_DA initial values of user initialized C variables residing in data
CINIT_ROM_BA initial values of user initialized C variables residing in bitaddressable data
CINIT_ROM_ID initial values of user initialized C variables residing in idat
CINIT_ROM_PD initial values of user initialized C variables residing in pdat
CINIT_ROM_XD initial values of user initialized C variables residing in xdat
CINIT_ROM_ST strings (residing in either idat, pdat or xdat, depending on memory model used)
C51_PD user C variables residing in pdat
CINIT_RAM_PD initialized C variables residing in pdat
C51_XD user C variables residing in xdat
CINIT_RAM_XD initialized C variables residing in xdat
?HEAP allocation of heap area
CINIT_RAM_ST for all models but small, string area
XSEG AT xx absolute segments for variables placed using the _at() keyword
?VIRT_STACK virtual stack space used by _reentrant functions
FP_XDAT floating point data used by run-time routines
If overlaying is used (default for non-reentrant functions), more segments are declared containing the module name, function name, memory type and register bank involved.
The segment CINIT_RAM_ST (RAM area for strings) is allocated in either idat, pdat or xdat, depending on the memory model (MODEL) used in the C startup code. Default is idat. For details on changing startup code, see section 7.1, Startup Code.
If you use the -R option, to specify the name cc51 must use for a certain segment, this name is added to this list. Note that link51 produces a link map (suffix .l51) which shows the addresses of all segments used in the application.
The following diagrams show the structure of the stack. The first diagram reflects the system stack. The second diagram shows the virtual stack when using reentrant functions.
Figure 7-1: Stack diagrams
The system stack is used (using direct internal RAM) for return addresses only. The stack is allocated via the ?STACK segment. You can specify the size of the stack segment in the C startup code (cstart.asm). link51 locates the ?STACK segment as the last indirect addressable internal RAM segment (after all the user IDATA segments), because the system stack is growing from low to high. For _small, _aux and, _large functions, automatics and parameters are allocated in overlayable data sections, and therefore, do not use any stack space.
In EDE you can enter the system stack size in the
System stack size field in the Linker | Stack/Heap entry of the Project | Project Options... dialog.
The label __STKSTART (also present in cstart.asm) is used as the absolute bottom of the system stack. If this label is not present, the system reset value of the SP register is used (at address data:0x7).
For _reentrant functions, a virtual stack is used in external RAM. Automatics and parameters are all accessed using a virtual stack pointer register, allocated as a 16-bit pointer in direct addressable internal RAM (label __SP). The stack frame also contains a so-called virtual frame pointer, which can be seen as a frame pointer register for debugging purposes, and therefore, is supported by CrossView Pro as a pseudo register called $vfp. The saved registers are also accessed using a virtual stack pointer. The virtual stack pointer can be seen as a virtual stack pointer register, and therefore, is supported by CrossView Pro as a pseudo register called $vsp.
In EDE you can enter the virtual stack size: enable the
option Application uses reentrant functions in the Linker | Stack/Heap entry of the Project | Project Options... dialog and enter a size in the Virtual stack size field.
Run time routines are called for a function's prologue, epilogue and automatic/parameter access.
The label __TOP_OF_VIRT_STACK (cstart.asm) is used as the absolute top of the virtual stack. When using _reentrant functions, this label, and of course __SP, should be present.
The heap is only needed when dynamic memory management library functions are used: malloc(), calloc(), free() and realloc(). The heap is a reserved area in external RAM with a default size of 0 bytes. If you use one of the memory allocation functions listed above, the linker will give errors if no heap is defined. So when you want to use one of these routines, you must change the heap size in the startup code.
The macro preprocessor symbol HEAP is used to define the size of the heap. A special XDAT segment called ?HEAP is used for the allocation of the heap area. You can place the heap segment anywhere in memory, using a linker command file specifying either the order of allocation or an absolute address. The public assembly symbols __HEAPSTART and __HEAPLENGTH are used by the library function sbrk(), which is called by malloc() when memory is needed from the heap.
After editing, you must process the C startup file with both mpp51 and asm51 to make the correct object file. For a detailed description, see section 7.1, Startup Code.
cc51 has implemented single precision floating point arithmetic, i.e. 'double' and 'long double' variables are treated as normal 'float' variables.
Floating point operators use a special floating point stack area which should be defined within the startup code. This area is placed in external RAM and has a default size of 0 bytes. You must change the size (in the startup code) to be able to use floating point.
A special floating point library is delivered to support floating point arithmetic when no external RAM is available. This library is called floats.lib. This library uses a floating point stack in internal RAM (idat space). When you use this library, no math functions are available within the library. In the startup code you have to specify that the floating point stack is located in internal RAM (see the startup file cstart.asm for more information). If you do not change the startup code, the linker will produce error messages. Placing the floating point stack in internal RAM does not significantly increase floating point arithmetic.
When your application uses floating point arithmetic, be aware of the following:
- Define a floating point stack in the startup code, all operations and temporary results are placed on this stack. For very complex expressions, the stack must be large enough to hold all temporary results, but normally 5 elements will do.
- Floating point is not reentrant. No floating point arithmetic or even assignments can be done on interrupt.
- The floating point library float.lib must be specified to the linker as the last library in the list. Also the C library must be linked before float.lib.
- Due to the very limited internal RAM of a 80C751 derivative (only 64 bytes), floating point is not supported.
Interrupt functions may be implemented directly in C, by using the _interrupt(n) function qualifier. A function declared with this qualifier differs from a normal function definition in a number of ways:
1. The appropriate interrupt vector, consisting of a JMP instruction jumping to the interrupt function is generated. The vector may be suppressed with _interrupt(-1) with the -v option or the #pragma novector.
2. All non R0-R7 registers A, B, DPTR and PSW that might possibly be corrupted during the execution of the interrupt function are saved on function entry and restored on function exit. The compiler will check the function to see which of these registers are being used and automatically save/restore only those registers. When the _using() qualifier is used the registers R0-R7 are implicitly saved when the register bank is being switched (by using the predefined symbolic register addresses AR0-AR7). When this qualifier is not used the compiler will check the function and save/restore only those registers.
3. The function is terminated with a RETI instruction instead of a RET instruction.
; 8051 C compiler vx.y rz SNaaaaaa (c) year TASKING, Inc. ; options: -s $CASE NAME INTRPT ; intrpt.c 1 int x,y; PUBLIC _x C51_DA SEGMENT DATA RSEG C51_DA _x: DS 2 PUBLIC _y _y: DS 2 ; intrpt.c 2 ; intrpt.c 3 _interrupt(17) void int17( void ) ; intrpt.c 4 { PUBLIC _?int17 CSEG AT 08BH LJMP _?int17 ; free registers in this function: B DPTR R1 R2 R3 INTRPT_INT17_PR SEGMENT CODE RSEG INTRPT_INT17_PR _?int17: USING 0 PUSH ACC PUSH AR0 PUSH AR4 PUSH AR5 PUSH AR6 PUSH AR7 PUSH PSW ; intrpt.c 5 x++; INC _x+1 MOV A,_x+1 JNZ _3 INC _x _3: ; intrpt.c 6 y += x-3; MOV R7,_x+1 MOV R6,_x MOV R5,#03H MOV R4,#00H LCALL __MINI MOV R0,#_y LCALL __CAPLIID ; intrpt.c 7 ; intrpt.c 8 return; ; intrpt.c 9 } POP PSW POP AR7 POP AR6 POP AR5 POP AR4 POP AR0 POP ACC RETI ; intrpt.c 10 EXTRN CODE(__MINI) EXTRN CODE(__CAPLIID) EXTRN CODE(SMALL) END
When an interrupt occurs, the vector instructs the processor to jump to the handler. The interrupt handler always saves PSW. When the _using() qualifier is being used it will switch to the correct register bank by loading a new value in PSW, based on the value specified with the _using() qualifier. When this qualifier is not being used each of the registers R0-R7 which are (or could be) used in the interrupt routine will be saved. Each of the non R0-R7 registers: A,B and DPTR, are also being saved when they are used in the routine. After the context is being saved the user C interrupt function is being executed, and when it is completed the context is being restored. All saved registers are being popped from the stack including PSW. By restoring the original PSW value, the correct register bank is being restored automatically. Finally the RETI (return from interrupt) is executed.
In the above example the C interrupt function uses the following registers: A, R0, R4, R5, R6, R7 and PSW. All of these registers are being saved on function entry and restored on function exit. When the using() qualifier would have been used the compiler would omit saving/restoring register R0 and R4-R7, but instead code would be generated to switch the register bank (e.g. MOV PSW,#18 for register bank 3).
Because the PUSH and POP instruction require a direct address operand, the assembler uses the predefined symbolic register addresses AR0-AR7 to push and pop the corresponding registers R0-R7.
The relation between the interrupt number and the vector address is: interrupt_id = (vector_address
- 3)/8. In the example above, where the interrupt number is 17, the vector address is 08BH.
You can write your own interrupt handler (and interrupt vector) in assembly. When you don't want the vector to be generated automatically you can use the -v command (or #pragma novector). When you don't want the interrupt frame (saving/restoring registers) to be generated you can use the -vf command. In that case you will have to specify your own interrupt frame. For this you can use the inline capabilities of the compiler. The example below shows an interrupt function for which only DPTR has to be saved and restored.
; 8051 C compiler vx.y rz SNaaaa (c) year TASKING, Inc. ; options: -s -vf $CASE NAME INT ; intrpt.c 1 _inline _using(1) void ; intrpt.c 2 interrupt_prolog( void ) ; intrpt.c 3 { ; intrpt.c 4 #pragma asm ; intrpt.c 5 PUSH DPL ; intrpt.c 6 PUSH DPH ; intrpt.c 7 #pragma endasm ; intrpt.c 8 } ; intrpt.c 9 ; intrpt.c 10 _inline _using(1) void ; intrpt.c 11 interrupt_epilog( void ) ; intrpt.c 12 { ; intrpt.c 13 #pragma asm ; intrpt.c 14 POP DPH ; intrpt.c 15 POP DPL ; intrpt.c 16 #pragma endasm ; intrpt.c 17 } ; intrpt.c 18 ; intrpt.c 19 _bit int1_flag; PUBLIC _int1_flag C51_BI SEGMENT BIT RSEG C51_BI _int1_flag: DBIT 1 ; intrpt.c 20 ; intrpt.c 21 _interrupt(1) _using(1) void ; intrpt.c 22 alarm( void ) ; intrpt.c 23 { PUBLIC _?alarm CSEG AT 0BH JMP _?alarm ; free registers in this function: A B DPTR R0 R1 R2 R3 R4 R5 R6 R7 INT_ALARM_PR SEGMENT CODE RSEG INT_ALARM_PR _?alarm: USING 1 ; intrpt.c 24 interrupt_prolog(); PUSH DPL PUSH DPH ; intrpt.c 25 ; intrpt.c 26 int1_flag = 1; SETB _int1_flag ; intrpt.c 27 ; intrpt.c 28 interrupt_epilog(); POP DPH POP DPL ; intrpt.c 29 } RETI ; intrpt.c 30 EXTRN CODE(SMALL) END
When using assembly in an interrupt function, it might be necessary to save registers not being saved automatically by the compiler. For this you can use #pragma intsave registers.
Example:
#pragma intsave A R0 R1 /* the interrupt function uses registers A, R0 and R1 */ _interrupt(1) void alarm( void ) { #pragma asm MOV A,0C8H XCH A,R1 MOV A,0C9H ADD A,R1 MOV 0C9H,A #pragma endasm
If an interrupt function does not use the fast parameter area, you can instruct the compiler to generate a shorter interrupt frame with #pragma intsave NOPARMregbank. With this pragma the compiler does not generate code to save and restore the present data in the fast parameter area.
Example:
#pragma intsave NOPARM0
With the option -nofastparm you can instruct
the compiler to switch off the use of the fast parameter section for all functions.
In this case you do not need #pragma intsave NOPARMregbank.
With the _frame function qualifier you can specify which registers and SFRs must be saved for a particular interrupt function. Only the specified registers will be pushed and popped from the stack. The syntax is:
Example:
_interrupt(1) _frame(A,R0,R1) void alarm( void ) { /* an interrupt function */ }
For certain ROM monitors it is necessary to specify an offset for
all interrupt vectors. For this you can use the command -ivo=
value or
#pragma VECTOR value. Suppose the previous
example is built for a ROM monitor with the interrupt table at offset 0x4000. When compiling the example with -ivo=0x4000 the vector is being located
at address 0x400B instead of 0xB.
The standard 8051 architecture provides just one 16-bit pointer for indirect addressing of external memory (DPTR). At this moment there are several architectures supporting more than just one data pointer. The Infineon Technologies C500/C800 family has support for 8 16-bit data pointers, the Dallas 80C320/520/530 and AMD 80C521 have support for 2 16-bit data pointers, and also the Philips 51 family has support for 2 16-bit data pointers. Using more than one data pointer is mainly useful when copying bytes from source to destination or when comparing different areas in memory. Most beneficial for multiple data pointer optimization is therefore the C library containing a lot of functions in that area. The table below shows which 8051 C functions benefit from using multiple data pointers.
MODEL | Small | Aux | Large | Reentrant |
strcmp() | x | x | ||
strcpy() | x | x | ||
strncmp() | x | x | ||
strncpy() | x | x | ||
memcmp() | x | x | ||
memcpy() | x | x | ||
memmove() | x | x | ||
xdxdcpy() | x | x | x | x |
xdxdmove() | x | x | x | x |
romxdcpy() | x | x | x | x |
romxdmove() | x | x | x | x |
Table 7-3: Functions that benefit from multiple data pointers
All these C functions have been fully optimized for multiple data pointer support. These optimized functions can be used by linking the multiple data pointer library (mdptr[dps][salr]) before linking the standard C library. That way when using one of the functions the one using multiple data pointers will be linked instead of the standard implementation.
When using multiple data pointers in combination with interrupt functions it is necessary to make sure all data pointers are being saved and restored by the interrupt function. For the Infineon Technologies C500/C800 family use the command -ps, for the Dallas 80C320/520/530, AMD 80C521 use the command -pd and for the Philips 51 family use -pp in order to generate appropriate interrupt frames. The example below shows the interrupt frame for an interrupt function when using dual data pointer support (Dallas 80C320/520/530, AMD 80C521).
; 8051 C compiler vx.y rz SNaaaa (c) year TASKING, Inc. ; options: -s -pd $CASE NAME INTRPT ; intrpt.c 1 _bit int1_flag; PUBLIC _int1_flag C51_BI SEGMENT BIT RSEG C51_BI _int1_flag: DBIT 1 ; intrpt.c 2 ; intrpt.c 3 _interrupt(1) _using(1) void ; intrpt.c 4 alarm( void ) ; intrpt.c 5 { PUBLIC _?alarm CSEG AT 0BH JMP _?alarm ; free registers in this function: A B DPTR R0 R1 R2 R3 R4 R5 R6 R7 INTRPT_ALARM_PR SEGMENT CODE RSEG INTRPT_ALARM_PR _?alarm: USING 1 PUSH ACC PUSH B PUSH DPL PUSH DPH PUSH 084H PUSH 085H PUSH 086H MOV 086H,#00H PUSH PSW MOV PSW,#08H ; intrpt.c 6 int1_flag = 1; SETB _int1_flag ; intrpt.c 7 } POP PSW POP 086H POP 085H POP 084H POP DPH POP DPL POP B POP ACC RETI ; intrpt.c 8 EXTRN CODE(SMALL) END
Assembly language functions can be called from C-51 and vice versa. The names used by cc51 are case sensitive, so you must tell asm51 to act case sensitive too, using the $CASE control. cc51 prepends an underscore for the name of the C variable, to distinguish these names from the 8051 registers. So, any names used or defined in C-51 must have a leading underscore in assembly code. Internal compiler symbols (run-time library) use two underscores.
The assembler uses the following naming convention for C variables and functions:
Name in C | Name used in assembly |
variable | _variable |
_cdecl function() | _function |
function() | _?function |
_regparm function() | _?function |
_regparm function( ... ) /* function with variable argument list */ | _??function |
Table 7-4: Naming convention for variables and functions
When you call an assembly routine that has a name of e.g. 50 characters, you get a link error "UNRESOLVED EXTERNAL". The reason for it is that the C compiler truncates names to 32 characters, but the assembler and linker do not. The solution is, when calling assembly routines, use names of 31 characters or less (if you do not count the leading '_' for a moment). The same rule applies when you call a C function from your assembly code.
The following parameter passing scheme is used:
1. For functions declared _regparm (default), the first non bit arguments are passed via registers; the __PARMx area will not be used by these functions. This register parameter passing scheme is memory model independent.
2. Parameters which do NOT fit in the register passing scheme, are passed the same way as done by _cdecl.
3. For _small, _aux and _large functions having the _cdecl qualifier, the data locations for function parameters are in data fields with the same name as the function itself (also prepended with an underscore), but with _BIT or _BYTE appended to it. An assembly function with parameters must define those data fields in a XDAT or DATA segment, depending on the memory model used with the C modules. Of course, bit parameters must always be defined in a BIT segment.
For _reentrant functions, _cdecl parameter passing is done using the virtual stack.
4. For _aux and _large functions, when using _cdecl new-style prototypes, the compiler tries to pass the first arguments in a static field in data, called __PARMx, where x is the register bank used. To simplify the programming of an assembly routine, prototype the routine with a _cdecl qualifier. Now all parameters will simply be passed using the module_function_BYTE area. However, if all parameters fit in the register parameter passing scheme, _regparm is recommended.
For more information on parameter passing
see section 3.4, Function Parameters in chapter Language Implementation.
The quickest (and most reliable) way to make an assembly language function, which must conform to C-51, is to make the body of this function in C, and compile this module with the memory model used by all other C modules. If the assembly function must return something, specify the return type in the 'assembler function' using C syntax, and let it return something. If parameters are used, force code generation for accessing these parameters with a dummy statement (e.g. an assignment) or declare the parameter as volatile and just access it:
int assem( char volatile a, char c, int i ) { a; return( c + i ); }
Now compile this module, using the correct memory model. The compiler makes the correct frame, and you can edit the generated assembly module, to make the real assembly function inside this frame.
For more information on return types
see section 7.2, Register Usage in this chapter.
A second method to create an interface to assembly is to make use of the feature of the cc51 compiler to have inline assembly.
Assembly lines in the C-source must be introduced by a '#pragma asm', the end is indicated by a '#pragma endasm'. For example:
int assem( char c, int i ) { int j; j = i; #pragma asm MOV P2,#01 #pragma endasm j = c; }
When the assembly does not change any registers, like in the example above, also '#pragma asm_noflush' may be used instead of '#pragma asm'.
For an explanation of the used pragmas
see section 4.4,
Pragmas.
When you use the reentrant model (-Mr option) or some _reentrant functions, a virtual stack mechanism is used. A special stack pointer to this virtual stack is made. Non register function parameters are pushed on the virtual stack and removed after the function call.
During these actions the virtual stack pointer is updated more than once. This operation however needs several instructions. When a program uses interrupts, it is very well possible that an interrupt occurs during the update of the virtual stack pointer. The not yet correct virtual stack pointer will be changed and is thus pointing to an undefined address.
You can use the standard library as delivered with the compiler when the virtual stack is NOT accessed during the interrupt. This is guaranteed if:
- the C interrupt function is _small, _aux or _large
- the C interrupt function is not calling (direct or indirect) a _reentrant function
In all other cases, the update of the virtual stack pointer should prevent interrupts to occur. This can be done by disabling and enabling the interrupts during the update. However, this will slow down the program and increase the interrupt response time. Therefore, the default libraries delivered with the compiler (c51s.lib, c51a.lib, c51m.lib or c51r.lib) do not disable interrupts during a virtual stack pointer update. Special protected libraries are delivered for this purpose (c51sp.lib, c51ap.lib, c51mp.lib or c51rp.lib). So, when linking replace the normal C library with the protected version.
In EDE you can select a protected library by enabling
the options Application uses reentrant functions and Use protection on virtual stack pointer updates in the
Linker | Stack/Heap entry of the Project | Project Options... dialog.
This section explains how to link your C-51 application.
A typical linker command for a C-51 application looks like this:
link51 cstart.obj,your_objects, libraries to output_name options
Note that the file cstart.obj must also be linked. For the small model, a default cstart.obj is delivered. When you use another model, or when you want something specific (e.g. when using 'malloc()', specify a heap), you have to create your own cstart.obj.
You have to make your own cstart.obj when:
Apart from the startup code, you have to specify all your own object files and libraries. The last objects to link are the C library delivered with the C-51 package. For each model a specific library is delivered, you have to choose the one you need (see chapter 6 , Libraries). If you use the reentrant model, see also section 7.10 , Reentrant Model / _reentrant Functions.
When you have linked the wrong C library, you get an unresolved external during the link phase. The external has the name of the model you used in your application. I.e. 'SMALL', 'AUX', you will get such an unresolved external.
When you have used floating point within your application, you have to link the floating point run-time library too. This library must be placed after the C-51 library. So,
link51 cstart.obj,my.obj,float.lib,c51s.lib to my.out
does NOT work (probably unresolved externals will be the result). But:
link51 cstart.obj,my.obj,c51s.lib,float.lib to my.out
is correct.
As an option to the linker, the option 'FUNCTIONOVERLAY' (or 'FO') must be specified, unless the majority of the application consists of PL/M instead of C. With this option you specify to the linker that it should overlay as much local C data as possible, thus saving data space.
If you specify to the linker that it may overlay data, you have to specify all indirect function calls you have used in your application (i.e. all functions which are called by using function pointers). How to do this can be found in the user manual of link51.
This section describes a number of commonly made mistakes and what you can do about them.
Problem: Unresolved externals are found, among the symbols is one (or more) of the names 'SMALL', 'AUX', 'LARGE' or 'REENTRANT'.
Possible causes:
No C library is specified on the linker command line.
Problem: Unresolved externals are found, names end on _BYTE or _BIT.
Possible cause:
Prototypes of functions are not present or do not match with the function definition. These types of errors are detected by the compiler. See also _cdecl and _regparm
function qualifiers (-OR and -Or).
Problem: Unresolved externals are found, names '__HEAPSTART' and '__HEAPEND' are in between.
Cause: No heap space is specified in the C startup code, the heap is needed for 'malloc()', 'calloc()', 'realloc()'.
Problem: Unresolved externals are found, names are found which do not occur in the application.
Cause: Does the application use floating point arithmetic? Maybe the library float.lib is not linked.
Problem: Unresolved externals are found, names '__FLOATSTART' and '__FLOATEND' are in between.
Cause: The application uses floating point, but no floating point stack is specified within the cstart module.
Problem: Unresolved externals are found. A name like '_function' is in between.
Cause: The C-application uses the function named function, but the function itself is not linked or is not programmed as a _cdecl function (see also the -Or option). Check the function prototypes used in the application.
Problem: Unresolved externals are found. A name like '_?function' is in between.
Cause: The C-application uses the function named function, but the function itself is not linked or is not programmed as a _regparm function (see also the -Or option). Check the function prototypes used in the application.
Problem: Unresolved externals are found. A name like '_??function' is in between.
Cause: The C-application uses the function named function, but the function itself is not linked or is not programmed as a _regparm function (see also the -Or option), or the function is not programmed as a variable argument function. Check the function prototypes used in the application.
Problem: Variables and parameters of one procedure are overwritten by another procedure.
Cause: Prototypes of functions do not match their function definition. These types of errors are detected by the compiler.
Problem: Variables and parameters of some function are overwritten.
Cause: In static models, functions may never be recursive. The C-51 compiler cannot check this. Use the 'FUNCTIONOVERLAY' option of the linker together with the 'GRAPH()' option. Link51 checks if recursion is found in the application, reports the recursion in a function call graph and refuses to continue.
Problem: Variables are not '0' during startup of the program.
Cause: In the C language it is defined that 'static' variables, which are not initialized at startup, have the value '0'. In C-51 you have to specify this in the startup code. Another way to get around this problem is to initialize the variables with the value '0'.
Problem: Interrupts are not disabled at startup of the program.
Cause: By default, the C startup code will not clear the enable all (EA) bit. When you define the preprocessor symbol CLR_EA all interrupts are disabled at startup. In EDE you can define this symbol by enabling the option Disable all interrupts at startup in the Processor | Startup Code entry of the Project | Project Options... dialog.