Using Multiple Memory Regions with the FreeRTOS Heap

ARM Cortex-M microcontrollers can have multiple memory controllers. This is a good thing as it allows the hardware to do multiple parallel memory read/writes. However this makes the memory map more complicated for the software: it divides the memory into different regions and memory segments.  This article is about how to enable FreeRTOS to use multiple memory blocks for a virtual combined memory heap:

FreeRTOS with Segmented Heap Memory

FreeRTOS with Segmented Heap Memory

Segmented SRAM

For example the an NPX Kinetis K64F has 256KByte of SRAM, but the biggest memory object or array I can use is only 192 KByte. How is that?

Because the device has two memory controllers, ony for the UPPER and one for the LOWER memory area, with a division at address 0x2000’0000. This manifests in two memory areas in the linker file:

 SRAM_UPPER (rwx) : ORIGIN = 0x20000000, LENGTH = 0x30000 /* 192K bytes (alias RAM) */ 
 SRAM_LOWER (rwx) : ORIGIN = 0x1fff0000, LENGTH = 0x10000 /* 64K bytes (alias RAM2) */

One might think that these two areas could be combined, as they are adjacent. Wrong, as this will very likely will result in a hard fault. I cannot have memory objects crossing that memory boundary at 0x2000’0000. For a discussion on that topic see https://mcuoneclipse.com/2013/07/10/freertos-heap-with-segmented-kinetis-k-sram/

So how to use more than one half of the memory? FreeRTOS has the solution with the Heap_5 memory scheme.

heap_5

FreeRTOS offers different memory management schemes, and one of it the heap_5.

From http://www.freertos.org/a00111.html:

This scheme … allows the heap to span multiple non adjacent (non-contiguous) memory regions.

To use Scheme 5, make sure heap_5.c is used in the application:

FreeRTOS Heap_5.c

FreeRTOS Heap_5.c

If using the McuOnEclipse FreeRTOS port for Processor Expert, select Scheme 5:

FreeRTOS Heap_5.c in Processor Expert

FreeRTOS Heap_5.c in Processor Expert

Memory Variables

Next define memory arrays like below. I’m using the industry standard GNU toolchain for ARM microcontroller with __attribute__ to allocate variables in a specific segment (see Defining Variables at Absolute Addresses with gcc):

 
static __attribute__ ((used,section(".noinit.$SRAM_LOWER_Heap5"))) uint8_t heap_sram_lower[50*1024]; /* placed in in no_init section inside SRAM_LOWER */
static __attribute__ ((used,section(".noinit_Heap5"))) uint8_t heap_sram_upper[128*1024]; /* placed in in no_init section inside SRAM_UPPER */

The above reserves one block of 50 KByte and one block of 128 KByte.

The section name I have used heavily depends on the linker file used. For the above case I have used the Eclipse based NXP MCUXpresso IDE 10.0.2 with GNU Linker file. That linker file uses two ‘no-init’ memory placements:

 .noinit_RAM2 (NOLOAD) : ALIGN(4)
 {
 *(.noinit.$RAM2*)
 *(.noinit.$SRAM_LOWER*)
 . = ALIGN(4) ;
 } > SRAM_LOWER

and

 .noinit (NOLOAD): ALIGN(4)
 {
 _noinit = .;
 *(.noinit*) 
 . = ALIGN(4) ;
 _end_noinit = .;
 } > SRAM_UPPER

‘No-init’ means that the startup code will *not* initialize the variable/RAM inside that section (see GNU Linker, can you NOT Initialize my Variable?). Initialization of the heap memory is not needed and it only would slow down the startup of the program.

HeapRegion_t

Next I list the heap regions in a table of type HeapRegion_t:

 
static HeapRegion_t xHeapRegions[] =
{
 { &heap_sram_lower[0], sizeof(heap_sram_lower)},
 { &heap_sram_upper[0], sizeof(heap_sram_upper)},
 { NULL, 0 } // << Terminates the array.
};

That table lists the regions, starting with the lower address block. Each entry has a pointer to the start of the memory block and its size. The list is terminated by a NULL sentinel.

Defining FreeRTOS Regions

To tell FreeRTOS about the memory regions, I have to call vPortDefineHeapRegions() with a pointer to the description table:

 
vPortDefineHeapRegions(xHeapRegions); // Pass the array into vPortDefineHeapRegions(). Must be called first!

The important thing is that this has to be called *before* any FreeRTOS memory allocation (task, semaphore, mutex, queue, …) is performed! I usually call it right after main() is called.

Using Multiple FreeRTOS Memory Regions

After that, FreeRTOS can be used with multiple many regions, as if it would be only a single heap memory region:

FreeRTOS with Segmented Heap Memory

FreeRTOS with Segmented Heap Memory

The memory is still divided into pieces, but the total amount of memory available is now the sum of the pieces. Of course memory is still fragmented, and this defines the largest piece of memory I can allocate.

Summary

Using the heap_5 allows me to use a FreeRTOS heap to span multiple memory regions: exactly what I need for devices which have multiple RAM memory controllers or which have gaps between the SRAM regions. With heap_5 I still cannot have single objects larger than the largest memory region, but it allows me to use multiple non-contiguous areas to be handled in a safe way.

I have posted the example project used in this article on GitHub.

Happy Heaping 🙂

Links

5 thoughts on “Using Multiple Memory Regions with the FreeRTOS Heap

  1. Dear Erich,

    thank you for all your posts and blogs. I’m learning a lot from it.
    I have a question that is somehow related to this post.
    In one of my application I have to use functions like sprintf() in several FreeRTOS tasks. As you know these functions consume massively stack memory. That results in large task-heap of each FreeRTOS task that is using it. I was looking for a possibility to move the stack demand of functions like sprintf() outside of the FreeRTOS global heap. So if I protect the function call to functions with a high stack demand with a semaphore I have to reserve the stack size only once. Do you have any idea how to do this?

    Best Regards
    Markus

    Like

    • Hi Markus,
      I recommend that you do not use printf() and its variants, with the stack needs just as one reason. Better use variants like xprintf (see https://mcuoneclipse.com/2014/08/17/xformat-a-lightweight-printf-and-sprintf-alternative/).
      Otherwise, if you *really* (???) want to use snprintf() or sprintf(), then use them from one task only. All the other tasks pass the format string and the parameters using a message queue to the task. The ‘printf’ task takes the messages, processes the sprintf()/etc and passes back with another queue the data to the caller. Doable, maybe a few hours to implement it. Again, it would be much better if you use xprintf instaed.

      Like

  2. Hi,
    Thank you for your explanations, this is exactly what I need!
    I have one question though. Is there a way to control in which heap the memory
    allocations are performed? I’m working with a fragmented RAM and I have a DMA that is configurated for one specific fragment. Therefore I need to be assured that the elements needing to perform DMA transfers are placed in the right fragment otherwise this results in an error due to the AHB bus matrix topology. I’m woking with a STM32F7 series processor.

    I’m thrilled to hear from you
    Colin

    Like

  3. Pingback: New NXP MCUXpresso IDE v11.0 | MCU on Eclipse

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.