[Back] [Top] [Next]

11 Some C51 Programming Tricks

11.1 Accessing R0 etc. directly from C51

A C51 user was using existing assembler routines to perform a specific task. For historical reasons the 8 bit return value from the assembler was left in R0 of register bank 3. Ordinarily C51 would return chars in R7 and therefore simply equating a variable to the assembler function call would not work.

The solution was to declare an uncommitted memory specific pointer to the DATA area. At run time the absolute address of the register (here 0x18) was assigned to the pointer. The return value was then picked up via the pointer after exiting the assembler section.


/*** Example Of Accessing Specific Registers In C ***/
char data *dptr ;  // Create pointer to DATA location

/* Define Address Of Register */

#define R0_bank3 0x40018L   /* Address of R0 in */
                            /* bank 3, 4 => DATA space */
char x,y ;

/* Execute */

main() {
dptr = (char*) R0_bank3 ;  // Point at R0, bank3

x = 10 ; 
dptr[0] = x ;   // Write x into R0, bank3 
y = *dptr ;     // Get value of R0, bank3

}

An alternative might have been to declare a variable to hold the return value in a separate module and to use the linker to fix that module's DATA segment address at 0x18. This method is more robust and code efficient but is considerably less flexible.

11.2 Making Use Of Unused Interrupt Sources

One problem with the 8051 is the lack of a TRAP or software interrupt instruction. While C166 users have the luxury of real hardware support for such things, 8051 programmers have to be more cunning.

A situation arose recently where the highest priority interrupt function in a system had to run until a certain point, from which lesser interrupts could then come in. Unfortunately, changing the interrupt priority registers part way through the interrupt function did not work, the lesser interrupts simply waiting until the RETI. The solution was to hijack the unused A/D converter interrupt, IADC, and attach the second section of the interrupt function to it. Then by deliberately setting the IADC pending flag just before the closing "}", the second section could be made to run immediately afterwards. As the priority of the ADC interrupt had been set to a low level, it was interruptable.


/* Primary Interrupt Attached In CC0 Input Capture */

tdc_int() interrupt 8 {

/* High priority section - may not be interrupted */


/* Enable lower priority section attached to */
                            /* ADC interrupt */

IADC = 1 ; // Force ADCinterrupt 
EADC = 1 ; // Enable ADC interrupt 
}

/* Lower priority section attached to ADC interrupt */

tdc_int_low_priority() interrupt 10

IADC = 0 ; // Prevent further calls 
EADC = 0 ;

/* Low priority section which must be interruptable and */
    /* guaranteed to follow high priority section above */

}

11.3 Code Memory Device Switching

This dodge was used during the development of a HEX file loader for a simple 8051 monitor. After receiving a hexfile into a RAM via the serial port, the new file was to be executed in RAM starting from 0000H. A complication was that the memory map had to be switched immediately prior to hitting 0000H.

The solution was to place the map switching section at 0xfffd so that the next instruction would be fetched from 0x0000, thus simulating a reset. Ideally all registers and flags should be cleared before this.


"reg.h" 
#include "cemb537.h" 
#include  <stdio.h>

   main()
      {

      unsigned char tx_char,rx_char,i ;

      P4 = map2 ;
#include
      v24ini_537() ;

      timer0_init_537() ;

      hexload_ini() ;

      EAL = 1 ;

      while(download_completed == 0)
         {

         while(char_received_fl == 0)
            { receive_byte() ; }

         tx_byte = rx_byte ; /* Echo */
         hexload() ;
         send_byte(tx_byte) ;

         char_received_fl = 0 ;
         } 

      real_time_count = 0 ;
      while(real_time_count < 200) 
         { ; }

      i = ((unsigned char (code*)(void)) 0xFFFD) () ;  
                       // Jump to absolute address.

      }

//^^^^^^^^^^^^^^^^^^^^^^^ End of Module


;
   NAME SWITCH
; 
; Cause PC to roll-over at FFFFH to simulate reset 
;
   P4      DATA 0E8H
;
   CSEG AT 0FFFDH
;
   MOV  P4,#02Fh  ; 
;
   END

//^^^^^^^^^^^^^^^^^^^^^^^ End of Module "MAPCON"

There are other ways of doing this. For instance the code for the MAPCON module could be located at link time thus: CODE(SWITCH(0FFFDH)), so dispensing with the "CSEG AT".

11.4 Simulating A Software Reset

In a similar vein to the above, the 8051 does not possess a software reset instruction, unlike the 80C166 etc.. This method uses abstract pointers to create a call to address zero, thus simulating a software reset.

However it should be remembered that all internal locations must be cleared before the CPU can be considered properly reset! The return address must be reset as the stack still contains the return address from the call.

; 
; 
; void main(void) {

    RSEG  ?PR?main?T1
    USING    0
main:
            ; SOURCE LINE # 9
; 
; ((void (code*) (void)) 0x0000) () ;
            ; SOURCE LINE # 11
    LCALL    00H       ; Jump to address ZERO!
; 
; }
            ; SOURCE LINE # 13
    RET      
; END OF main

11.5 The Compiler Preprocessor - #define

This is really just a text replacement device.

It can be used to improve program readability by giving constants meaningful names, for example:

    #define fuel_constant 100 * 2

so that the statement temp = fuel_constant will assign the value 200 to temp.

Note that the preprocessor only allows integer calculations.

Other more sophisticated examples are given in the C51 manual, pages 4-2.


[Back] [Top] [Next]