STM32H dual-core: FreeRTOS on both cores and communication via shared memory

Embedded Systems Software

The STM32H7® dual-core family, for example the H745 or H755, carries a Cortex-M7® and a Cortex-M4 on one chip. Both cores run independently. This mode is called AMP, asymmetric multiprocessing.

The split of duties is clear: the M7 handles the compute load at high clock rate. The M4 handles the deterministic peripheral work. For cooperation the cores need shared memory and a wake-up mechanism.

This article shows both. First one FreeRTOS™ per core. Then a directed channel via a FreeRTOS message buffer in shared memory, woken up via the hardware semaphore unit.

Why no dedicated DSP

A Cortex-M7 comes with a double-precision FPU and the DSP instruction set. Typical digital signal conditioning of a sensor runs on it in many cases without a separate DSP part. The M4 handles the deterministic peripheral and timing work in parallel.

Typical signal conditioning includes:

For AI and edge computing with high throughput, that is neural inference, controllers of this class are not intended. Such tasks need a dedicated NPU or edge-AI part.

A dedicated DSP is worth its weight only when throughput or latency exceed the reserves of the M7. Below that, the dual-core solution saves a chip, a second toolchain and the coupling between two chips.

For hard real-time in I/O there is another route without a DSP: hardware parallelism instead of interrupt-driven software. This is described in our article Parallel instead of interrupted: XMOS xCORE compared to the classic MCU.

Two cores, two RTOS instances

Each core gets its own FreeRTOS image with its own heap and its own scheduler tick. There is no shared scheduler. A task on the M7 does not see the tasks on the M4.

The boot is staggered. The M7 starts first, configures the clock and the memory and then releases the M4. This way the shared environment is in place before the second core uses its buffer.

The H7 splits the memory into domains. The SRAM4 in domain D3 is reachable by both cores and by the DMA. It is well suited as the location for the shared buffer.

Place shared memory correctly

Cache coherence is the central pitfall. The M7 has an L1 data cache, the M4 has none. If the M7 writes to shared memory, the value stays in the cache first. The M4 then reads the old value from RAM.

There are two clean ways. The first marks the shared region as non-cacheable via the MPU. The second maintains the cache manually, with a clean after the write and an invalidate before the read.

The first variant is more robust. An MPU region for the buffer as non-cacheable and shareable eliminates the manual cache operations and their error sources. The linker places the buffer via its own section into SRAM4, at the same address in both images.

Wake-up via the hardware semaphore

Shared memory alone is not enough. The receiver has to be told that data is ready. Active polling costs CPU time and increases latency.

The H7 has a hardware semaphore unit (HSEM) with an interrupt. Core A raises a semaphore after the write. That produces an interrupt on core B. This way the receiver task is only woken up when data is actually pending.

Briefly summarised from reference manual RM0399, the registers you actually touch:

Concrete path in our code: after sending, core A writes HSEM_R<id> = 0x0000_0000 (release), which sets the pending bit in core B’s HSEM_ISR2. Interrupt HSEM2_IRQn fires on the target core, the ISR acknowledges by writing to HSEM_ICR2 and calls xMessageBufferSendCompletedFromISR.

FreeRTOS message buffer across the core boundary

A FreeRTOS message buffer is a single-writer / single-reader construct. That fits exactly to a directed channel from core A to core B. For both directions you allocate two buffers.

The buffer, meaning the control structure and the data area, sits in the shared, non-cacheable SRAM4. FreeRTOS offers two hooks for the signalling: sbSEND_COMPLETED and sbRECEIVE_COMPLETED.

You override these hooks. A send on core A triggers an interrupt on core B via the hardware semaphore. The ISR on core B calls xMessageBufferSendCompletedFromISR and wakes up the waiting receiver task. The channel is thereby blocking and event-driven, without polling and without a global lock.

/* Shared, non-cacheable SRAM4, same address in both images */
extern MessageBufferHandle_t xCoreAtoB;   /* created by CM7 */

/* Hook: after a send wake the other side via HSEM */
#define sbSEND_COMPLETED(pxBuf)   vHsemNotify(HSEM_ID_A2B)

/* Runs on CM4 when CM7 has sent */
void HSEM1_IRQHandler(void)
{
    BaseType_t xHigher = pdFALSE;
    vHsemClear(HSEM_ID_A2B);
    xMessageBufferSendCompletedFromISR(xCoreAtoB, &xHigher);
    portYIELD_FROM_ISR(xHigher);
}

/* Sender task on CM7 */
xMessageBufferSend(xCoreAtoB, &frame, sizeof frame, portMAX_DELAY);

/* Receiver task on CM4 */
len = xMessageBufferReceive(xCoreAtoB, &frame, sizeof frame, portMAX_DELAY);

The names vHsemNotify, vHsemClear and HSEM_ID_A2B are placeholders for the semaphore primitives of the project. For the reverse direction you allocate a second buffer and a second semaphore ID.

Sequence

  1. Place the buffer via a linker section into SRAM4, at the same address in both images.
  2. Configure an MPU region for SRAM4 as non-cacheable, on both cores.
  3. Create the buffer on exactly one core, with xMessageBufferCreateStatic. The other core waits until the buffer is ready.
  4. Enable the semaphore interrupt on both cores, one ID per direction.
  5. Define sbSEND_COMPLETED so it raises the semaphore of the opposite direction.
  6. In the ISR call xMessageBufferSendCompletedFromISR with the handle of the local buffer.

Pitfalls

Takeaway

The dual-core H7 splits compute and real-time peripherals cleanly across two cores. The coupling does not need external middleware. A FreeRTOS message buffer in the shared, non-cacheable SRAM4, woken via the hardware semaphore, is enough for a robust channel.

The biggest pitfall is not the logic of the channel but cache coherence. Whoever keeps the shared region non-cacheable from the start avoids the most common source of errors in a dual-core design.

The H7 plays its strengths where compute performance leads: FFT-heavy signal processing, high communication throughput, graphics HMI, several complex protocols in parallel. For that profile the dual-core split with one RTOS per core is the clean answer.

Keywords: STM32H7 dual-core, Cortex-M7, Cortex-M4, FreeRTOS message buffer, shared memory, SRAM4 non-cacheable, hardware semaphore HSEM, AMP asymmetric multiprocessing, cache coherence, dsPIC33CH, ASIL-B ASIL-C, embedded consulting Switzerland, swissEmbedded firmware engineering

Two cores coupled cleanly, without cache traps and without external middleware? We bring up FreeRTOS on both cores of the STM32H7, set up shared memory and HSEM, and deliver the whole thing with a test bench. Or we show you when the dsPIC33CH with ASIL hardware is the better grip.

Contact us →