[Back] [Top] [Next]

10 Miscellaneous Points

10.1 Tying The C Program To The Restart Vector

This is achieved by the assembler file STARTUP.A51. This program simply places a LJMP STARTUP at location C0000 (Lowest EPROM location

The startup routine just clears the internal RAM and sets up the stack pointer. Finally it executes a LJMP to "main", (hopefully) the first function in the C program.


    LJMP main
        .
        .
        .
        .
        main()
        {
        }

In fact this need be the only assembler present in a C51 program.

10.2 Intrinsic Functions

There are a number of special 8051 assembler instructions which are not normally used by C51. For the sake of speed it is sometimes useful to get direct access to these.

Unlike the normal C51 '>>' functions, _cror_ allows direct usage of an 8051 instruction set feature, in this case the "RR A" (rotate accumulator). This yields a much faster result than would be obtained by writing one using bits and the normal >> operator. There are also _iror_ and _lror_ intrinsic functions for integer and long data as well.

The _nop_ function simply adds an in-line NOP instruction to generate a short and predictable time delay. Another function, _testbit_, makes use of the JBC instruction to allow a bit to be tested, a branch taken and the bit cleared if set. The only extra step necessary is to include "intrins.h" in the C51 source file.

Here is an example of how the _testbit_() intrinsic function is used to save a CLR instruction:


; #include <intrins.h>
; 
; 
; unsigned int shift_reg = 0 ; 
; 
; bit test_flag ; 
; 
; void main(void) {
    RSEG  ?PR?main?T
    USING    0
main:
            ; SOURCE LINE # 12
; 
; /* Use Normal Approach */ 
; 
;    test_flag = 1 ;
            ; SOURCE LINE # 14
    SETB     test_flag
; 
;    if(test_flag == 1) {
            ; SOURCE LINE # 16
    JNB      test_flag,?C0001
;       test_flag = 0 ;
            ; SOURCE LINE # 17
    CLR      test_flag
;       P1 = 0xff     ;
            ; SOURCE LINE # 18
    MOV      P1,#0FFH
;       }
            ; SOURCE LINE # 19
?C0001: 
; 
; /* Use Intrinsic Function */ 
; 
;    test_flag = 1 ;
            ; SOURCE LINE # 21
    SETB     test_flag
; 
;   if(!_testbit_(test_flag)) {
            ; SOURCE LINE # 23
    JBC      test_flag,?C0003
;       P1 = 0xff     ;
            ; SOURCE LINE # 24
    MOV      P1,#0FFH
;       }
            ; SOURCE LINE # 25
; 
;    }
            ; SOURCE LINE # 27
?C0003:
    RET      
; END OF main
    END

See pages 9-17 in the C51 Manual

10.3 EA Bit Control #pragma

Whilst the interrupt modifier for function declarations remains unchanged a new directive, DISABLE, allows interrupts to be disabled for the duration of a function. Note that this can be individually applied to separate functions within a module but is given as a #pragma rather than as part of the function declaration. Although not verified yet, DISABLE gives the user some control over the EA or EAL bit.

10.4 16 Bit sfr Support

Another new feature is the 16bit sfr type. Within expanded 8051 variants in particular, many 16 bit timer and capture registers exist. Rather than having to load the upper and lower bytes individually with separate C statements, the sfr16 type is provided. The actual address declared for a 16 bit sfr in the header file is always the low byte of the sfr. Now to load a 16 bit sfr from C, only a single int load is required. Be warned - 8-bit instructions are still used, so the 16 bit load/read is not indivisible - odd things can happen if you load a timer and it overflows during the process! Note that usually only timer 2 or above has the high/low bytes arranged sequentially.

10.5 Function Level Optimisation

Optimisation levels of 4 and above are essentially function optimisations and, as such, the whole function must be held in PC memory for processing. If there is insufficient memory for this, a warning is issued and the additional optimisation abandoned. Code execution will still be correct however. See p1-8 in the C51 manual.

10.6 In-Line Functions In C51

One of the fundamentals of C is that code with a well-defined input, output and job is placed into a function i.e. a subroutine. This involves placing parameters into a passing area, whether a stack or a register, and then executing a CALL. It is unavoidable that the call instruction will use two bytes of stack.

In most 8051 applications this not a problem, as there is generally 256 on-chip RAM potentially available as stack. Even after allowing for a few registerbanks, there is normally sufficient stack space for deeply nested functions.

However in the case of the 8031 and reduced devices such as the 87C751, every byte of RAM is critical. In the latter case there are only 64 bytes!

A trick which can both save stack and reduce run time is to use macros with parameters to act like "in-line" functions. The ability to create macros with replaceable parameters is not commonly used but on limited RAM variants it can be very useful.

Here a strcpy() function created as a macro named "Inline_Strcpy", whilst it looks like a normal function, it does not actually have any fixed addresses or local data of its own. The '\' characters serve to allow the macro definition to continue to a new line, in this case to preserve the function-like appearance.

It is "called" like a normal function with the parameters to be passed enclosed in ( ). However no CALL is used and the necessary code is created in-line. The end result is that a strcpy is performed but no new RAM or stack is required.

Please note however, the drawback with this very simple example is that the source and destination pointers are modified by the copying process and so is rather suspect!

A further benefit in this example is that the notional pointers s1 and s2 are automatically memory-specific and thus very efficient. Thus in situations where the same function must operate on pointer data in a variety of memory spaces, slow generic pointers are not required.


#define Inline_Strcpy(s1,s2)  {\ while((*s1 = *s2) != 0)}\ 
                     {\*s1++ ; *s2++; }\
                               }
char xdata *out_buffx = { "                           " } ;
char xdata *in_buffx = { "Hello" } ; 
char idata *in_buffi = { "Hello" } ;
char idata *out_buffi = { "                           " }  ;  
char code *in_buffc = { "Hello" } ;

void main(void) {

   Inline_Strcpy(out_buffx,in_buffx)  // In line functions
   Inline_Strcpy(out_buffi,in_buffi)
   Inline_Strcpy(out_buffx,in_buffc)
   }

Another good example of how a macro with parameters can be used to aid source readability is in the optimisation feature in Appendix D. The interpolation calculation that originally formed a subroutine could easily be redefined as a macro with 5 parameters, realising a ram and run time saving at the expense of code size.

Note that 'r', the fifth parameter, represents the return value which has to be "passed" to the macro so that it has somewhere to put the result!


#define interp_sub(x,y,n,d,r)  y -= x ; \ 
if(!CY) { r = (unsigned char) (x +(unsigned char)(((unsigned 
            int)(n * y))/d)) ;\ 

} else { r = (unsigned char) (x - (unsigned char)(((unsigned int)(n * -y))/d)) ; }  
This is then called by:

/*Interpolate 2D Map Values */
/*Macro With Parameters Used*/

interp_sub(map_x1y1,map_x2y1,x_temp1,x_temp2,result_y1) 

and later it is reused with different parameters thus:

interp_sub(map_x1y2,map_x2y2,x_temp1,x_temp2,result_y2) 

To summarise, parameter macros are a good way of telling C51 about a generalised series of operations whose memory spaces or input values change in programs where speed or RAM usage is critical.


[Back] [Top] [Next]