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

Rabbit Programming Tips
(C) 2006 SHDesigns

Using Battery-Backed RAM in Softools

There are two methods, depending on the board.

Boards without fast RAM:

This is simple as all memory is battery-backed. The issue is all global variables (BSS) are zeroed on startup. Rather than mess with startup and possibly break library code, it is better to define a special segment.

Start by defining a battery-backed segment:

#pragma SEG(BSS,BB_BSS)
....battery-backed variables....
#pragma SEG(BSS) // return back to regular variables

This defines a seg called BB_BSS. Variables defined between the two pragmas will go in BB_BSS. These variables must not have an initializer, or they will go in the IDATA segment.

Create a new segment call BB_BSS in the linker settings. Move it in between the BSS and STACK segments:

Segment should precede STACK and be after BSS. Excluded may be needed (will not add it to the .bin file.) Do not put it before BSS, or it will be mapped to Flash by cstart.

On these boards xmem is battery-backed and not zeroed. So data allocated by xalloc() will be preserved.

Example:

#pragma SEG(BSS,BB_BSS)
int current_state;
struct error_log ErrorLog[100];
unsigned ErrorIndex;
unsigned long mem_flag;
#pragma SEG(BSS)
char buff[100];

For the above, current_state, ErrorLog, ErrorIndex and mem_flag will be in BBRAM. However, they will initially be random data. I normally use checks on the data to make sure it is valid. Here is a sample:

#define MEM_FLAG 0xFACE55AAl
 if (mem_flag!=MEM_FLAG) // variable is not set, mem must be trashed
 {
  ErrorIndex=0;
  memset(ErrorLog,0,sizeof(ErrorLog));
  current_state=0;
  mem_flag=MEM_FLAG; // future runs will see this is valid.
 }

I also add checks for pointers. I.e:

  if (rd_ptr<bb_buff || (rd_ptr>(bb_buff+BUFF_SIZE))
   rd_ptr=bb_buff;

The above checks that the pointer is inside the bb_buff array.

For data that may be in an unknown state after a reboot, I add CRC on the data. If the CRC fails, then I assume the data is bad and reinitialize.

Note: include files will also need the seg definitions for other modules to access the variables::

//somefile.h
#pragma SEG(BSS,BB_BSS)
extern int current_state;
extern error_log ErrorLog[];
#pragma SEG(BSS)

Without the pragmas, external files will use the wrong segment. The compiler may not warn of this.

Boards With FAST RAM:

This gets more complicated. You will need to map memory to BBRAM. It is not in the 16-bit memory map. First, lets display the memory map:

 Range Mem type Battery backed?
00000-7FFFF  512k Fast RAM  No
80000-BFFFF 256k Slow RAM Yes
C0000-FFFFF 256k Slow RAM Yes *

* on 256k boards (RCM32000) this is not usable

Some versions of CSTART.asm had last 256k mapped to Flash.

Now, globals and the stack get mapped into BSS, STACK and IDATA. The end address for STACK sets where in memory this is located.

The slow RAM runs with 1 wait-state (3-clocks) Fast RAM runs 0 wait-state (2-clocks) so there is a 33% penalty for using BBRAM. You want to keep as much in fast RAM as possible for speed.

Option 1: map xmem to BBRAM:

This option is the simplest, set the end address for STACK to 7FFFF. This will leave the rest for xmem. All xmem allocated by xalloc() will be BBRAM but will have 1 added wait state. This will effect performance some, but not significantly. Since all BBRAM variables would be allocated with xalloc() and are far, there is a performance hit with slower RAM and more code to access.

Option 2: Map near data to BBRAM:

Set the end of STACK to 0x8BFFF. That will put, stack, globals and xmem in BBRAM. This will make all globals battery backed. You will then have to define a BB_BSS segment as described in the top section.

Since STACK now in slow RAM, it will effect speed. It does remove the added code to access far variables by mapping a near segment. It is a simple solution to implement.

Option 3: Map a BB_RAM segment to BBRAM:

This is complicated but has the highest performance and most flexibility. I'll review the data segments first:

BSS  - Global variables
BSSZ - Zeroed variables (seems to not be used.)
IDATA - initialized variables, i.e int xyz=3;
STACK - stack segment

These are basically one continuous "near" data area by default in Softools. The Rabbit will access this via 16-bit pointers. By default these are mapped by the STACKSEG, DATASEG and SEGSIZE registers:

Seg  Value  Mapping
 SEGSIZE  0xSG 0x0000-(0xG000-1= Root Code
0xG000-(0xS000-1) = Data seg
0xS000-0xDFFF= Stack seg
 STACKSEG  0xNN  Physical=Addr+STACKSEG<<12
 DATASEG  0xNN  Physical=Addr+DATASEG<<12

SEGSIZE sets the boundaries between the root code, DATASEG and STACKSEG segments. Normally, Softools does not use the DATASEG. We can use this to map our own DATASEG. This is best described given a map file:

Logical   Logical   Physical  Physical   Size
Start     End       Start     End
00005400  00009205  00037400  0003B205  003E06  BSS              DATA
00009345  0000CBFF  0003B345  0003EBFF  0038BC *IDATA            DATA
0000CC00  0000DFFF  0003EC00  0003FFFF  001400  STACK            STACK

BSS starts at logical address 0x5400 and physical address of 0x37400. Anything below BSS is root code or constants, all near addreses above this need to be mapped to RAM. Cstart.asm will use STACKSEG for this. So:

physical = STACKSEG <<12 + laddr; (laddr is logical address.)

So the compiler will address variables at a logical (near) address starting at 0x5400. It needs to be mapped to a physical address of 0x37400. So solving for DATASEG:

STACKSEG = (physical-laddr) >>12;
STACKSEG = (0x37400-0x5400)>>12 = 0x32.

So cstart.asm will set STACKSEG to 0x32. but we a only have half of the segment definition. We will need to describe what gets put in the STACKSEG. The SEGSIZE register sets the area of near memory mapped. SEGSIZE can only map on 4k boundaries. This is a limitation of the Rabbit CPU. Near (logical) data starts at 0x5400. We will map starting at 0x5000. So we will set SEGSIZE to 0x50. The '5' says STACKSEG starts at 0x5000 and the '0' says DATASEG starts at 0 (DATASEG==0) Logical addresses below 0x5000 use DATASEG and are mapped with logical==physical.

Ok, we have STACKSEG and DATASEG, but DATASEG is basically not mapped. If we set the start boundary for DATASEG to our own value, we can map our own segment. We will want to achieve something like the following:

 Logical  Segment    Compiler segments
 0000-4FFF  ROOT code/Const  Logical=Physical  CODE, CONST
 5000-7FFF  DATASEG  Mapped to BBRAM  BB_BSS
 8000-DFFF  STACKSEG  Mapped to Fast RAM and what the compiler expects  BSS, IDATA, STACK
 E000-FFFF  XPC  Used for FAR code  FARCODE, FARCONST

On the above, SEGSIZE would be 0x85. '8' says anything above 0x8000 is STACKSEG and '5' says anything above 0x5000 but below 0x8000 is DATASEG.

Again we are stuck with 4k (0x1000) boundaries. We can use the linker to help us with this. Cstart will configure the STACKSEG and upper

First we will create a BB_BSS segment. Variables can be placed there with the #pragma SEG(BSS,BB_BSS) as described in the first section for systems with no fast RAM. In this case we will place BB_BSS before BSS. To get our mapping we will need to add some restrictions. Set both BB_BSS and BSS to have 4k alignment (1000). This does two things, it makes them start of 4k boundaries, and also will cause errors in the linker if this alignment can not be achieved.

As said before, cstart will set up STACKSEG and SEGSIZE for the BSS, IDATA and STACK segments. We will need to map our BB_BSS. This needs to use ASM code to use the seg variables. Sample code is as follows:

#pragma offset_labels on
void map_bb_ram(char far * bb_addr)
{
#asm
    ld   b,sgst BB_BSS>>12
  ioi ld a,(SEGSIZE)
    or   b  ; add our BB_SEG start address
  ioi ld (SEGSIZE),a
    ld   hl,(ix+.bb_addr) ; get lower 16 bits
    ld   a,(ix+.bb_addr+2) ; get uypper 4 bits
    ld   de,0x0fff
    add  hl,de  ; round up
    jr   nc,..same_64k
    inc  a
..same_64k:
    add  hl,hl ; shift upper 4 buts of HL into
    rla
    add  hl,hl
    rla
    add  hl,hl
    rla
    add  hl,hl
    rla
 ; A now has upper 8-bits of physical address
    sub  sgst BB_BSS>>12 ; subtract locgical address 4k
    ioi  ld (DATASEG),a
}

The above function will map the BB_BSS segment to a physical address. Where do we get this address? We can just set a predefined address in the 0x80000-0xFFFFF range (512k bbram) or 0x80000-0xbFFFF (256k bbram). But we don't want to interfere with xalloc() or where the compiler mapped near data to RAM. A simple solution is to create another xalloc() that gets bbram.

This is be done by grabbing the top of the BBRAM for our variables:

// these are in cstart
extern unsigned long _xmemSize,_xmemEnd,_xmemHeap;

char far * bb_xalloc(unsigned long bb_size)
{
    if ((_xmemEnd-0x80000l)<bb_size) // bbram starts at 0x80000
       return (char far *)NULL;
    if ((_xmemEnd-_xmemHeap)<bb_size) // no free xmem at all!
       return (char far *)NULL;
	_xmemSize-=bb_size; // tell xalloc() there is bb_size bytes used
	_xmemEnd-=bb_size;
    return (char far *)_xmemEnd; // where our BB_BSS data resides
}

We can define a function to find available bbram:

unsigned long bb_xavail(void)
{
    if (_xmemEnd<0x80000l) // bbram starts at 0x80000
       return 0l;
    return (_xmemEnd-0x80000l);
}

This will steal the top of mem away from the xalloc() pool. Define a function to get the size of the BB_RAM seg rounded up to 4k:

unsigned get_bb_size(void)
{
  unsigned bb_size;
#asm
    ld   hl,sgsz BB_BSS
#endasm
    bb_size=_HL; //asm left HL with size of BB_BSS
    bb_size+=0x0fff; // round up 4k
    bb_size&=0xf000; // # of 4k pages
    return bb_size;
}

Ok, we have just about everything. Now we would just define variables and allocate the BB_BSS seg at run time.

#pragma SEG(BSS,BB_BSS)
// variables between these seg statements is battery-backed and not zeroed
extern int current_state;
extern error_log ErrorLog[];
#pragma SEG(BSS)

void main(void)
{
    unsigned bb_size;
   char far * bb_far_array;
   // main variables
    bb_size=get_bb_size(); // get the size of the segment rouned up to 4k;
    // assume this works
    map_bb_ram(bb_xalloc(bb_size)); // map STACKSEG to regular RAM, DATASEG to BB RAM
    // rest of main code

    // allocate a battery-backed far array
    bb_far_array=bb_xalloc(FAR_SIZE);
}

Ok, that will map our BB_RAM variables. Now we will have full control over battery-backed global variables and regular variables and stack in fast RAM. The bb_xalloc() can be used to allocate far buffers that are battery backed. xalloc() will return fast RAM buffers that are not battery backed. Once xalloc() reaches 0x80000, it will start using battery-backed RAM.

A code sample is now available.

The sample does the following:

  1. Maps a BB_RAM seg to the top of battery-backed RAM.
  2. defined battery-backed variables
  3. Uses a "valid" flag to tell if the BB_BSS has been initialized.
  4. Allocates a battery-backed far array
  5. runs some tests on the regular an BB RAM.

The sample includes a bb_ram.lib (and source) that can be used in your programs. The sample code will work on boards with fast RAM using the remapping techniques shown above. It can also be used on regular boards that have all RAM battery backed.

The sample can be downloaded here:

Note: this is not compatible with the CoExec with the far stack options. It uses similar tricks to use DATASEG and STACKSEG to have a different stack segment for each task. You can use the bb_xalloc() function with CoExec.


Additional Information: Back to Tips page - SHDesigns Home Page