[Back] [Top] [Next]

2 Keil C51 Compiler Basics - The 8051 Architecture

The Keil C51 compiler has been written to allow C programmers to get code running quickly on 8051 systems with little or no learning curve. However, to get the best from it, some appreciation of the underlying hardware is desirable. The most basic decision to be made is which memory model to use.

For general information on the C language, number and string representation, please refer to a standard C textbook such as K & R

2.1 8051 Memory Configurations

2.1.1 Physical Location Of The Memory Spaces

Perhaps the most initially confusing thing about the 8051 is that there are three different memory spaces, all of which start at the same address.

Other microcontrollers, such as the 68HC11, have a single Von Neuman memory configuration, where memory areas are located at sequential addresses, regardless of in what device they physically exist.

Within the CPU there is one such, the DATA on-chip RAM. This starts at D:00 (the 'D:' prefix implies DATA segment) and ends at 07fH (127 decimal). This RAM can be used for program variables. It is directly addressable, so that instructions like 'MOV A,x' are usable. Above 80H the special function registers are located, which are again directly addressable. However, a second memory area exists between 80H and 0FFH which is only indirectly addressable and is prefixed by I: and known as IDATA. It is only accessible via indirect addressing (MOV A,@Ri) and effectively overlays the directly addressable sfr area. This constitutes an extended on-chip RAM area and was added to the ordinary 8051 design when the 8052 appeared. As it is only indirectly addressable, it is best left for stack use, which is, by definition, always indirectly addressed via the stack pointer SP. Just to confuse things, the normal directly addressable RAM from 0-80H can also be indirectly addressed by the MOV A,@Ri instruction!


Fig.1. - The 8051's Memory Spaces.

A third memory space, the CODE segment, also starts at zero, but this is reserved for the program. It typically runs from C:0000 to C:0FFFFH (65536 bytes) but as it is held within an external EPROM, it can be any size up to 64KB (65536 bytes). The CODE segment is accessed via the program counter (PC) for opcode fetches and by DPTR for data. Obviously, being ROM, only constants can be stored here.

A fourth memory area is also off-chip, starting at X:0000. This exists in an external RAM device and, like the C:0000 segment, can extend up to X:0FFFFH (65536 bytes). The 'X:' prefix implies the external XDATA segment. The 8051's only 16 bit register, the DPTR (data pointer) is used to access the XDATA. Finally, 256 bytes of XDATA can also be addressed in a paged mode. Here an 8 bit register (R0) is used to access this area, termed PDATA.

The obvious question is: "How does the 8051 prevent an access to C:0000 resulting in data being fetched from D:00?"

The answer is in the 8051 hardware: When the cpu intends to access D:00, the on-chip RAM is enabled by a purely internal READ signal - the external /RD pin is unchanged.

    MOV   A,40      ; Put value held in location 40 into the accumulator.
                      This addressing mode (direct) is the basis of the
                      SMALL memory model. 
    MOV   R0,#0A0H  ; Put the value held in IDATA location 0A0H into
    MOV   A,@R0     ; the accumulator 

This addressing mode is used to access the indirectly addressable on-chip memory above 80H and as an alternative way to get at the direct memory below this address.

A variation on DATA is BDATA (bit data). This is a 16 byte (128 bit) area, starting at 020H in the direct segment. It is useful in that it can be both accessed byte-wise by the normal MOV instructions and addressed by special bit-orientated intructions, as shown below:

    SETB  20.0  ;
    CLRB  20.0  ;

The external EPROM device (C:0000) is not enabled during RAM access. In fact, the external EPROM is only enabled when a pin on the 8051 named the PSEN (program store enable) is pulled low. The name indicates that the main function of the EPROM is to hold the program.

The XDATA RAM and CODE EPROM do not clash as the XDATA device is only active during a request from the 8051 pins named READ or WRITE, whereas the CODE device only responds when the PSEN pin is low.

To help access the external XDATA RAM, special instructions exist, conveniently containing an 'X'....

MOV   DPTR,#08000H
MOVX  A,@DPTR       ; "Put a value in A located at address in the
                       external RAM, contained in the DPTR register (8000H)".

The above addressing mode forms the basis of the LARGE model.

    MOVX  R0,#080H  ;  
    MOVX  A,@R0     ;

This alternative access mode to external RAM forms the basis of the COMPACT memory model. Note that if Port 2 is attached to the upper address lines of the RAM, it can act like a manually operated "paging" control.

The important point to remember is that the PSEN pin is active when instructions are being fetched; READ and WRITE are active when MOVX.... ("move external") instructions are being carried-out.

Note that the 'X' means that the address is not within the 8051 but is contained in an external device, enabled by the READ and WRITE pins.

2.1.2 Possible Memory Models

With a microcontroller like the 8051, the first decision is which memory model to use. Whereas the PC programmer chooses between TINY, SMALL, MEDIUM, COMPACT, LARGE and HUGE to control how the processor segmentation of the RAM is to be used (overcome!), the 8051 user has to decide where the program and data are to reside.

C51 currently supports the following memory configurations:

  1. ROM: currently the largest single object file that can be produced is 64K, although up to 1MB can be supported with the BANKED model described below. All compiler output to be directed to Eprom/ROM, constants, look-up tables etc., should be declared as "code".
  2. RAM: There are three memory models, SMALL, COMPACT and LARGE
  3. SMALL: all variables and parameter-passing segments will be placed in the 8051's internal memory.
  4. COMPACT: variables are stored in paged memory addressed by ports 0 and 2. Indirect addressing opcodes are used. On-chip registers are still used for locals and parameters.
  5. LARGE: variables etc. are placed in external memory addressed by @DPTR. On-chip registers are still used for locals and parameters.
  6. BANKED: Code can occupy up to 1MB by using either CPU port pins or memory-mapped latches to page memory above 0xFFFF. Within each 64KB memory block a COMMON area must be set aside for C library code. Inter-bank function calls are possible.

See the section on BL51 for more information on the BANKED model.

A variation on these models is to use one model globally and then to force certain variables and data objects into other memory spaces.

This technique is covered later.

2.1.3 Choosing The Best Memory Configuration/Model

With the four memory models, a decision has to be made as to which one to use. Single chip 8051 users may only use the SMALL model, unless they have an external RAM fitted which can be page addressed from Port 0 and optionally, Port 2, using MOVX A,@R0 addressing.

This permits the COMPACT model. While it is possible to change the global memory model half way through a project, it is not recommended!

SMALL: Total RAM 128 bytes (8051/31)

Rather restricting in the case of 8051/31. Will support code sizes up to about 4K but a constant check must be kept on stack usage. The number of global variables must be kept to a minimum to allow the linker OVERLAYer to work to best effect. With 8052/32 versions, the manual use of the 128 byte IDATA area above 80H can allow applications up to about 10-12K but again the stack position must be kept in mind.

Very large programs can be supported by the SMALL model by manually forcing large and/or slow data objects in to an external RAM, if fitted. Also variables which need to be viewed in real time are best located here, as dual-ported emulators like the Hitex T51 can read their values on the fly. This approach is generally best for large, time-critical applications, as the SMALL global model guarantees that local variables and function parameters will have the fastest access, while large arrays can be located off-chip.

COMPACT: Total RAM 256 bytes off-chip, 128 or 256 bytes on-chip.

Suitable for programs where, for example, the on-chip memory is applied to an operating system. The compact model is rarely used on its own but more usually in combination with the SMALL switch reserved for interrupt routines.

COMPACT is especially useful for programs with a large number of medium speed 8 bit variables, for which the MOVX A,@R0 is very suitable.

It can be useful in applications where stack usage is very high, meaning that data needs to be off-chip. Note that register variables are still used, so the loss of speed will not be significant in situations where only a small number of local variables and/or passed parameters are used.

LARGE: Total RAM up to 64KB, 128 or 256 bytes on-chip.

Permits slow access to a very large memory space and is perhaps the easiest model to use. Again, not often used on its own but in combination with SMALL. As with COMPACT, register variables are still used and so efficiency remains reasonable.

In summary, there are five memory spaces available for data storage, each of which has particular pros and cons.

Here are some recommendations for the best use of each:

DATA: 128 bytes; SMALL model default location

Best For:

Frequently accessed data requiring the fastest access. Interrupt routines whose run time is critical should use DATA, usually by declaring the function as "SMALL". Also, background code that is frequently run and has many parameters to pass. If you are using re-entrant functions, the re-entrant stacks should be located here as a priority.

Worst For:

Any variable arrays and structures of more than a few bytes.

IDATA; Not model-dependant

Best For:

Fast access data arrays and structures of limited size (up to around 32 bytes each) but not totalling more than 64 or so bytes. As these data types require indirect addressing, they are ideally placed in the indirectly addressable area. It is also a good place to locate the stack, as this is by definition indirectly addressed.

Worst For:

Large data arrays, fast access words.

CODE: 64K bytes

Best For:

Constants and large lookup tables, plus opcodes, of course!

Worst For:

Variables!

PDATA: 256bytes; COMPACT model default area

Best For:

Medium speed interrupt and fast background char (8 bit) variables and moderate-sized arrays and structures. Also good for variables which need to be viewed in real time using an emulator.

Worst For:

Very large data arrays and structure above 256 bytes.

Very frequently used data (in interrupts etc..).

Integer and long data.

XDATA; LARGE model default area

Best For:

Large variable arrays and structures (over 256 bytes)

Slow or infrequently-used background variables. Also good for variables which need to be viewed in real time using an emulator.

Worst For:

Frequently-accessed or fast interrupt variables.

2.1.4 Setting The Memory Model - #Pragma Usage

The overall memory type is selected by including the line #pragma SMALL as the first line in the C source file.

See Section 2.1.3 for details on specific variable placement. SMALL is the default model and can be used for quite large programs, provided that full use is made of PDATA and XDATA memory spaces for less time-critical data.

Special note on COMPACT model usage

The COMPACT model makes certain assumptions about the state of Port 2. The XDATA space is addressed by the DPTR instructions which place the 16 bit address on Ports 0 and 2. The COMPACT model uses R0 as a 8 bit pointer which places an address on port 0. Port 2 is under user control and is effectively a memory page control. The compiler has no information about Port 2 and unless the user has explicitly set it to a value it will be undefined, although generally it will be at 0xff. The linker has the job of combining XDATA and PDATA variables and unless told otherwise it puts the PDATA (COMPACT default space) at zero. Hence, the resulting COMPACT program will not work.

It is therefore essential to set the PPAGE number in the startup.a51 file to some definite value - zero is a good choice. The PPAGEENABLE must be set to 1 to enable paged mode. Also, when linking, the PDATA(ADDR) control must be used to tell L51 where the PDATA area is, thus:

L51 module1.obj, module2.obj to exec.abs PDATA(0)XDATA(100H)

Note that the normal XDATA area now starts at 0x100, above the zero page used for PDATA. Failure to do this properly can result in very dangerous results, as data placement is at the whim of PORT2!

2.2 Local Memory Model Specification

2.2.1 Overview

C51 version 3.20 allows memory models to be assigned to individual functions. Within a single module, functions can be declared as SMALL, COMPACT or LARGE thus

#pragma COMPACT 
/* A SMALL Model Function */ 
fsmall() small { 
   printf("HELLO") ;
   } 
/* A LARGE Model Function */ 
flarge() large { 
   printf("HELLO") ;
   } 
/* Caller */ 
main() { 
   fsmall() ;  // Call small func.
   flarge() ;  // Call large func. 
   } 

See pages 5-20 in the C51 reference manual for further details.

2.2.2 A Point To Watch In Multi-Model Programs

A typical C51 program might be arranged with all background loop functions compiled as COMPACT, whilst all (fast) interrupt functions treated as SMALL. The obvious approach of using the #pragma MODEL or command line option to set the model can cause odd side effects. The problem usually manifests itself at link time as a MULTIPLE PUBLIC DEFINITION error related to, for instance, putchar().

The cause is that in modules compiled as COMPACT, C51 creates references to library functions in the COMPACT library, whilst the SMALL modules will access the the SMALL library. When linking, L51 finds that it has two putchars() etc. from two different libraries.

The solution is to stick to one global memory model and then use the SMALL function attribute, covered in the previous section, to set the memory model locally.

Example:

    #pragma COMPACT
    void fast_func(void) SMALL{
    /*code*/
    }


[Back] [Top] [Next]