evading
evading

Reputation: 3090

STM32F4 running FreeRTOS in external RAM

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

Upvotes: 1

Views: 5739

Answers (2)

Ettore Barattelli
Ettore Barattelli

Reputation: 209

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:

  • make sure you initialize the SRAM controller/FSMC prior to calling any FreeRTOS function (like xTaskCreate)
  • once you start the tasks, all stack allocated variables will be placed in 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)
  • if your code uses dynamic memory allocation, make sure you use 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)
  • if you're doing a lot of dynamic task creation/deletion and memory allocation with 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 block

Another (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.

Upvotes: 5

Richard
Richard

Reputation: 3236

When the application starts up it will try to initialise data by either clearing it to zero, or initialising it to a non-zero value, depending on the section the variable is placed in. Using a normal run time model, that will happen before main() is called. So you have something like:

1) Reset vector calls init code 2) C run time init code initialises variables 3) C run time init code calls main()

If you use the linker to place variables in external RAM then you need to ensure the RAM is accessible before that initialisation takes place, otherwise you will get a hard fault. Therefore you need to either have a boot loader that sets up the system for you, then starts your application....or more simply just edit the start up code to do the following:

1) Reset vector calls init code 2) >>>C run time init code configures external RAM<<< 3) C run time init code initialised variables 4) C run time init code calls main().

That way the RAM is available before you try to access it.

However, if all you want to do is have the FreeRTOS heap in external RAM, then you can leave the init code untouched, and just use an appropriate heap implementation - basically one that does not just declare a large static array. For example, if you use heap_5 then all you need to do is ensure the heap init function is called before any allocation is performed, because the heap init just describes which RAM to use as the heap, rather than statically declaring the heap.

Upvotes: 2

Related Questions