STM32H Dual-Core: FreeRTOS auf beiden Kernen und Kommunikation über Shared Memory

Embedded Systems Software

Die STM32H7®-Dual-Core-Familie, zum Beispiel der H745 oder H755, trägt einen Cortex-M7® und einen Cortex-M4 auf einem Chip. Beide Kerne laufen unabhängig. Diese Betriebsart heisst AMP, asymmetrisches Multiprocessing.

Die Aufgabenteilung ist klar: Der M7 übernimmt die Rechenlast bei hoher Taktrate. Der M4 übernimmt die deterministische Peripherie-Arbeit. Für die Zusammenarbeit brauchen die Kerne geteilten Speicher und einen Weckmechanismus.

Dieser Artikel zeigt beides. Zuerst je ein FreeRTOS™ pro Kern. Dann einen gerichteten Kanal über einen FreeRTOS Message Buffer im geteilten Speicher, geweckt über die Hardware-Semaphore-Einheit.

Warum kein dedizierter DSP

Ein Cortex-M7 bringt eine doppelt genaue FPU und den DSP-Befehlssatz mit. Die typische digitale Signalkonditionierung eines Sensors läuft damit in vielen Fällen ohne separaten DSP-Baustein. Der M4 übernimmt parallel die deterministische Peripherie- und Zeitarbeit.

Zur typischen Signalkonditionierung gehören:

Für AI- und Edge-Computing mit hohem Durchsatz, also neuronale Inferenz, sind Controller dieser Klasse nicht ausgelegt. Solche Aufgaben brauchen einen dedizierten NPU- oder Edge-AI-Baustein.

Ein dedizierter DSP lohnt sich erst, wenn Durchsatz oder Latenz die Reserven des M7 übersteigen. Darunter spart die Dual-Core-Lösung einen Baustein, eine zweite Toolchain und die Kopplung zwischen zwei Chips.

Für harte Echtzeit im I/O gibt es einen weiteren Weg ohne DSP: Hardware-Parallelität statt interrupt-getriebener Software. Diesen Ansatz beschreibt unser Artikel Parallel statt unterbrochen: XMOS xCORE im Vergleich zur klassischen MCU.

Zwei Kerne, zwei RTOS-Instanzen

Jeder Kern bekommt sein eigenes FreeRTOS-Image mit eigenem Heap und eigenem Scheduler-Tick. Es gibt keinen gemeinsamen Scheduler. Ein Task auf dem M7 sieht die Tasks auf dem M4 nicht.

Der Boot läuft gestaffelt. Der M7 startet zuerst, konfiguriert Takt und Speicher und gibt danach den M4 frei. Damit steht die geteilte Umgebung, bevor der zweite Kern seinen Puffer benutzt.

Der H7 teilt den Speicher in Domänen. Das SRAM4 in der Domäne D3 ist von beiden Kernen und vom DMA erreichbar. Es eignet sich als Ablage für den geteilten Puffer.

Shared Memory richtig platzieren

Die Cache-Kohärenz ist die zentrale Falle. Der M7 hat einen L1-Datencache, der M4 hat keinen. Schreibt der M7 in den geteilten Speicher, bleibt der Wert zunächst im Cache. Der M4 liest dann den alten Wert aus dem RAM.

Es gibt zwei saubere Wege. Der erste markiert den geteilten Bereich per MPU als non-cacheable. Der zweite pflegt den Cache von Hand, mit Clean nach dem Schreiben und Invalidate vor dem Lesen.

Die erste Variante ist robuster. Eine MPU-Region für den Puffer als non-cacheable und shareable eliminiert die manuellen Cache-Operationen und ihre Fehlerquellen. Den Puffer legt der Linker per eigener Section in das SRAM4, auf beiden Images an dieselbe Adresse.

Wecken über die Hardware-Semaphore

Geteilter Speicher allein reicht nicht. Der Empfänger muss erfahren, dass Daten bereitliegen. Aktives Pollen kostet CPU-Zeit und erhöht die Latenz.

Der H7 hat eine Hardware-Semaphore-Einheit (HSEM) mit Interrupt. Kern A löst nach dem Schreiben eine Semaphore aus. Das erzeugt einen Interrupt auf Kern B. So wird der Empfänger-Task erst geweckt, wenn wirklich Daten anliegen.

Aus dem Reference Manual RM0399 kurz zusammengefasst, welche Register man tatsächlich anfasst:

Konkreter Pfad in unserem Code: nach dem Senden schreibt Kern A HSEM_R<id> = 0x0000_0000 (Release), was das Pending-Bit im HSEM_ISR2 von Kern B setzt. Der Interrupt HSEM2_IRQn feuert am Ziel-Kern, die ISR quittiert per Schreiben in HSEM_ICR2 und ruft xMessageBufferSendCompletedFromISR.

FreeRTOS Message Buffer über die Kerngrenze

Ein FreeRTOS Message Buffer ist ein Single-Writer/Single-Reader-Konstrukt. Genau das passt zu einem gerichteten Kanal von Kern A zu Kern B. Für beide Richtungen legt man zwei Buffer an.

Der Buffer, also Steuerstruktur und Datenbereich, liegt im geteilten, non-cacheable SRAM4. FreeRTOS bietet zwei Hooks für die Signalisierung: sbSEND_COMPLETED und sbRECEIVE_COMPLETED.

Man überschreibt diese Hooks. Ein Send auf Kern A löst über die Hardware-Semaphore einen Interrupt auf Kern B aus. Die ISR auf Kern B ruft xMessageBufferSendCompletedFromISR auf und weckt damit den wartenden Empfänger-Task. Der Kanal ist so blockierend und ereignisgesteuert, ohne Polling und ohne globalen Lock.

/* Geteiltes, non-cacheable SRAM4, gleiche Adresse in beiden Images */
extern MessageBufferHandle_t xCoreAtoB;   /* von CM7 erzeugt */

/* Hook: nach einem Send die Gegenseite per HSEM wecken */
#define sbSEND_COMPLETED(pxBuf)   vHsemNotify(HSEM_ID_A2B)

/* Läuft auf CM4, wenn CM7 gesendet hat */
void HSEM1_IRQHandler(void)
{
    BaseType_t xHigher = pdFALSE;
    vHsemClear(HSEM_ID_A2B);
    xMessageBufferSendCompletedFromISR(xCoreAtoB, &xHigher);
    portYIELD_FROM_ISR(xHigher);
}

/* Sender-Task auf CM7 */
xMessageBufferSend(xCoreAtoB, &frame, sizeof frame, portMAX_DELAY);

/* Empfänger-Task auf CM4 */
len = xMessageBufferReceive(xCoreAtoB, &frame, sizeof frame, portMAX_DELAY);

Die Namen vHsemNotify, vHsemClear und HSEM_ID_A2B sind Platzhalter für die Semaphore-Primitive des Projekts. Für die Gegenrichtung legt man einen zweiten Buffer und eine zweite Semaphore-ID an.

Ablauf

  1. Den Puffer per Linker-Section in das SRAM4 legen, mit gleicher Adresse in beiden Images.
  2. Eine MPU-Region für das SRAM4 als non-cacheable konfigurieren, auf beiden Kernen.
  3. Den Buffer auf genau einem Kern erzeugen, mit xMessageBufferCreateStatic. Der andere Kern wartet, bis der Buffer bereit ist.
  4. Den Semaphore-Interrupt auf beiden Kernen freischalten, je eine ID pro Richtung.
  5. sbSEND_COMPLETED so definieren, dass es die Semaphore der Gegenrichtung auslöst.
  6. In der ISR xMessageBufferSendCompletedFromISR mit dem Handle des lokalen Buffers aufrufen.

Fallen

Lehre

Der Dual-Core-H7 trennt Rechnen und Echtzeit-Peripherie sauber auf zwei Kerne. Die Kopplung braucht keine fremde Middleware. Ein FreeRTOS Message Buffer im geteilten, non-cacheable SRAM4, geweckt über die Hardware-Semaphore, reicht für einen robusten Kanal.

Der grösste Stolperstein ist nicht die Logik des Kanals, sondern die Cache-Kohärenz. Wer den geteilten Bereich von Anfang an non-cacheable hält, umgeht die häufigste Fehlerquelle im Dual-Core-Design.

Der H7 spielt seine Stärken dort aus, wo Rechenleistung führt: FFT-lastige Signalverarbeitung, hoher Kommunikationsdurchsatz, Grafik-HMI, mehrere komplexe Protokolle parallel. Für dieses Profil ist die Dual-Core-Aufteilung mit einem RTOS pro Kern die saubere Antwort.

Schlüsselwörter: STM32H7 Dual-Core, Cortex-M7, Cortex-M4, FreeRTOS Message Buffer, Shared Memory, SRAM4 non-cacheable, Hardware-Semaphore HSEM, AMP asymmetrisches Multiprocessing, Cache-Kohärenz, dsPIC33CH, ASIL-B ASIL-C, Embedded Consulting Schweiz, swissEmbedded Firmware Entwicklung

Zwei Kerne sauber koppeln, ohne Cache-Fallen und ohne fremde Middleware? Wir setzen FreeRTOS auf beiden Kernen des STM32H7 auf, richten Shared Memory und HSEM ein und liefern das Ganze mit Test-Bench. Oder wir zeigen euch, wann der dsPIC33CH mit ASIL-Hardware der bessere Griff ist.

Kontaktieren Sie uns →