How Does the STM32H7 Dual-Core Cortex-M7/Cortex-M4 Architecture Work?
Last updated 19 August 2026 · 12 min read
Direct Answer
The dual-core STM32H745/H755 and STM32H747/H757 devices pair a Cortex-M7 (up to 480 MHz) with a Cortex-M4 (up to 240 MHz) on one die, each with its own firmware image. By default the Cortex-M7 boots first and holds the Cortex-M4 in reset; the M7 firmware releases the M4 by setting the BOOT_C2 bit in the RCC_GCR register once shared clock and power configuration is complete, though option bytes (BCM7/BCM4) can instead let both cores boot independently. Memory is split across three power domains — D1 (Cortex-M7, AXI SRAM, and the M7-exclusive ITCM/DTCM), D2 (Cortex-M4 and shared SRAM1–3), and D3 (low-power SRAM4, reachable by both cores) — and flash is split into two banks, one per core in the common linker configuration. The HSEM (hardware semaphore) peripheral provides 32 semaphores with interrupt-driven lock/release signalling that both cores use to arbitrate shared resources, most importantly the RCC and PWR registers that both cores can otherwise corrupt with simultaneous writes.
Detailed Explanation
STMicroelectronics' STM32H745/H755 and STM32H747/H757 lines put two independent Arm cores on one die: a Cortex-M7 running at up to 480 MHz and a Cortex-M4 running at up to 240 MHz, each with its own clock tree branch, its own NVIC, and its own compiled firmware image (per RM0399 and the STM32H747/STM32H757 datasheet). This is a different split from the single-core STM32H7 parts (STM32H742/743/753/750, documented in RM0433) covered in Which STM32 Family Should You Use?. The dual-core parts add a second, independently clocked processor rather than simply raising the clock speed of one core.
The closest architectural parallel on this site is Nordic's nRF5340, covered in how nRF Connect SDK firmware is structured for the nRF5340's dual-core architecture. Both parts split firmware into two images built and flashed together, and both use a hardware mailbox mechanism for inter-core signalling. The STM32H7's asymmetric M7/M4 split targets general-purpose compute partitioning: heavy application logic and a display or graphics stack on the M7, hard real-time control loops or a communication stack on the M4. That's a different problem from the nRF5340's radio-versus-application split, and STM32H7 exposes the underlying boot and memory-partitioning mechanics directly to firmware rather than hiding them behind a build-system abstraction like sysbuild.
The Dual-Core Boot Sequence
STM32H7 dual-core devices control which core boots, and in what order, through two mechanisms working together: option bytes set at programming time, and a runtime register the booting core writes to release the other core.
Option bytes: BCM7 and BCM4. Each core has a corresponding option byte (BCM7 for the Cortex-M7, BCM4 for the Cortex-M4) that determines whether that core is allowed to boot automatically at power-on reset. The factory default configuration sets BCM7 = 1 and BCM4 = 0: at power-on, only the Cortex-M7 begins fetching its reset vector and executing; the Cortex-M4 stays held in reset. Setting both BCM7 = 1 and BCM4 = 1 in the option bytes instead lets both cores boot independently and simultaneously from their own configured boot addresses, without either one waiting on the other.
Runtime release: RCC_GCR BOOT_C2. In the default (CM7-boots-first) configuration, the Cortex-M4 does not start executing until the Cortex-M7's firmware explicitly releases it, by setting the BOOT_C2 bit in the RCC global control register (RCC_GCR). STM32CubeH7's HAL exposes this as HAL_RCCEx_EnableBootCore(RCC_BOOT_C2). The typical CM7 startup sequence is: configure the system clock and any shared power/voltage scaling settings that both cores depend on, then call this function to bring the Cortex-M4 out of reset. The SYSCFG_UR1 register's BCM4 bit reflects the current boot-enable state of the Cortex-M4 and can be read back to confirm the release took effect.
This CM7-first, CM7-releases-CM4 pattern is the configuration ST's own STM32CubeH7 examples and most third-party BSPs (including Zephyr's STM32H7 dual-core support) default to, because it gives firmware an explicit, single-threaded window to finish clock and shared-resource setup before a second core can touch anything. Booting both cores simultaneously (BCM7 = BCM4 = 1) removes that window, so it is only appropriate when both cores' startup code is written to tolerate running concurrently from reset, including, in most designs, arbitrating early RCC/PWR access through HSEM (covered below) from the very first instructions each core executes.
Boot address selection. Each core has its own pair of boot-address option bytes (analogous in purpose to the single-core STM32's BOOT0 pin behaviour, described in how the STM32 DFU bootloader flashes firmware), letting the Cortex-M7 and Cortex-M4 boot from different flash banks or from the system memory bootloader independently of each other. In the common project layout, the Cortex-M7's boot address points at flash bank 1 and the Cortex-M4's at flash bank 2, so each core's image lives in its own bank and neither core's linker script needs to know where the other core's code sits.
RAM and Flash Partitioning Across D1/D2/D3
STM32H7 dual-core devices organise memory across three independent power domains, D1, D2, and D3, each of which can be clocked and powered down separately (per RM0399). This domain split exists primarily for power management, but it also determines which memory each core can reach quickly and which memory is genuinely shared.
D1 domain: Cortex-M7's domain. D1 contains the Cortex-M7 core, the AXI SRAM (up to 512 KB on the largest H747/H757 parts, device-dependent), and the M7's tightly coupled memories: ITCM (instruction TCM, up to 64 KB) and DTCM (data TCM, up to 128 KB). ITCM and DTCM connect directly to the Cortex-M7's core bus and are not reachable by the Cortex-M4 or by any DMA controller. That is the same restriction already documented for single-core STM32H7/F7 parts in how to configure STM32 HAL DMA, which is why a DMA buffer accidentally placed in DTCM produces a transfer error on any STM32H7 part, dual-core or not. AXI SRAM is reachable by both cores via the inter-domain bus matrix and is commonly used as general-purpose working RAM for the Cortex-M7, though the Cortex-M4 can access it too, just at lower throughput across the domain boundary.
D2 domain: Cortex-M4's domain and shared peripherals. D2 contains the Cortex-M4 core along with SRAM1, SRAM2, and SRAM3 (128 KB, 128 KB, and 32 KB respectively on H747/H757, device-dependent) and most communication peripherals (USB, Ethernet, SPI/I2S, CAN). These three SRAM blocks are the most commonly used memory for inter-core shared buffers in ST's reference examples, since both cores reach them over the standard AHB bus matrix without the ITCM/DTCM restriction.
D3 domain: low-power, always-reachable memory. D3 holds SRAM4 (up to 64 KB) plus a small battery-backed SRAM, along with low-power peripherals (RTC, low-power timers, the backup domain). D3 stays powered in the lowest-power STOP-mode configurations that keep the backup domain alive (see how to configure STM32 low-power modes for the mode ladder this applies to), which makes SRAM4 a common place to put state that must survive both cores briefly losing power in D1 or D2 during a low-power transition.
Flash partitioning. The 2 MB flash (device-dependent; smaller dual-core parts have less) is organised as two banks. The standard project structure assigns one bank to each core's firmware image, Cortex-M7 code and constants in bank 1, Cortex-M4 code and constants in bank 2, configured through each core's independent boot-address option bytes described above. Both banks are, at the hardware level, readable by both cores; the split is a firmware and linker-script convention that keeps the two images from overlapping, not a hardware access restriction the way ITCM/DTCM is.
The practical implication for linker scripts and MPU configuration: a Cortex-M4 firmware image should never assume it can allocate stack, heap, or DMA buffers in ITCM or DTCM (they simply are not visible to it), and should default to SRAM1–3 or AXI SRAM for anything the Cortex-M7 also needs to read or write. See how the memory map works in an embedded microcontroller for the general Cortex-M memory-map concepts this domain structure builds on.
The HSEM Peripheral for Inter-Core Synchronization
Because the Cortex-M7 and Cortex-M4 share access to the same RCC, PWR, and several other system registers, and can both read and write most SRAM regions, STM32H7 dual-core devices need a hardware-level way to arbitrate access rather than relying on software conventions alone. A bus transaction from one core has no visibility into what the other core is doing at the same instant. The HSEM (hardware semaphore) peripheral, documented in RM0399, provides this: 32 independent semaphores (IDs 0–31), each with its own lock register that records which core (and, optionally, which software process) currently holds it.
Two locking procedures. HSEM supports a two-step take (HAL_HSEM_Take(), which writes a process ID and then must be confirmed with HAL_HSEM_IsSemTaken()) and a one-step "fast take" (HAL_HSEM_FastTake()), which attempts the lock in a single atomic read-modify-write and returns success or failure immediately. Both are non-blocking: a core that fails to acquire a semaphore gets an immediate failure return rather than stalling, and is expected to retry, back off, or fall back to a wait state depending on the application.
Interrupt-driven release notification. Each core can enable a per-semaphore interrupt with HAL_HSEM_ActivateNotification(); when the other core releases that semaphore with HAL_HSEM_Release(), the waiting core's HSEM interrupt fires and its HAL_HSEM_FreeCallback() runs. This is the mechanism most STM32H7 dual-core examples use to avoid polling: one core takes a semaphore, writes to a shared buffer, then releases the semaphore as a signal that the buffer is ready, and the other core, already waiting on that semaphore's interrupt, wakes and reads it. It plays a broadly similar role to the shared-memory mailbox interrupt that underlies the nRF5340's IPC service, though on STM32H7 it is exposed as a general-purpose peripheral rather than wrapped in a higher-level messaging API by default.
Typical use cases:
- Protecting shared RCC/PWR register writes. Both cores can independently enable peripheral clocks or adjust power settings through the same RCC and PWR registers. A semaphore (commonly reserved for exactly this purpose in ST's reference firmware) wrapped around any such access prevents one core's read-modify-write from silently overwriting the other core's change. See the FAQ below for the failure mode this avoids.
- Mailbox-style data exchange. A shared SRAM1–3 region combined with an HSEM lock/release pair implements a lightweight mailbox: one core writes a message, releases the semaphore, and the other core's interrupt-driven callback picks it up. This is the pattern most application-level inter-core communication on STM32H7 is built on, whether used directly or wrapped by a higher-level protocol like OpenAMP/RPMsg.
- Coordinating the boot handoff itself. Beyond RCC_GCR's BOOT_C2 release, some designs use an HSEM semaphore as a "core is ready" flag the newly-released Cortex-M4 sets once its own clock and peripheral initialisation is complete, letting the Cortex-M7 confirm the handoff succeeded rather than assuming it did.
Design Considerations
- Keep the default CM7-first boot sequence unless there's a specific reason not to. It gives the Cortex-M7 a guaranteed single-threaded window to finish shared clock and power setup before the Cortex-M4 can interfere, avoiding an entire class of early-boot race conditions that a simultaneous-boot configuration (BCM7 = BCM4 = 1) would otherwise need to handle in software from the first instruction.
- Reserve a semaphore for shared RCC/PWR access on day one, not after a hard-to-reproduce clock bug appears. Retrofitting HSEM protection around scattered
RCC_APBxENR-style clock-enable calls after both cores are already writing to them independently is far more error-prone than establishing the convention before either core's peripheral init code is written. - Place inter-core shared buffers in SRAM1–3 (D2) or AXI SRAM (D1), never in ITCM/DTCM. The Cortex-M4 and any DMA controller cannot reach the Cortex-M7's tightly-coupled memories at all; a buffer placed there by an M7-only allocation habit will simply be invisible to the M4 side.
- Treat the Cortex-M7's D-cache as a coherency hazard for HSEM-guarded shared memory, the same way it is for DMA buffers. A Cortex-M7 write to a shared SRAM mailbox can sit in D-cache before being flushed to SRAM; if the release semaphore fires before the write is visible in SRAM, the Cortex-M4 can read stale data even though the lock protocol behaved correctly. Clean the relevant cache lines before releasing the semaphore, using the same
SCB_CleanDCache_by_Addr()approach documented for DMA buffers in how to configure STM32 HAL DMA. - Bring up and debug each core independently before integrating them. ST-Link and STM32CubeIDE can attach to either core's debug port separately; validating each core's own peripheral and clock configuration in isolation, before the two are exchanging data over HSEM, makes it far easier to tell whether a fault originates in one core's firmware or in the inter-core protocol itself.
- Architecting and debugging STM32H7 dual-core firmware, including the boot handoff and inter-core HSEM protocol, is part of the embedded firmware work Zeus Design does for commercial hardware products.
Common Mistakes
- Assuming the Cortex-M4 can access ITCM or DTCM because the Cortex-M7 can. These tightly-coupled memories are wired directly to the Cortex-M7's core bus; a shared data structure or DMA buffer placed there is silently unreachable from the Cortex-M4 side and from any DMA controller, producing a fault or stale data that looks like a logic bug rather than a memory-placement mistake.
- Not protecting shared RCC/PWR writes with HSEM and hitting an intermittent, timing-dependent clock fault. Because the failure only appears when both cores happen to touch the same register within a few cycles of each other, it can pass testing for a long time before surfacing in the field. See the FAQ above for the exact mechanism.
- Porting single-core STM32H7 startup code to one core of a dual-core part without accounting for the other core's boot state. Code that assumes it is the only core running, such as code that blocks waiting on a peripheral the other core hasn't finished configuring yet, can hang or behave inconsistently depending on how fast the other core reaches the same point.
- Forgetting to flush the Cortex-M7 D-cache before releasing an HSEM lock on a shared buffer. The lock/release sequence completes correctly, but the Cortex-M4 reads pre-write data from SRAM because the Cortex-M7's write is still sitting in a dirty cache line. It is the same cache-coherency hazard documented for DMA on STM32H7/F7, just triggered through HSEM instead of a DMA transfer.
- Choosing simultaneous dual-core boot (BCM7 = BCM4 = 1) without redesigning early startup code for concurrency. This configuration is available and sometimes appropriate, but code copied from a CM7-first reference project usually assumes it has exclusive early access to shared registers, an assumption that no longer holds once both cores start at the same time.
Frequently Asked Questions
- Can both the Cortex-M7 and Cortex-M4 boot at the same time on power-up?
- Yes, if the BCM7 and BCM4 option bytes are both set. This makes each core boot independently from its own configured boot address at power-on reset, rather than the default arrangement where the Cortex-M7 boots alone and releases the Cortex-M4 later under firmware control. Booting both cores simultaneously removes the CM7's role as gatekeeper, so any shared peripheral or clock configuration that assumed a fixed boot order needs to be re-checked; most reference designs keep the default CM7-first sequence specifically to avoid that class of race condition.
- Do I need HSEM if I am also using OpenAMP or a shared-memory IPC protocol between the cores?
- Usually yes, at a different layer. OpenAMP-based messaging (as used in some STM32H7 dual-core examples, mirroring the pattern on Nordic's nRF5340) handles structured message passing over a shared-memory ring buffer, but the underlying shared-memory region and any shared peripheral registers still need arbitration to avoid one core reading a buffer mid-write by the other. HSEM is the mechanism ST's HAL and middleware use for that lower-level arbitration and for the interrupt that wakes the receiving core; OpenAMP's virtio/rpmsg layer on STM32H7 is typically built on top of HSEM notifications rather than replacing them.
- What happens if I don't protect shared RCC or PWR register writes with HSEM?
- Both cores share a single set of RCC and PWR registers. If the Cortex-M7 and Cortex-M4 both perform a read-modify-write on the same register at the same time (enabling different peripheral clocks via RCC_APBxENR, for example), one core's write can silently overwrite the other's, leaving a peripheral clock disabled that the application believes is enabled. This produces intermittent, hard-to-reproduce faults that only appear when both cores happen to reconfigure clocks close together in time. ST's recommended practice is to wrap any shared RCC/PWR register access in an HSEM lock/release pair on both cores, not just the core that appears to change clocks most often.
References
Related Questions
Which STM32 Family Should You Use?
Compare STM32 families for new designs: G0, G4, F4, H7, L4, U5, WB, and WL — performance tiers, power profiles, peripheral sets, and which to choose.
How Do You Configure STM32 HAL DMA for UART, SPI, and ADC?
Configure STM32 HAL DMA for UART, SPI, and ADC — normal vs circular mode, interrupt callbacks, double buffering, and cache coherency on STM32H7/F7.
How Do You Configure STM32 NVIC Interrupt Priorities?
Learn how to configure STM32 NVIC interrupt priorities using HAL, priority grouping, and the FreeRTOS configMAX_SYSCALL_INTERRUPT_PRIORITY constraint.
How Does the STM32 Clock Tree Work?
The STM32 clock tree routes HSE or HSI through a PLL to generate SYSCLK, then divides it across AHB and APB buses. Learn how it works and how to configure it.
How Do You Configure STM32 Low-Power Modes (Sleep, Stop, Standby)?
Configure STM32 Sleep, Stop, and Standby modes: current draw and wake latency by mode, wake-up sources (EXTI, RTC), and the RTC backup domain.
How Does the Memory Map Work in an Embedded Microcontroller?
The Cortex-M memory map assigns flash, RAM, and peripherals to fixed address regions. Covers STM32 layout, volatile keyword, and how linker scripts map to it.
Related Forum Discussions
STM32L4 never wakes from Stop mode — button EXTI interrupt just doesn't fire
Working on a battery-powered sensor node, STM32L476RG (Nucleo board for now). Idea is: device sits in Stop 2 mode most of the time, wakes wh
STM32F401 UART printing garbage after switching to 84 MHz PLL — same 115200 baud in CubeMX and PuTTY
Got a WeAct Black Pill (STM32F401CCU6) project that's been running happily on the default HSI clock at 16 MHz. Using USART1 on PA9/PA10 thro
STM32H743 HAL_UART_Receive_DMA fires error callback immediately — TEIF1 set, RxCplt never fires
Upgrading a project from STM32F4 to STM32H743. UART DMA receive worked on the F4 without any issues: standard CubeMX setup, call HAL_UART_Re
STM32 GPIO interrupt configured but ISR never fires — what am I missing?
Trying to use a button on PA0 to trigger an interrupt on an STM32F411 Nucleo board. Using HAL, generated the init code with CubeMX. The GPIO