7 RUN-TIME ENVIRONMENT

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

7.1 Startup Code

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:

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.

7.2 Register Usage

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

7.3 Segment Usage

cc51 uses a large number of segments. This section contains a list of all possible segment names of a complete C application:

BIT

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

DATA

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

IDATA

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

CODE

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)

XDATA

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.

7.4 Stack

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.

7.5 Heap

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.

7.6 Floating Point

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.

7.7 Interrupt Functions

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.

Example:

; 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.

Example:

; 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

Pragma intsave

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:

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:

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.

_frame function qualifier

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:

Pragma vector

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.

7.8 Multiple Data Pointer Support

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

7.9 Assembly Language Interfacing

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:

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:

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.

7.10 Reentrant Model / _reentrant Functions

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.

7.11 Linking an Application

This section explains how to link your C-51 application.

A typical linker command for a C-51 application looks like this:

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,

does NOT work (probably unresolved externals will be the result). But:

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.

7.12 Troubleshooting

This section describes a number of commonly made mistakes and what you can do about them.

7.12.1 Linking Problems

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.

7.12.2 Run-time Problems

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.


Copyright © 2002 Altium BV