Using external SRAM for .data, .bss, and .heap sections on the USB AVRs

219 views
Skip to first unread message

Opendous Support

unread,
Apr 19, 2011, 1:11:00 AM4/19/11
to microp...@googlegroups.com
I got it to work. Basically, variables in the .data section cannot
be initialized if their addresses are in the external address space.
The solution is to move the stack up and place the .data section after
the stack such that it overlaps both internal and external memory.
You must make sure that any variables required for run-time are in the
internal SRAM portion of .data. One problem is that you may need to
manually reinitialize .data arrays in external SRAM.

The necessary linker command is:
EXTMEMOPTS +=-Wl,--defsym=__stack=0x801100,--section-start,.data=0x801200,--defsym=__heap_start=0x80DFFF,--defsym=__heap_end=0x80FFFF

The details:

Normally, the .data section is located at address 0x100 (0x800100
with the SRAM offset). Compile firmware and check this by searching
for the first 008... in the .sym file (symbol table).
00800100 D __data_start

I created a static variable, varInDATA, in the main .c file and in
the makefile I relocated the .data section to 0x300:
--section-start,.data=0x800300

The varInDATA variable wound up at 0x3cd = 973 and when I ran the
test script the output was:
varInDATA=33 at 973

If you look in the .sym file you will notice there are many variables
that need to be initialized (they have the 0x800000 offset). These
need to be copied from FLASH to SRAM before your firmware will run.
Quite a few of them are related to LUFA and USB operation in general.

I relocated the .data section to 0x802100 (start of external SRAM)
and the firmware did not run. This means that the SRAM data variables
are not being initialized which implies the XMEM interface is not
functional during initialization. This is the strange part as I have
placed all the XMEM initialization code into .init0. If you look at
the .lst file (assembly listing) you will see that the first two
instructions (lines 25 and 26) enable XMEM:

XMCRA = ((1 << SRE)); // first line of EnableExternalSRAM
// (1 << SRE) = (1 << 7) = 128 = 0b10000000
// from iousbxx6_7.h:
#define XMCRA _SFR_MEM8(0x74) // 0x74 = 116
#define SRE 7

1 .file "USBVirtualSerial-ExtSRAMTest.c"
2 __SREG__ = 0x3f
3 __SP_H__ = 0x3e
4 __SP_L__ = 0x3d
5 __CCP__ = 0x34
6 __tmp_reg__ = 0
7 __zero_reg__ = 1
15 .Ltext0:
16 .section .init0,"ax",@progbits
17 .global EnableExternalSRAM
19 EnableExternalSRAM:
20 .LFB93:
21 .LSM0:
22 /* prologue: naked */
23 /* frame size = 0 */
24 .LSM1:
25 0000 80E8 ldi r24,lo8(-128)
26 0002 8093 7400 sts 116,r24

For some reason SRAM variables do not want to be initialized in
external memory. I took another look at the .sym file and the final
SRAM variable is at 0x224d (0x0080224d N _end). (0x224d - 0x2100) =
0x14d = 333. Only 333 bytes are required for proper LUFA operation.
You can also get this value by typing make without any programming
target, "Data: 333 bytes (4.1% Full)".

I relocated the .data section to 0x1F00 (0x2100 - 0x1F00 = 0x200 =
512) and allowed 16kbytes for the heap (0xFFFF-0xBFFF=0x4000=16384)
EXTMEMOPTS +=-Wl,--section-start,.data=0x801F00,--defsym=__heap_start=0x80BFFF,--defsym=__heap_end=0x80FFFF

It works but the stack will now eventually collide with .data!
varInDATA=33 at 8139
stackVal=38 at 8436
someVal=38 at 8431

Variable varInDATA is in .data, stackVal is a local variable (on the
stack) in main() and someVal is a local variable in the function
returnStackAddress(). As you can see, the stack is growing up towards
zero and will eventually collide with .data.

Therefore, the solution is to move the stack below .data or figure
out how to get .data to initialize in external SRAM. Internal SRAM is
faster so better to move the stack and leave room in internal SRAM for
.data. Put it on a 4-byte boundary just in case.

EXTMEMOPTS +=-Wl,--defsym=__stack=0x801100,--section-start,.data=0x801200,--defsym=__heap_start=0x80DFFF,--defsym=__heap_end=0x80FFFF

Success! There is now a (0x1100-0x100)=1000= 4096 byte stack, a
((0xDFFF-1)-0x1200)=CDFE= 52734 byte .data+.bss section with the first
3584 bytes in internal SRAM and the rest in external SRAM, and a heap
for malloc() of (0xDFFF-0xFFFF)= 8192 bytes. Note the first 0x100=256
bytes of internal SRAM are used as addresses for the data and
peripheral registers. Test script output is:

PORTE=135
__malloc_heap_start=57343
__malloc_heap_end=65535
ExtMemArray[0]=4294967295 at 57345
varInDATA=33 at 4832
stackVal=38 at 4343
someVal=38 at 4338
ExtMemArray[ExtMemLastIndex]=1234567890 at 65529
ExtMemLastIndex=2046

As you can see from the above, the stack is growing upward
(someVal-stackVal= -5) to 0 and varInDATA in .data is after the stack
(4832>4343).

Good luck with your project!

On Mon, Apr 18, 2011 at 7:12 AM, Opendous Support
<opendous...@gmail.com> wrote:
>  The attached USBVirtualSerial-ExtSRAMTest firmware is a complete
> LUFA-based example of how to use malloc() and its associated linker
> commands to allocate and access a 32-bit integer array in external
> SRAM on the Micropendous boards and communicate with the user over
> USBVirtualSerial using stdlib printf functions.
>
>  Either compile it from scratch or load
> USBVirtualSerial-ExtSRAMTest_Micropendous-AT90USB1287.hex directly
> onto your board.  A 16MHz crystal is assumed.
>
>  The Host side testing script, TestExtSRAMAccess.py, requires Python
> 2.5.x and PySerial
> http://python.org/download/releases/2.5.5
> http://sourceforge.net/projects/pyserial/files/pyserial/2.5/
>
>  If you do not want to install Python the communication protocol is
> very simple.  Send some data to the Serial Port of the device loaded
> with USBVirtualSerial-ExtSRAMTest firmware and read back seven lines
> of \r\n-terminated text.
>
>  The output should be something like:
> PORTE=135
>
>
> __malloc_heap_start=8448
>
> __malloc_heap_end=65535
>
>
> ExtMemArray[0]=4294967295 at 8450
>
> ExtMemArray[1]=1010101010 at 8454
>
> ExtMemArray[ExtMemLastIndex]=1234567890 at 65506
>
>
> ExtMemLastIndex=14264
>
>
>
>  Changes made to USBVirtualSerial.c from Micropendous-2011-03-01.zip
> to create USBVirtualSerial-ExtSRAMTest.c include:
>
>  The required linker command in the makefile is:
> EXTMEMOPTS +=-Wl,--defsym=__heap_start=0x802100,--defsym=__heap_end=0x80ffff
>  This places the heap in external SRAM while leaving internal SRAM
> for variables and the stack.  Other options given at
> http://www.nongnu.org/avr-libc/user-manual/malloc.html are not
> workable.
>
>  Set up external SRAM in an .init section:
> // set up external SRAM prior to anything else to make sure malloc()
> has access to it
>
> void EnableExternalSRAM (void) __attribute__ ((naked)) __attribute__
> ((section (".init3")));
>
> void EnableExternalSRAM(void)
>
> {
> PORTE_EXT_SRAM_SETUP;
>
>  XMCRA = ((1 << SRE)); // zero wait-states
>
>  XMCRB = 0;
>   }
>
>  Create a global pointer to the data array:
> static uint32_t* ExtMemArray;
>
>  Use malloc to allocate the memory for 32-bit integers near the top of main()
> uint16_t ExtMemArraySize = 57056; // 57056/4 = 14264
>
> ExtMemArray = (uint32_t *) malloc(ExtMemArraySize);
>
>
> if (ExtMemArray == NULL) {
>  abort(); }
>
>  Before the call to USB_Init() in SetupHardware, make sure once more
> that the external SRAM interface is active:
> PORTE_EXT_SRAM_SETUP;
>
> ENABLE_EXT_SRAM;
>
> SELECT_EXT_SRAM_BANK0;
>
>  Inside MainTask() change the values of array elements and send
> associated data to the Host if it requests it:
> ExtMemArray[0] = (uint32_t)4294967295;
>  // maximum unsigned 32-bit integer value
> ExtMemArray[14264] = (uint32_t)1234567890; // final array element
> if ((count = fread(&buffer, 1, CDC_TXRX_EPSIZE, &USBSerialStream)) > 0) {
>  fprintf(&USBSerialStream, "__malloc_heap_start=%5u\r\n",
> ((uint16_t)__malloc_heap_start));
>  fprintf(&USBSerialStream, "ExtMemArray[0]=%10lu at %5u\r\n",
> ((uint32_t)ExtMemArray[0]), ((uint16_t)&ExtMemArray[0]));
>  fprintf(&USBSerialStream, "ExtMemArray[14264]=%10lu at %5u\r\n",
> ((uint32_t)ExtMemArray[14264]), ((uint16_t)&ExtMemArray[14264]));
> }
>
>  USBVirtualSerial-ExtSRAMTest firmware can be found at:
> http://code.google.com/p/micropendous/downloads/detail?name=USBVirtualSerial-ExtSRAMTest.zip
>
>
> On Sun, Apr 17, 2011 at 1:45 PM, hcaltenco <hcal...@gmail.com> wrote:
>> Yes, hehe... and many other makefiles I have seen also have the wrong
>> linker command, including all the LUFA library examples. I will write
>> Dean about it so he corrects it in future LUFA distributions.
>>
>> A demo that uses the linker and malloc to allocate the data and
>> possibly also the bss and heap sections will be very helpful. Thank
>> you very much.
>>
>> On Apr 17, 8:45 am, Opendous Support <opendous.supp...@gmail.com>
>> wrote:
>>> I seem to have copied the wrong linker command into/from the
>>> makefile.  -T will never work and --section-start must be used:http://www.nongnu.org/avr-libc/user-manual/malloc.html
>>>
>>> I will finish up a demo based on malloc() and update the Micropendous
>>> distribution accordingly.
>>>
>>> On Apr 16, 11:43 am, hcaltenco <hcalte...@gmail.com> wrote:
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>> > Hello all,
>>>
>>> > I am having a problem storing the ".data" section in the external ram
>>> > of the micropendous board. I hope someone here can help me, since I
>>> > have been looking in forums and no one seems to have had the same
>>> > problem.
>>>
>>> > I have an application that uses 50 kb of static data, therefore needs
>>> > external ram. Which is not a problem since the micropendous boards
>>> > come very well equiped ;). However, I was quite surprised that the
>>> > default makefiles in the micropendous distribution and other examples
>>> > (i.e. latest LUFA distribution) give the linker external memory
>>> > options that do not work.
>>>
>>> > The "-Tdata" is not recognized by the linker. Give it a try, try to
>>> > compile one of the examples (e.g. MicropendousKeyboardTest) using the
>>> > default " -Wl,-Tdata=0x802100,--defsym=__heap_end=0x80ffff " for the
>>> > at90usb1287. Then open the .lss or .map files and you will see that
>>> > the data section starts at the internal address 0x800100. The correct
>>> > extmemoptions for the linker should be: "-Wl,--section-
>>> > start,.data=0x802100,--defsym=__heap_end=0x8090ff ", as stated in the
>>> > avr-libc documentation (http://www.nongnu.org/avr-libc/user-manual/
>>> > malloc.html). Compiling with these flags will produce lss and map
>>> > files with the correct external address starting at 0x802100. It took
>>> > me some time to find out that, and will probably save many people that
>>> > use external sram some time if this was corrected in all the
>>> > distribution examples.
>>>
>>> > Anyway, then you are just supposed to initialize external memory in
>>> > the hardware initialization or at the beginning of your main() as:
>>> >             /* Enable External SRAM */
>>> >             DISABLE_VOLTAGE_TXRX;
>>> >             PORTE_EXT_SRAM_SETUP;
>>> >             ENABLE_EXT_SRAM;
>>> >             XMCRA = ((1 << SRE)); // enable external SRAM with 0 wait
>>> > states
>>> >             XMCRB = 0;
>>> >             //__malloc_heap_start = (void*) 0x5100; // make sure that
>>> > your heap starts after your data and bss
>>> >             //__malloc_heap_end   = (void*) 0x90ff;
>>>
>>> > /*Allocating external memory using  __malloc_heap_start and
>>> > __malloc_heap_end do not seem to have any effect on any of the four
>>> > options described below (thats why I commented them).*/
>>>
>>> > However this does not solve the problem completely. Try it out for
>>> > example in the "MicropendousKeyboardTest" example. Setting all the
>>> > data, bss, and heap to the external does not type anything when you
>>> > ground a pin with a cable:
>>> >         EXTMEMOPTS += -Wl,--section-start,.data=0x802100,--
>>> > defsym=__heap_end=0x8090ff
>>>
>>> > Setting the data section in the external and the rest on the internal
>>> > does not type anything either:
>>> >         EXTMEMOPTS += -Wl,--section-start,.data=0x802100,--section-
>>> > start,.bss=0x800100
>>> >         EXTMEMOPTS += --defsym=__heap_start=__bss_end,--
>>> > defsym=__heap_end=0x8010ff
>>>
>>> > But Setting the bss section in external and the rest on the internal
>>> > works (types continuously a2 and a0 because the external memory
>>> > registers need to have that pins in low).
>>> >         EXTMEMOPTS += -Wl,--section-start,.data=0x800100,--section-
>>> > start,.bss=0x802100
>>> >         EXTMEMOPTS += --defsym=__heap_start=__data_end,--
>>> > defsym=__heap_end=0x8010ff
>>>
>>> > Also Setting only the heap to external ram and the rest in internal
>>> > also works (typing a2 and a0 continuously).
>>> >         EXTMEMOPTS += --defsym=__heap_start=0x802100,--
>>> > defsym=__heap_end=0x8090ff
>>>
>>> > Due to this issue, it seems that the .data section cannot be located
>>> > in external ram!!! Which is a bit annoying since is the 50kb section i
>>> > need to put there. The .bss and the heap seem to have no problem being
>>> > in the external memory.
>>>
>>> > I am suspecting it has something to do with the timings, since it
>>> > takes two clock cycles to write and read from external ram while it
>>> > takes only one clock cycle to write and read from internal ram. Has
>>> > anyone been successful using the data section in external memory?
>>>
>>> > I would appreciate very much the help, this problem is driving me
>>> > crazy.
>>>
>>> > All the best,
>

USBVirtualSerial-ExtSRAMTest-with-52kb-data-section.zip

hcaltenco

unread,
Apr 19, 2011, 9:08:11 AM4/19/11
to Micropendous
You are my hero!,

you just saved me from many hours and hours of trial and error
programming and debugging. I owe you a big one.

I did tried to initialize external memory before without moving
sections around with the linker, but I never thought of moving the
stack so it does not collide with the other sections. That was a
brilliant idea!

To test if that really works... I created an static memory array
values in data, which is big enough to allow the section to grow into
external ram:
static uint32_t arrInDATA[5000] = {[0 ... 4999] = 54321};

The results are:
PORTE=135
__malloc_heap_start=57343
__malloc_heap_end=65535
varInDATA=33 at 5002
stackVal=38 at 4339
someVal=38 at 4334
arrInDATA[0]=54321 at 5025
arrInDATA[ArrInDataLastIndex]=54321 at 10021
ArrInDataLastIndex=1249
ExtMemArray[0]=4294967295 at 57345
ExtMemArray[ExtMemLastIndex]=1234567890 at 65529
ExtMemLastIndex=2046

you can see that the first element of the array in data is at internal
memory and the last one is at external memory. So it works perfectly,
I did the same in other lufa demos and the functionality of hid demos
(keyboard and mouse) work fine. So it really seems to be working now.

Thank you!




On Apr 19, 7:11 am, Opendous Support <opendous.supp...@gmail.com>
wrote:
> >http://www.nongnu.org/avr-libc/user-manual/malloc.htmlare not
> >http://code.google.com/p/micropendous/downloads/detail?name=USBVirtua...
>  USBVirtualSerial-ExtSRAMTest-with-52kb-data-section.zip
> 832KViewDownload

Opendous Support

unread,
Apr 19, 2011, 10:05:42 PM4/19/11
to Micropendous
Even better news, I got .data to initialize in external SRAM.

I moved other critical initialization commands into .init3 and
removed all other mentions of PORTE throughout the code.

void EnableExternalSRAM (void) __attribute__ ((naked)) __attribute__
((section (".init3")));
void EnableExternalSRAM(void)
{
XMCRA = ((1 << SRE)); // XMEM with zero wait-states
XMCRB = 0;
PORTE_EXT_SRAM_SETUP;
ENABLE_EXT_SRAM;
SELECT_EXT_SRAM_BANK0;
MCUSR &= ~(1 << WDRF);
wdt_disable();
clock_prescale_set(clock_div_1);

DISABLE_VOLTAGE_TXRX;
SELECT_USB_B;
}

I am now able to relocate the .data section at will with the linker
command (in the makefile search for EXTMEMOPTS):
EXTMEMOPTS +=-Wl,--defsym=__stack=0x802000,--section-
start,.data=0x802100,--defsym=__heap_start=0x80DFFF,--
defsym=__heap_end=0x80FFFF

The above will keep the stack in internal SRAM (0x802000 but it
grows toward 0), .data+.bss in external SRAM (0x802100) and the heap
near the end of external SRAM (0x80DFFF...0x80FFFF). If you intend to
use malloc() you can increase the size of heap (alter __heap_start).
Note that the stack should still be place before .data+.bss and
that .data+.bss must be large enough to encompass all your static
variables (such as global variables/arrays/pointers).
> ...
>
> read more »

Opendous Support

unread,
Apr 20, 2011, 11:17:40 AM4/20/11
to Micropendous
I just posted a firmware example that demonstrates memory section
relocation and malloc() usage for USB AVR boards with external SRAM:
http://code.google.com/p/micropendous/downloads/detail?name=Micropendous-ExtSRAMUsageDemo.zip

hcaltenco

unread,
Apr 23, 2011, 3:38:05 AM4/23/11
to Micropendous
Great!

Thank you very much for taking time in solving this issue. by
initializing external SRAM inside the .init3 function it is now
possible to move the memory sections at will anywhere in external or
internal SRAM. Is funny that it was necessary to move external SRAM
initialization to an init section. I wonder if this happens only for
the AT90USB128x or also for other AVRs. I have a micropendous 3 board
with AT90USB647 and another custom made board with AT90CAN128 that i
could add external memory. I will test them with memory initialization
inside and outside init sections. And report back to this forum.

One should be careful not to further change values of port E in other
hardware initialization commands. But do not remove them from the
code... Instead conditionally initialize external-memory related pins
only if external memory won't be used (i.e. if board is not
micropendous).

Thank you again for the big help!

On Apr 20, 5:17 pm, Opendous Support <opendous.supp...@gmail.com>
wrote:
>   I just posted a firmware example that demonstrates memory section
> relocation and malloc() usage for USB AVR boards with external SRAM:http://code.google.com/p/micropendous/downloads/detail?name=Micropend...
> ...
>
> read more »
Reply all
Reply to author
Forward
0 new messages