🛠 TCM, DMA et cache sur le STM32H743ZI : placer ses données pour un temps réel déterministe

STM32H7 Nucleo-144 (porte-drapeau : NUCLEO-H743ZI) · avance · Schéma : IMG-stm32h7-nucleo-avance-3-stm32h743zi-tcm-dma-cache.svg — carte mémoire du STM32H743ZI : 192 Ko de TCM RAM en accès cycle unique à côté du Cortex-M7, 864 Ko de SRAM utilisateur reliés par l'interconnect AXI/MBX, contrôleur DMA à 16 canaux traversant les zones cacheables, flat vector style, étiquettes en français, fond blanc.

What you will build

The H743ZI is a memory engineer's MCU: 192 KB of TCM RAM (tightly-coupled, deterministic single-cycle access) plus up to 864 KB user SRAM and 4 KB backup SRAM, wired by an AXI/MBX interconnect, with a 16-channel DMA engine and a Cortex-M7 that caches aggressively. This tutorial places code and data on purpose instead of by accident — the difference between a deterministic control loop and a heisenbug.

The rules of the house

  1. TCM for what must never miss: ISRs, time-critical loops and task stacks placed in TCM get deterministic timing — the board sheet calls out TCM RAM for deterministic real-time execution as a headline feature.
  2. DMA buffers must be cache-safe: with the M7 data cache on, a buffer in cacheable SRAM can be read stale by the CPU or overwritten by the DMA before eviction. Either place buffers in non-cacheable MPU regions, or manage maintenance explicitly.
  3. The errata are real: ST's errata sheet ES0392 (H742/H743/H750/H753) must be read before using DMA/cache-heavy or Ethernet corner cases.
  1. Benchmark only after checking clocks: Zephyr boots at 96 MHz, the STM32duino core at 480 MHz (VOS0, AHB at HCLK/2 = 240 MHz). Same silicon, 5× the score — clock first, numbers second.

Step 1 — Place things explicitly

/* GCC linker-section attributes (linker script must define the regions). */
__attribute__((section(".dtcmram")))
volatile uint32_t ctrl_state;          /* hot state in TCM: single-cycle */

__attribute__((section(".dma_buffers")))   /* MPU-marked Non-cacheable */
uint8_t spi_dma_buf[4096];

void __attribute__((section(".itcmram"), optimize("Ofast")))
fast_control_loop(void) {              /* time-critical loop out of flash */
  for (uint32_t i = 0; i < 64; i++) acc += k[i];
}

Step 2 — Prove determinism

  1. Run the control loop from flash with cache on, toggle a GPIO per iteration, measure jitter on a scope.
  2. Move the loop to ITCM and the data to DTCM ; re-measure — the jitter band collapses because neither fetch nor load crosses the cached AXI path.
  3. Add a DMA stream hammering the AXI/MBX interconnect: the flash-cached version degrades, the TCM version does not.

Step 3 — Cache maintenance when you cannot move the buffer

/* Clean before DMA transmit, invalidate before CPU read after receive. */
SCB_CleanDCache_by_Addr(spi_dma_buf, sizeof spi_dma_buf);
HAL_SPI_Transmit_DMA(&hspi1, spi_dma_buf, len);
/* ... on transfer-complete callback: */
SCB_InvalidateDCache_by_Addr(spi_dma_buf, sizeof spi_dma_buf);

Keep buffers 32-byte aligned and padded to cache-line size so neighbouring variables never share a line with a DMA buffer.

Expected result

A control path whose worst-case timing does not move when the rest of the system gets busier — plus a written map of which regions your linker script declares cacheable or not.

Common pitfalls

Autres tutoriels pour cette carte

Exemples de code pour cette carte

← Retour à la fiche 🛠 Tous les tutoriels