How to generate a new storage section (Arduino / AVR-GCC)

Go To Last Post
5 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

If I have a hardware device that has, say, battery backed SRAM, is there a way that I can access it using variables rather than addresses? (I have the code for reading and writing the BB sram already, of course).

To clarify, imagine Arduino EEPROM. Most people use it like this:

 

#include EEPROM.h
uint8_t v = 10;
EEPROM.write (0, 10); // write to EEPROM address zero
v = EEPROM.read (0); // read back from EEPROM address 0
// v should read back as "10"

 

However, the proper way to use EEPROM is:

 

uint8_t n EEMEM; // place n in eeprom
uint8_t v = 10;
eeprom_write_byte (&n, v); // write v to n (in eeprom)
v = eeprom_read_byte (&n); // read eeprom n into v
// v should read back as "10"

 

By using EEMEM, I don't need to worry WHERE "n" is, nor do I need to worry what size it is (8 bit, 16 bit, etc...)

Now, I want to do the same thing with MY "memory".  Say I have functions to read and write data to battery backed SRAM on an RTC clock chip like this:

 

value = sram_read_byte (uint8_t addr);

sram_write_byte (uint8_t addr, uint8_t value);

 

I would like do do something like this:

#define SRAM __attribute__((section(".sram")))

... and then be able to use it in my program:

 

uint8_t n SRAM;
uint8_t v = 10;
sram_write_byte (&n, v); // write v to n (in RTC sram)
v = sram_read_byte (&n); // read RTC sram n into v
// v should read back as "10"

 

I've looked through the source for AVR-LIBC (both eeprom and progmem code) and can't seem to figure out how it's done.

Any ideas or help will be appreciated!

 

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The way GCC handles PROGMEM (flash) and EEMEM (EEPROM) are simply to have "named sections". The linker will then allocate addresses within that section. So if I do something like:

#define MYMEM __attribute__((section(".mymem")))

int foo MYMEM;
long bar MYMEM;
char data[20] MYMEM;
float vars[6] MYMEM;

int main(void) {
}

The the linker will handle address allocation within MYMEM:

.mymem          0x00800100       0x32 load address 0x0000008a
 .mymem         0x00800100       0x32 main.o
                0x00800100                vars
                0x00800118                data
                0x0080012c                bar
                0x00800130                foo

it's defaulted to a 0x00800100 base address (ie same as RAM .data) but I could over-ride that with a section start but the problem is that if I simply set it to 0:

 

 

then this happens:

section .text loaded at [00000000,00000089] overlaps section .mymem loaded at [00000000,00000031]collect2.exe(0,0): error: ld returned 1 exit status
		make: *** [test1.elf] Error 1

this is the reason that GCC does this:

MEMORY
{
  text   (rx)   : ORIGIN = 0, LENGTH = __TEXT_REGION_LENGTH__
  data   (rw!x) : ORIGIN = 0x800060, LENGTH = __DATA_REGION_LENGTH__
  eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = __EEPROM_REGION_LENGTH__
  fuse      (rw!x) : ORIGIN = 0x820000, LENGTH = __FUSE_REGION_LENGTH__
  lock      (rw!x) : ORIGIN = 0x830000, LENGTH = __LOCK_REGION_LENGTH__
  signature (rw!x) : ORIGIN = 0x840000, LENGTH = __SIGNATURE_REGION_LENGTH__
  user_signatures (rw!x) : ORIGIN = 0x850000, LENGTH = __USER_SIGNATURE_REGION_LENGTH__
}

So you would have to set this section to have a new base - let's say 0x860000 and then you'd have to later subtract that to get a 0 based offset:

.mymem          0x00860000       0x32
 .mymem         0x00860000       0x32 main.o
                0x00860000                vars
                0x00860018                data
                0x0086002c                bar
                0x00860030                foo

But you can use a bit of a trick if only the lower parts of these addresses matter:

volatile uint32_t addr;

int main(void) {
	addr = &foo;
}

Because pointers in avr-gcc are 16 bit the long 32 bit address is masked into 16 bits:

	addr = (uint32_t)&foo;
  90:	80 e3       	ldi	r24, 0x30	; 48
  92:	90 e0       	ldi	r25, 0x00	; 0
  94:	09 2e       	mov	r0, r25
  96:	00 0c       	add	r0, r0
  98:	aa 0b       	sbc	r26, r26
  9a:	bb 0b       	sbc	r27, r27
  9c:	80 93 00 01 	sts	0x0100, r24	; 0x800100 <_edata>
  a0:	90 93 01 01 	sts	0x0101, r25	; 0x800101 <_edata+0x1>
  a4:	a0 93 02 01 	sts	0x0102, r26	; 0x800102 <_edata+0x2>
  a8:	b0 93 03 01 	sts	0x0103, r27	; 0x800103 <_edata+0x3>

So I can write:

void myRAMwrite(uint16_t addr, uint8_t val) {
	// somehow set device to location addr
	// somehow write data byte "val" to that location
}

then:

int main(void) {
	myRAMwrite(&data[4], 'X');
}

except that if I put that in the same source file it doesn't generate code to make the CALL as it can see it does nothing. But if I just extern the function then:

    myRAMwrite(&data[4], 'X');
  90:    68 e5           ldi    r22, 0x58    ; 88
  92:    8c e1           ldi    r24, 0x1C    ; 28
  94:    90 e0           ldi    r25, 0x00    ; 0
  96:    0e 94 50 00     call    0xa0    ; 0xa0 <myRAMwrite>

The key thing here being that it knows that data[4] s at 0x001C in my new address space (the 0x0086 in the upper part of the address is effectively masked),

Last Edited: Mon. Nov 5, 2018 - 05:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Krupski wrote:
Most people use it like this:
BTW I'm horrified if they do - you shouldn't be passing absolute addresses because say you do this:

int n;
float f;

EEPROM.write(0, f);
EEPROM.write(2, n);

then you just wrote "n" over 2 of the 4 bytes that hold "f" and more generally, you, the user has to know what sizeof() is for all these things. Surely you can still use EEMEM with this Arduino library:

int n;
float f;

int EEn EEMEM;
float EEf EEMEM;

EEPROM.write(&EEf, f);
EEPROM.write(&EEn, n);

then you let the linker position the variables for you?

 

EDIT: oh dear God, even their user manual uses absolute addressing:

 

https://www.arduino.cc/en/Tutori...

 

frown

Last Edited: Mon. Nov 5, 2018 - 05:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In case clawson was not clear:

You can place battery variables in their own section with __attribute__((section=".battery"))) .

.battery needs to be positioned with --section-start .

Slightly above user signature should do.

You will still need to write to code to read and write from and to that section.

I'd do something with templates so that I could get the sizes right automatically.

As clawson noted, pointers in avr-gcc are only 16 bits,

so dealing with more than 64K gets tricky.

"SCSI is NOT magic. There are *fundamental technical
reasons* why it is necessary to sacrifice a young
goat to your SCSI chain now and then." -- John Woods

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, from the replies it seems as thought it would be a lot more trouble than it's worth.

 

I think I'll forget about it... for now.

 

Thanks everyone!

Gentlemen may prefer Blondes, but Real Men prefer Redheads!