Custom Mega2560 with extended memory

I'm trying to create a custom board (available via boards manager) that is a modified Mega2560 with extended RAM.
I found several ways to use the extra memory, but the most flexible seems to be to create a custom board and tell the linker that there is more memory.
I don't really know how to do that, and which files I have to modify in the arduino "realm".
I succeeded to add my board to the boards manager following these instructions from @Juraj.

One of the questions that I still have is, how can I add code to switch external memory interface on

  XMCRA |= 1ul<<7;
  XMCRB = 0;

so that it is not needed to do this in the sketch every time?

Second thing that I don't know how to do, is how to modify platform.txt to have the linker make use of the extra memory.
Currently I do this by allocating the heap in setup()

  __malloc_heap_start = (char*)0x8000;
  __malloc_heap_end   = (char*)0xFFFF;

But as far as I understood this leaves this space for dynamically allocated variables only. Not quite what I want to achieve
The avr-libc documentation gives info on how this can be done at compile-time, but I don't understand how this can be added to platform.txt. Part of the problem sure is that I don't understand how the linker is invoked in there...

I'm grateful for any help here :slight_smile:

There is some information about the linking "recipe" here:
https://arduino.github.io/arduino-cli/latest/platform-specification/#recipes-for-linking

Actually I cannot help much, but I'm interested in your project :slight_smile:

IMO the only memory sections are stack and heap, with the heap used for everything except the stack. On "big" systems the heap is subdivided into a data section for constants and initialized variables, a bss section for uninitialized variables and the rest is free for dynamic memory.

I don't know where you found these values, the internal memory ends already at 0x21FF so I wonder what are the original values? How much memory do you really want to add?

I also wonder about the bootloader and program initialization. Perhaps the bootloader is not involved because all initialized constant and variable values have to be stored initially in flash and are copied only on program start into SRAM. This should be kept in mind if you want to allocate big arrays. Initialized arrays also occupy flash memory and such const arrays can be left there if the code or runtime overhead is acceptable.

So the linker determines the size of the text (code), data and bbs sections and lets the startup code know how much data to copy from where in flash to SRAM and where to start the stack and remaining heap. You'll have to enable external memory before the SRAM initialization code.

I hope that I'm not too wrong and start digging into the firmware now myself.

the avr-libc documentation explains the possibilities to separate .data and .bss
Even if that is not what I plan (I think I'll do a you mentioned: all heap to external RAM), its quite nice to know :slight_smile:

Internal RAM ends at 0x21FF, external RAM will extend up to 0xFFFF

I got that information from the ATMEGA2560 datasheet and this site.

An interesting link that looks like much hacking. But you didn't answer my questions about the original values which you override and about the amount of RAM you want to add. Do you want to append it to the internal SRAM or handle 32k external memory starting at 0x8000?

Yes, I have a 32k external memory IC attached via many,. many jump wires - but it seems to work :smiley:

To be honest, I have no special reason for starting the external RAM at 0x8000, this is what I copied from the above mentioned link.
As far as I understood this, any address located after 0x2200 should be fine for external RAM.
There is still the distinction of upper and lower sector, but I didn't dive into that so far.

As for the original values. I did not bother finding out... I 'll try adding a --verbose to the linker invocation. Maybe that will shed some light on this topic.

I tried adding the flags from the avr-ibc link to compiler.ldflags= in platform.txt, but that only yields an error message:

avr-gcc: error: unrecognized command line option '--section-start,.data=0x808000,--defsym=__heap_end=0x80ffff'
exit status 1
Error compiling for board Mega 2560 + Xmem.

And if you think that it looks like I don't really know what I'm doing, you're very close to the truth :rofl:

ok, --verbose did not yield anything useful :frowning_face:

I know why the error message popped up:
I ommited the -Wl flag in the linker commands
avr-gcc ... -Wl,--section-start,.data=0x808000,--defsym=__heap_end=0x80FFFF ...

with the correct command line parameters it compiles fine, but absolutely nothing happens in the serial monitor (I had some demo output writing and reading to the xRAM that worked fine with the manual configuration in the sketch).

I found that
compiler.ldflags=-Wl,--defsym=__heap_start=0x808000,--defsym=__heap_end=0x80FFFF
works,
but
compiler.ldflags=-Wl,--section-start,.data=0x808000,--defsym=__heap_end=0x80FFFF
doesn't

which leaves me with the exact same problem that only dynamically created variables are stored in XMem

My knowledge about compiler and linker is very limited, so I have no Idea why this is so...
maybe start a new topic?

What if you only modify heap_end?

I just found an interesting memory extension with lot of documentation and sample program.

I stumbled upon that one as well a while ago, and another 512k project with also loads of information

But both only "transfer" the heap to the external SRAM.
So far I have not encountered a project that shoves .data and .bss to the external RAM as well.

Not sure what you try to achieve by setting only heap_end...
Tried it anyway :wink:
Static variables still get put in internal RAM, if I try and use more space then the 8k, the variables get addresses that are completely out of scope and the µC stalls

compiler.ldflags=-Wl,--defsym=__heap_end=0x80A1FF

  uint32_t arraysize = 1024;
  Serial.begin(57600);
  
  XMCRA |= 1ul<<7; // Switch ext mem iface on
  XMCRB = 0;
/*------------------------------------
  __malloc_heap_start = (char*)0x8000;
  __malloc_heap_end   = (char*)0xFFFF;
//-------------------------------------*/
//*
  uint32_t dataBuffer1[arraysize];
  uint32_t dataBuffer2[arraysize];
  uint32_t dataBuffer3[arraysize];
  uint32_t dataBuffer4[arraysize];
  uint32_t dataBuffer5[arraysize];
  uint32_t* dataBuffer6 = new uint32_t[arraysize];
  uint32_t* dataBuffer7 = new uint32_t[arraysize];
14:02:57.346 -> Dynamic buffer 1 of 4096 bytes created, address: 0x11F9
14:02:57.393 -> Dynamic buffer 2 of 4096 bytes created, address: 0x1F9
14:02:57.393 -> Dynamic buffer 3 of 4096 bytes created, address: 0xF1F9
14:02:57.393 -> Dynamic buffer 4 of 4096 bytes created, address: 0xE1F9
14:02:57.393 -> Dynamic buffer 5 of 4096 bytes created, address: 0xD1F9
14:02:57.393 -> Dynamic buffer 6 of 4096 bytes created, address: 0x3

I wonder about the 0x80... where I would expect 0xA1FF.
None of the dumped addresses resides above 0xFFFF

This is a specialty of the AVR it seems: https://www.nongnu.org/avr-libc/user-manual/mem_sections.html#harvard_arch

I experimented with .initN sections, but that did not do the trick. I was told that the XMEM interface has to be enabled in the startup code.
But I have no idea on how to modify that. Up to now I even haven't found the corresponding file...

Ardui-yes has created a dedicated topic for discussion of the startup code work:

I finally have it running like it should!

The main problem was the I made false assumptions on what will go where in memory space!

normal data buffers go to he stack, not to the .data section.

Either the information on avr-libc memory sections is misleading or I completely misunderstood it.

if you declare the buffers satic, they go to .data and thus to the external RAM

(nearly) complete test-sketch

void enableExternalMemory() __attribute__((naked,used)) __attribute__((section (".init3")));
void enableExternalMemory()
{
  XMCRA = (1<<SRE);
}

void setup()
{
  uint32_t arraysize = 1024;
  Serial.begin(57600);
  
  static uint32_t dataBuffer1[1024];
  static uint32_t dataBuffer2[1024];
  static uint32_t dataBuffer3[1024];
  static uint32_t dataBuffer4[1024];
  static uint32_t dataBuffer5[1024];
  uint32_t* dataBuffer6 = new uint32_t[arraysize];
  uint32_t* dataBuffer7 = new uint32_t[arraysize];
  
  Serial.println();
  Serial.print("Dynamic buffer 1 of ");
  Serial.print(arraysize * sizeof(uint32_t));
  Serial.print(" bytes created, address: 0x");
  Serial.println((uintptr_t)dataBuffer1, HEX);
  Serial.print("Dynamic buffer 2 of ");
  Serial.print(arraysize * sizeof(uint32_t));
  Serial.print(" bytes created, address: 0x");
  Serial.println((uintptr_t)dataBuffer2, HEX);
  Serial.print("Dynamic buffer 3 of ");
  Serial.print(arraysize * sizeof(uint32_t));
  Serial.print(" bytes created, address: 0x");
  Serial.println((uintptr_t)dataBuffer3, HEX);
  Serial.print("Dynamic buffer 4 of ");
  Serial.print(arraysize * sizeof(uint32_t));
  Serial.print(" bytes created, address: 0x");
  Serial.println((uintptr_t)dataBuffer4, HEX);
  Serial.print("Dynamic buffer 5 of ");
  Serial.print(arraysize * sizeof(uint32_t));
  Serial.print(" bytes created, address: 0x");
  Serial.println((uintptr_t)dataBuffer5, HEX);
  Serial.print("Dynamic buffer 6 of ");
  Serial.print(arraysize * sizeof(uint32_t));
  Serial.print(" bytes created, address: 0x");
  Serial.println((uintptr_t)dataBuffer6, HEX);
  Serial.print("Dynamic buffer 7 of ");
  Serial.print(arraysize * sizeof(uint32_t));
  Serial.print(" bytes created, address: 0x");
  Serial.println((uintptr_t)dataBuffer7, HEX);

  
  for (size_t i=0; i<arraysize; ++i)
  {
    dataBuffer1[i] = i+1;
    dataBuffer2[i] = i+1;
    dataBuffer3[i] = i+1;
    dataBuffer4[i] = i+1;
    dataBuffer5[i] = i+1;
    dataBuffer6[i] = i+1;
    dataBuffer7[i] = i+1;
  }
  Serial.println();
  Serial.println();
  Serial.println(err_str);


/*
  Serial.println("Data Buffer 1");
  for (size_t i=0; i<arraysize; i+=4)
  {
    Serial.println();
    Serial.print(i, DEC);
    Serial.print('\t');
    Serial.print(dataBuffer1[i], DEC);
    Serial.print('\t');
    Serial.print(dataBuffer1[i+1], DEC);
    Serial.print('\t');
    Serial.print(dataBuffer1[i+2], DEC);
    Serial.print('\t');
    Serial.print(dataBuffer1[i+3], DEC);
  }
//----------------------------------------*/
}

void loop()
{
}

(I included only output for 1 buffer to save some space in this thread)

To complete the board&variant creation, I created a folder called variants with a subfolder xmem in the boards folder under sketches. Then modify platform.txt to select this xmem variant instead of original arduino.mega
I copied the original pins_arduino-h from the original Arduino-Mega hardware folder and added a initXmem.c file with the following content

#include <avr/io.h>
#include <avr/iomxx0_1.h>
void enableExternalMemory() __attribute__((naked,used)) __attribute__((section (".init3")));
void enableExternalMemory()
{
  XMCRA = (1<<SRE);
}

Now the board can be selected via boardmanager and used just as a normal Arduino board

I hope that this works fine for everybody else
here is the .zip that has to go into "arduino sketches folder"/hardware

XmemMega.zip (5.2 KB)

Cheers and thanks for all help here and very very much at mikrocontroller.net

All local variables go into the stack.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.