SHDesigns: Embedded Systems Design, Consulting and Developer Resources Page hits:

SHDesigns Rabbit Programming Tips for Dynamic C

Using Arrays and Structures in XMEM with Dynamic C

Since DC does not support direct access to xmem arrays, implementing an array of data in xmem is a bit tricky. There is no way to define a large array in DC because DC does not support far pointers (SofTools does.) We need to write the same functionality with a bit more code. This tech-tip should make it easier. Many of DC samples use xmem2root() and root2xmem() to access xmem data. This works, but the code can be hard to read.

These samples show how to access arrays of built-in data types or structures in a simpler manner. Rather than writing code to calculate an address and hand-coding offsets, use the compiler to help you calculate offset addresses and use the xset/xget() functions to write/read arrays of built-in types or structure members.

Accessing arrays of char, int, long etc.

First allocate a buffer of the sizeof(type)*NUM_ENTRIES. As an example we will always use NUM_ENTRIES=100. Here is how to allocate the xmem buffer:

     // 4 xmem array pointers to, char, int, long and float data
unsigned long char_array,int_array,long_array,float_array; // xmem "pointers" are long ints

   char_array=xalloc(NUM_ENTRIES * sizeof(char)); // char array
    // (can use just NUM_ENTRIES for char arrays)
   int_array=xalloc(NUM_ENTRIES * sizeof(int));  // int array
   long_array=xalloc(NUM_ENTRIES * sizeof(long)); // long array
   float_array=xalloc(NUM_ENTRIES * sizeof(float)); // float array

The above allocates the buffer for all 4 arrays of the proper size. The returned value is then used to access the array. To access the data in the array, use the appropriate xset() and xget() functions. To read/write an entry we need to calculate the address to use. In the following example we with use 'index' to offset into the array to an entry:

Setting a value:
   xsetchar(char_array+index,value); // char arrays can use index directly
   xsetint(int_array+index*sizeof(int),value); 
   xsetlong(long_array+index*sizeof(long),value); 
   xsetlong(float_array+index*sizeof(float),value); // floats are same size as long on rabbit

Reading a value:
   value=xgetchar(char_array+index); // char arrays can use index directly
   value=xgetint(int_array+index*sizeof(int)); 
   value=xgetlong(long_array+index*sizeof(long)); 
   value=xgetlong(float_array+index*sizeof(float)); // floats are same size as long on rabbit

Notice we calculate an address of start of buffer + index*sizeof(type). The proper xget and xset function needs to be used to copy the right number of bytes..

Accessing xmem arrays of Structures

One method is the following:

unsigned long xmem_data;

xmem_data=xalloc(sizeof(MY_STRUCT)*NUM_ENTRIES);

Then have a local MY_STRUCT variable and use xmem2root()and root2xmem() to update the array. This is a waste if you only need to update one element of a structure. See the sample for filling an array on how to copy structures.

An easier way is to first generate an offsetof() macro:

#ifndef offsetof
#define offsetof(str,member)	(unsigned) &(((str *) 0)->member)
#endif

The above can be used to calculate an offset of a structure member. It may look like it generates code, but it just calculates an integer. This is done at compile time. This was a suprise with DC as it has such poor optimization.

Then use xsetint() xgetint() etc to read/write the structure members.

As an example, we will use the following struct:

typedef struct {
  int type;
  long timestamp;
  char message[100];
} LOG_ENTRY;

First we will allocate 100 entries:

unsigned long log_buff;
log_buff=xalloc(sizeof(LOG_ENTRY)*100);

Suppose we want to set the timestamp member in the index'th structure in the array to MS_TIMER: effectively log_buff[index].timestamp=MS_TIMER.

   //       log_buff[         index                  ].timestamp  //<--- what line below does
   xsetlong(log_buff + sizeof(LOG_ENTRY)*index + offsetof(LOG_ENTRY,timestamp),MS_TIMER);

This would set the member timestamp to MS_TIMER in the structure #index. Basically to access the member we need to calculate the address then us the appropriate xset***() function: xsetchar(), xsetint() xsetlong().

Reading a variable is basically the same:

long ltime;

// ltime=         log_buff[         index                  ].timestamp  //<--- what line below does
   ltime=xgetlong(log_buff + sizeof(LOG_ENTRY)*index + offsetof(LOG_ENTRY,timestamp));

Note you would use xsetlong() and xgetlong() for float values as they are 4-bytes.

This if much easier than manually figuring out that timestamp is 2 bytes from the start of the structure.

Strings are a bit more complicated. To copy a string to the array we would need to use root2xmem(). To set the message string in the index'th entry, use the following:

char my_string[100];

   strcpy(my_string,"Error: invalid configuration parameter"; 
   // my_string is the data we want to put in .message
   root2xmem(log_buff + sizeof(LOG_ENTRY)*index + offsetof(LOG_ENTRY,message),my_string,100);

Note: you could use strlen(my_string)+1 instead of 100. Reading a string is just the reverse with xmem2root:

char my_string[100];

   xmem2root(my_string,log_buff + sizeof(LOG_ENTRY)*10 + offsetof(LOG_ENTRY,message),100);

That will copy the string to a local buffer.

There is no xmemset() function to fill mem with a value. The easiest is to initialize one structure in local RAM and copy it relatedly into each structure in the xmem array;

unsigned long log_buff; // our array of 100 LOG_ENTRY structures
unsigned long index;

   LOG_ENTRY my_log; // local temp var
   memset(my_log,0,sizeof(LOG_ENTRY)); // all 0's
   my_log.type=TYPE_UNUESD; // mark as unused
   for (index=0;index<100;index++)
   {
      root2xmem(log_buff + sizeof(LOG_ENTRY)*index),my_log,sizeof(LOG_ENTRY));
   }

A faster way to do the above is to prevent the index calculation every time:

unsigned long log_buff; // our array of 100 LOG_ENTRY structures
unsigned long index;
unsigned long tmp_ptr;

   // create a local pointer to log_buff[0]
   tmp_ptr=log_buff;   // create a local pointer
   memset(my_log,0,sizeof(LOG_ENTRY)); // all 0's
   my_log.type=TYPE_UNUESD; // mark as unused
   for (index=0;index<100;index++)
   {
      root2xmem(tmp_ptr,my_log,sizeof(LOG_ENTRY));
      tmp_ptr+=sizeof(LOG_ENTRY);
   }

Anytime you are accessing an array of structures sequentially, you can calculate the first address, then add sizeof(struct) to the pointer to get to the next. Suppose we only wanted to set all of the .type members to TYPE_UNUSED. Rather than use use root2xmem() we will calculate the address of the first .type member then offset from that.

unsigned long log_buff; // our array of 100 LOG_ENTRY structures
unsigned long index;
unsigned long tmp_ptr;  // local variable to use as an address
LOG_ENTRY my_log; // local temp var

   // create a local pointer to log_buff[0].type
   tmp_ptr=log_buff+offsetof(LOG_ENTRY,type);
   for (index=0;index<100;index++)
   {
      xsetint(tmp_ptr,TYPE_UNUSED);
      tmp_ptr+=sizeof(LOG_ENTRY);
   }

The code produced is much faster. Note if we were going index through part of the array we would need to calc the first address a little differently:

unsigned long log_buff; // our array of 100 LOG_ENTRY structures
unsigned long index;
unsigned long tmp_ptr;
LOG_ENTRY my_log; // local temp var

   index=20; // we will start at the 20'th entry
   // create a local pointer to log_buff[index].type
   tmp_ptr=log_buff + index*sizeof(LOG_ENTRY) + offsetof(LOG_ENTRY,type);
   for (;index<30;index++) // do for entries 20-29
   {
      xsetint(tmp_ptr,TYPE_UNUSED);
      tmp_ptr+=sizeof(LOG_ENTRY);
   }

Ther is no xsetchar()/xgetchar() in DC. Here is a function to implement it:

root void xsetchar(long dst, char val);

nodebug root nouseix void  xsetchar(long dst, char val)
{
#asm nodebug
    ld    h,d
    ld    l,e
    res   0,l
    ld    iy,hl			; c/IY = paddr&0xffffe
    ld    hl,(sp+@SP+val)		; HL = value
    ld    b,l          ; save byte
    ld    a,c
    ldp   hl,(iy)
    bit   0,e
    jr    z,set_even
;set_odd:
    ld    h,b
    jr	.setcharexit
set_even:
    ld    l,b
.setcharexit:
    ldp   (iy),hl
#endasm
}

nodebug root int xgetchar(long dst);

nodebug root nouseix int xgetchar(long dst)
{
#asm nodebug
    ld    h,d
    ld		l,e
    ld    iy,hl			; c/IY = paddr
    ld    a,c
    ldp   hl,(iy)
    ld    h,0
#endasm
}

That should be all you need. Hope this gets you going ;)

(c) 2005, Scott Henion, SHDesigns

Notes:

  1. Be sure to check the value of xalloc() is non-0. If it is 0, there is not enough free xmem available.
  2. If you only want one structure or are accessing the first one, eliminate the sizeof(struct type)*index from the calculation.
  3. When calculating indexes of arrays over 32k, be sure to use long variables. Integers will become negative if the calculated offset is >32k.
  4. When allocating xmem data, the address will be the same on bootup if you always allocate them in the same order. With battery-backed RAM, the array data should always be the same after a reboot. Be careful though, compiling a new program may alter the memory map.
  5. Be sure to never modify the pointer allocated by xalloc(), always use a temp copy to do address calculations.


Additional Information: Back to Tips page - SHDesigns Home Page