We have a thesis project at work were the guys are trying to get external RAM to work for the STM32F417 MCU. The project is trying out some stuff that is really resource hungry and the internal RAM just isn't enough.
The question is how to best do this.
The current approach has been to just replace the RAM address in the link script (gnu ld) with the address for external RAM.
The problem with that approach is that during initialisation, the chip has to run on internal RAM since the FSMC has not been initialized.
It seems to work but as soon as pvPortMalloc is run we get a hard fault and it is probably due to dereferencing bogus addresses, we can see that variables are not initialized correctly at system init (which makes sense I guess since the internal RAM is not used at all, when it probably should be).
I realize that this is a vague question, but what is the general approach when running code in external RAM on a Cortex M4 MCU, more specifically the STM32F4?
Thanks
FreeRTOS defines and uses a single big memory area for stack and heap management; this is simply an array of bytes, the size of which is specified by the configTOTAL_HEAP_SIZE
symbol in FreeRTOSConfig.h
. FreeRTOS allocates tasks stack in this memory area using its pvPortMalloc
function, therefore the main goal here is to place the FreeRTOS heap area into external SRAM.
The FreeRTOS heap memory area is defined in heap_*.c
(with the exception of heap_3.c
that uses the standard library malloc and it doesn't define any custom heap area), the variable is called ucHeap
. You can use your compiler extensions to set its section. For GCC, that would be something like:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ((section (".sram_data")));
Now we need to configure the linker script to place this custom section into external SRAM. There are several ways to do this and it depends again on the toolchain you're using. With GCC one way to do this would be to define a memory region for the SRAM and a section for ".sram_data"
to append to the SRAM region, something like:
MEMORY
{
...
/* Define SRAM region */
sram : ORIGIN = <SRAM_START_ADDR>, LENGTH = <SRAM_SIZE>
}
SECTIONS
{
...
/* Define .sram_data section and place it in sram region */
.sram_data :
{
*(.sram_data)
} >sram
...
}
This will place the ucHeap
area in external SRAM, while all the other text and data sections will be placed in the default memory regions (internal flash and ram).
A few notes:
xTaskCreate
)ucHeap
(i.e. ext RAM), but global variables are still allocated in internal RAM. If you still have internal RAM size issues, you can configure other global variables to be placed in the ".sram_section"
using compiler extensions (as shown for ucHeap)pvPortMalloc/vPortFree
, instead of the stdlib malloc/free
. This is because only pvPortMalloc/vPortFree
will use the ucHeap area in ext RAM (and they are thread-safe, which is a plus)pvPortMalloc/vPortFree
with different memory block sizes, consider using heap_4.c
instead of heap_2.c
. heap_2.c
has memory fragmentation problems when using several different block sizes, whereas heap_4.c
is able to combine adjacent free memory blocks into a single large blockAnother (and possibly simpler) solution would be to define the ucHeap variable as a pointer instead of an array, like this:
static uint8_t * const ucHeap = <SRAM_START_ADDR>;
This wouldn't require any special linker script editing, everything can be placed in the default sections. Note that with this solution the linker won't explicitly reserve any memory for the heap and you will loose some potentially useful information/errors (like heap area not fitting in ext RAM). But as long as you only have ucHeap
in external RAM and you have configTOTAL_HEAP_SIZE
smaller than external RAM size, that might work just fine.