Close

Managing cross-core memory allocation

A project log for T76 Instrument Core: Pro instruments on Pi Pico 2

A C++ framework for building real-time, pro-grade digital instruments on the RP2350 / Pi Pico 2 platform.

marco-tabiniMarco Tabini 10/17/2025 at 14:260 Comments

The Pico SDK’s memory allocation routines are fully reentrant and can normally be used safely from multiple cores without worrying about race conditions. Once FreeRTOS enters the picture, however, things get more complicated.

Delegating memory management to FreeRTOS

FreeRTOS maintains its own heap and allocation routines (pvPortMalloc() and pvPortFree()), offering several heap management strategies. The most common is Heap 4, which minimizes fragmentation to reduce long-term out-of-memory errors.

Typically, the system reserves a fixed heap size at startup (via configTOTAL_HEAP_SIZE), and multitasking code then uses the pvPort* routines to allocate and free memory.

In practice, this creates two separate heaps—one managed by the SDK, the other by FreeRTOS—introducing unnecessary complexity. You must now predict and manage how much memory belongs to each heap, and deal with fragmentation and hard-to-debug crashes that can result from it.

My preferred approach is to delegate all memory management to FreeRTOS by overriding the system’s default allocation functions (malloc, free, new, delete) and redirecting them to their pvPort* equivalents.

This keeps memory handling consistent and lets us take advantage of FreeRTOS’s runtime diagnostics for heap integrity and allocation failure handling.

Configuring the Pico SDK

To implement this, add the following settings to your project’s CMakeLists.txt:

We can then write our custom allocation wrappers, which you will find in lib/sys/memory.cpp.

A bare-metal monkey wrench

In this setup, core 1 runs bare-metal code outside FreeRTOS’s control, which creates a problem: the FreeRTOS heap manager has no awareness of memory operations performed on that core.

For example, pvPortMalloc() guards the heap with a critical section to prevent concurrent access from other FreeRTOS tasks. But since core 1 operates outside the OS, those protections don’t apply. If core 1 modifies the heap while FreeRTOS is active on core 0, the system will eventually crash—often in ways that are difficult to reproduce due to timing interactions between the cores.

Possible solutions

There are probably several solutions to this problem, but I landed on a couple different ones:

T76_USE_GLOBAL_LOCKS is primarily designed for circumstances where we want to be able to allocate memory at startup, when the performance hit on core 1 operations is not important (although note that allocations will block on core 1 until the FreeRTOS scheduler starts running). Core 0 performance will remain relatively unaffected, since memory operations performed inside it will just translate into direct calls to pvPort* functions.

You will find these memory additions in this pull request.

Discussions