Back to ESAcademy Home Page


www.philipsmcu.comUsing pointers, arrays, structures and unions in 8051 C compilers

by Olaf Pfeiffer, ESAcademy
based on the "C51Primer", by Mike Beach, Hitex UK

Pointers and Arrays | Structures and Unions | Generic and Spaced Pointers ]

 

Home

News

Training Classes

Products

Consulting

Technical Library

Contact Us

Recommended Books

Structures

Structures are perhaps what makes C such a powerful language for creating very complex programs with huge amounts of data. They are basically a way of grouping together related data items under a single symbolic name.

Why Use Structures?

Here is an example: A piece of C51 software had to perform a linearization process on the raw signal from a variety of pressure sensors manufactured by the same company. For each sensor to be catered for there is an input signal with a span and offset, a temperature coefficient, the signal conditioning amplifier, a gain and offset. The information for each sensor type could be held in "normal" constants thus:

unsigned char sensor_type1_gain = 0x30 ;
unsigned char sensor_type1_offset = 0x50 ;
unsigned char sensor_type1_temp_coeff = 0x60 ;
unsigned char sensor_type1_span = 0xC4 ;
unsigned char sensor_type1_amp_gain = 0x21 ;

unsigned char sensor_type2_gain = 0x32 ;
unsigned char sensor_type2_offset = 0x56 ;
unsigned char sensor_type2_temp_coeff = 0x56 ;
unsigned char sensor_type2_span = 0xC5 ;
unsigned char sensor_type2_amp_gain = 0x28 ;
unsigned char sensor_type3_gain = 0x20 ;
unsigned char sensor_type3_offset = 0x43 ;
unsigned char sensor_type3_temp_coeff = 0x61 ;
unsigned char sensor_type3_span = 0x89 ;
unsigned char sensor_type3_amp_gain = 0x29 ;

As can be seen, the names conform to an easily identifiable pattern of:

unsigned char sensor_typeN_gain = 0x20 ;
unsigned char sensor_typeN_offset = 0x43 ;
unsigned char sensor_typeN_temp_coeff = 0x61 ;
unsigned char sensor_typeN_span = 0x89 ;
unsigned char sensor_typeN_amp_gain = 0x29 ;

Where 'N' is the number of the sensor type. A structure is a neat way of condensing this type of related and repeating data. In fact the information needed to describe a sensor can be reduced to a generalized:

unsigned char gain ;
unsigned char offset ;
unsigned char temp_coeff ;
unsigned char span ;
unsigned char amp_gain ;

The concept of a structure is based on this idea of generalized "template" for related data. In this case, a structure template (or "component list") describing any of the manufacturer's sensors would be declared:

struct SENSOR_DESC
{

unsigned char gain ;
unsigned char offset ;
unsigned char temp_coeff ;
unsigned char span ;
unsigned char amp_gain ;

} ;

This does not physically do anything to memory. At this stage it merely creates a template which can now be used to put real data into memory.

This is achieved by:

struct SENSOR_DESC sensor_database ;

This reads as "use the template SENSOR_DESC to layout an area of memory named sensor_database, reflecting the mix of data types stated in the template". Thus a group of 5 unsigned chars will be created in the form of a structure.

The individual elements of the structure can now be accessed as:

sensor_database.gain = 0x30 ;
sensor_database.offset = 0x50 ;
sensor_database.temp_coeff = 0x60 ;
sensor_database.span = 0xC4 ;
sensor_database.amp_gain = 0x21 ;

Arrays Of Structures

In the example though, information on many sensors is required and, as with individual chars and ints, it is possible to declare an array of structures. This allows many similar groups of data to have different sets of values.

struct SENSOR_DESC sensor_database[4] ;

This creates four identical structures in memory, each with an internal layout determined by the structure template. Accessing this array is performed simply by appending an array index to the structure name:

/*Operate On Elements In First Structure Describing */
/*Sensor 0 */

sensor_database[0].gain = 0x30 ;
sensor_database[0].offset = 0x50 ;
sensor_database[0].temp_coeff = 0x60 ;
sensor_database[0].span = 0xC4 ;
sensor_database[0].amp_gain = 0x21 ;

/* Operate On Elements In First Structure Describing */
/*Sensor 1 */

sensor_database[1].gain = 0x32 ;
sensor_database[1].offset = 0x56 ;
sensor_database[1].temp_coeff = 0x56 ;
sensor_database[1].span = 0xC5 ;
sensor_database[1].amp_gain = 0x28 ;

and so on...

Initialized Structures

As with arrays, a structure can be initialized at declaration time

struct SENSOR_DESC sensor_database =
{ 0x30, 0x50, 0x60, 0xC4, 0x21 } ;

so that here the structure is created in memory and pre-loaded with values.
The array case follows a similar form:

struct SENSOR_DESC sensor_database[4] =
{
{0x20,0x40,0x50,0xA4,0x21},
{0x33,0x52,0x65,0xB4,0x2F},
{0x30,0x50,0x48,0xC4,0x3A},
{0x32,0x56,0x56,0xC5,0x28}
} ;

Placing Structures At Absolute Addresses

It is sometimes necessary to place a structure at an absolute address. A typical example are CAN interfaces or other peripheral chips that offer arrays of data groups.

For example, the registers of a memory-mapped real time clock chip are to be grouped together as a structure. The template in this instance might be

// Contents Of RTCBYTES.C Module

struct RTC
{

unsigned char seconds ;
unsigned char minutes ;
unsigned char hours ;
unsigned char days ;

} ;

struct RTC xdata RTC_chip ; // Create xdata structure

A trick using the linker is required here so the structure creation must be placed in a dedicated module. This module's XDATA segment, containing the RTC structure, is then fixed at the required address at link time.

Using the absolute structure could be:

/* Structure located at base of RTC Chip */

MAIN.C Module

extern xdata struct RTC_chip ;

/* Other XDATA Objects */

xdata unsigned char time_secs, time_mins ;

void main(void)
{

time_secs = RTC_chip.seconds ;
time_mins = RTC_chip.minutes;

}

Linker Input File To Locate RTC_chip structure over real RTC Registers is:

l51 main.obj,rtcbytes.obj XDATA(?XD?RTCBYTES(0h))

Pointers To Structures

Pointers can be used to access structures, just as with simple data items. Here is an example:

/* Define pointer to structure */
struct SENSOR_DESC *sensor_database ;

/* Use Pointer To Access Structure Elements */
sensor_database->gain = 0x30 ;
sensor_database->offset = 0x50 ;
sensor_database->temp_coeff = 0x60 ;
sensor_database->span = 0xC4 ;
sensor_database->amp_gain = 0x21 ;

Note that the '*' which normally indicates a pointer has been replaced by appending '->' to the pointer name. Thus '*name' and 'name->' are equivalent.

Passing Structure Pointers To Functions

A common use for structure pointers is to allow them to be passed to functions without huge amounts of parameter passing; a typical structure might contain 20 data bytes and to pass this to a function would require 20 parameters to either be pushed onto the stack or an abnormally large parameter passing area. By using a pointer to the structure, only the two or three bytes that constitute the pointer need be passed. This approach is recommended for C51 as the overhead of passing whole structures can tie the poor old 8051 CPU in knots!

This would be achieved by:

struct SENSOR_DESC *sensor_database ;

sensor_database->gain = 0x30 ;
sensor_database->offset = 0x50 ;
sensor_database->temp_coeff = 0x60 ;
sensor_database->span = 0xC4 ;
sensor_database->amp_gain = 0x21 ;

test_function(*struct_pointer) ;

test_function(struct SENSOR_DESC *received_struct_pointer)
{

// Write directly into the structure
received_struct_pointer->gain = 0x20 ;
received_struct_pointer->temp_coef = 0x40 ;

}

Advanced Note: Using a structure pointer will cause the called function to operate directly on the structure rather than on a copy made during the parameter passing process.

Structure Pointers To Absolute Addresses

It is sometimes necessary to place a structure at an absolute address. This might occur if, for example, a memory-mapped real time clock chip is to be handled as a structure. An alternative approach to that given earlier is to address the clock chip via a structure pointer.

The important difference is that in this case no memory is reserved for the structure - only an "image" of it appears to be at the address.

The template in this instance might be:



/* Define Real Time Clock Structure */
struct RTC 
{
    char seconds ;
    char mins ;
    char hours ;
    char days ; 
} ;
             
/* Create A Pointer To Structure */
struct RTC xdata *rtc_ptr ;  // 'xdata' tells C51 that this 
                             //is a memory-mapped device.


void main(void) 
{
    rtc_ptr = (void xdata *) 0x8000 ;  // Move structure 
                            // pointer to address of real-time
                            // clock at 0x8000 in xdata

    rtc_ptr->seconds = 0 ;  // Operate on elements
    rtc_ptr->mins = 0x01 ;
}
    

This general technique can be used in any situation where a pointer-addressed structure needs to be placed over a specific IO device. However it is the user's responsibility to make sure that the address given is not likely to be allocated by the linker as general variable RAM!

To summarize, the procedure is:

  1. Define template
  2. Declare structure pointer as normal
  3. At run time, force pointer to required absolute address in the normal way.

Unions

Unions allow you to define different datatype references for the same physical address. This way you can address a 32-bit word as a "long" OR as 2 different "ints" OR as an array of 4 bytes.

A union is similar in concept to a structure except that rather than creating sequential locations to represent each of the items in the template, it places each item at the same address. A union specifying 4 bytes may still only occupy a single byte. A union may consist of a combination of longs, char and ints all based at the same physical address.

The the number of bytes of RAM used by a union is simply determined by the size of the largest element, so:

union test
{

char x ;
int y ;
char a[3] ;
long z ;

} ;

requires 4 bytes, this being the size of a long. The physical location of each element is the base address plus the following offsets:

Offset x y a z
+0 byte high byte a[0] highest byte
+1   low byte a[1] mid byte
+2     a[2] mid byte
+3     a[3] lowest byte

In embedded C the commonest use of a union is to allow fast access to individual bytes of longs or ints. These might be 16 or 32 bit real time counters, as in this example:



/* Declare Union */
union clock 
{
    long real_time_count ;     // Reserve four byte
    int real_time_words[2] ;   // Reserve four bytes as 
                               // int array
    char real_time_bytes[4] ;  // Reserve four bytes as
                               // char array
} ;

/* Real Time Interrupt */
void timer0_int(void) interrupt 1 using 1 
{
    clock.real_time_count++ ;       // Increment clock
   
    if(clock.real_time_words[1] == 0x8000) 
    {    // Check/compare lower word only

    /* Do something! */
    }

    if(clock.real_time_bytes[3] == 0x80)
    {    // Check/compare most significant byte only
  
    /* Do something! */
    }
      
}
   

Pointers and Arrays | Structures and Unions | Generic and Spaced Pointers ]

ESAcademy, 2000

All materials
provided 'as is'
see Disclaimer

www.esacademy.com
info@esacademy.com