I’ve been spending some time integrating the Rugged Circuits QuadRAM extension for the Arduino Mega (or Freetronics EtherMega) into the freeRTOS environment. And, it is now my standard environment. Actually, the MegaRAM, a slightly cheaper 128kByte version is my standard, as I’ve not found an application yet that needs more than 64kBytes total RAM. But, that will happen.
QuadRAM is available from Rugged Circuits again, after a long intermission.
Without adding any complexity into the environment, I can address up to 56kBytes of heap variables, effectively leaving the entire 8kbyte of internal RAM for the stack. With no complexity or overhead.
In addition, with some simple commands within a task can implement bank switching to access up to 7 additional banks of RAM, each up to 56kBytes.
A further degree of integration into freeRTOS is to completely automate memory bank switching, to give each of either 7 or 15 tasks a bank of RAM for its exclusive use. But this is a goal for the next few months.
Here are some pictures.
And here are the links to the products described.
I’m very happy with my Saleae Logic too. Must write a review on this, one day. Great tool, more useful every day.
The described code is all available on Sourceforge, if you’re intending to try this at home.
The only finished example using the memory routines is the MegaSD project. Take a look at it on Sourceforge to see how to use the extra RAM.
HOW TO
What do we have to do to get this build working? Well it is pretty simple really, once everything is figured out. It is really only three steps.
- Initialise the RAM, and tell the AVR that it should enable its extended RAM bus.
- Tell the compiler that you’re moving the heap into the new extended RAM address space.
- Tell freeRTOS to move its heap to the new extended RAM address space.
Initialise the RAM
The RAM should be initialised prior to building the standard C environment that comes along for the ride. It can be done in the .init1 (using assembler) or in .init3 in C. I built both methods, but elected to just use the C method, as it is more maintainable (legible).
There are a number of references for this code. Some of the older ones refer incorrectly to a MCUCR register. That is not correct for the ATmega2560.
This example covers what Rugged Circuits suggest for their testing of the QuadRAM, but it doesn’t put the initialisation into .init3, which is needed to make the initialisation before heap is assigned. It makes the initialisation carefree.
// put this C code into .init3 (assembler could go into .init1) void extRAMinit (void) __attribute__ ((used, naked, section (".init3"))); void extRAMinit (void) { // Bits PL7, PL6, PL5 select the bank // We're assuming with this initialisation we want to have 8 banks of 56kByte (+ 8kByte inbuilt unbanked) DDRL |= 0xE0; PORTL &= ~0xE0; // Select bank 0 // PD7 is RAM chip enable, active low. Enable it now. DDRD |= _BV(PD7); PORTD &= ~_BV(PD7); // Enable XMEM interface XMCRA = _BV(SRE); // Set the SRE bit, to enable the interface XMCRB = 0x00;
To ensure that this .init3 function, that I’ve put into lib_ext_ram, is included in your linked code, we need to call something from the lib_ext_ram library. If you’re planning to use the banks of RAM, then this is easy as you’ll naturally be calling the bank switching functions.
However, if you only want to use the extra 56kByte of RAM for simplicity (it is after all 7x more than you have available with just the internal RAM), then just call this function once from somewhere, possibly main(). I have added it to the freeRTOS stack initialisation function in port.c, so I don’t need to see it ever again.
extRAMcheck();
It returns the XMCRA value, that can be tested if you desire. But there’s no need as things will anyway have gone badly pear shaped if the RAM is not properly enabled. Calling this once is all that is needed to ensure that the .init3 code is properly inserted into the linked code.
Note: that the above code is specific to the QuadRAM device. The MegaRAM device has different IO in use, and the differences are noted in my code on Sourceforge.
Move the heap
The standard C heap has to be moved to the new location above the stack. There are other memory allocation options, but in my opinion this is the most sensible one and the only one I’m planning to implement.
The __heap_start and __heap_end symbols describe the addresses occupied by the extended RAM, and inform malloc() of the location of the heap. This is described in more detail here http://www.nongnu.org/avr-libc/user-manual/malloc.html. This is a great diagram showing the situation.
avr-gcc -Wl,-Map,MegaSDTest.map -Wl,--gc-sections -Wl,--section-start=.ext_ram_heap=0x802200 -Wl,--defsym=__heap_start=0x802200,--defsym=__heap_end=0x80ffff -mmcu=atmega2560 -o "MegaSDTest.elf" $(OBJS) $(USER_OBJS) $(LIBS)
Tell freeRTOS
Now freeRTOS has to be made aware of these changes to the heap location. There are three heap management options available for the AVR port. The two most memory economical options use a fixed array of memory defined in the .data section on initialisation. Clearly, this is not going to be useful. For the third option heap_3.c, which uses malloc(), we have nothing more to do.
However, getting heap_1.c and/or heap_2.c to work is not that complicated either. There are three parts to this. Firstly, creating a new section name, and locating it at the start of the desired heap space. We’ve already done that, above, with the –section-start command. The forth option heap_4.c has also been implemented.
Secondly, we have to make a small modification to both heap_1.c heap_2.c and heap_4.c to inform the compiler that the freeRTOS heap will be located at this .ext_ram_heap location. That is done in this manner (heap_2.c shown).
static union xRTOS_HEAP { //... edited ... unsigned char ucHeap[ configTOTAL_HEAP_SIZE ]; //... edited ... #if defined(portEXT_RAM) } xHeap __attribute__((section(".ext_ram_heap"))); // Added this section to get heap to go to the ext memory. #else } xHeap; #endif
And finally, now we’ve (probably, just because we can) allocated a large (up to 32kByte maximum) freeRTOS heap, we need to ensure that the loader omits this section from its preparations for writing the .hex file to flash (in a similar manner to the way the .eeprom section is removed).
avr-objcopy --remove-section=.eeprom --remove-section=.ext_ram_heap -O ihex MegaSDTest.elf "MegaSDTest.hex"
Something to watch for is that none of your other code is calling malloc(), because if it does its memory allocations will collide with the freeRTOS heap. Either check that malloc() is not being linked in or, for the paranoid, just assign the heap_1.c heap_2.c or heap_4.c heap to a region separate to your new malloc() heap addresses.
And that’s all there is to getting an easy 512kByte of fast no-wait-state RAM on your Freetronics EtherMega or Arduino Mega2560. Enjoy!