Dual-core STM32 projects

Many STM32 devices (for example some STM32H7 and STM32WB families) integrate two CPU cores on the same chip. STM32CubeIDE for Visual Studio Code provides support to create, build, and debug such dual-core projects.

Dual-core STM32 basics

Dual-core STM32 microcontrollers (MCUs) commonly use combinations such as:

  • A high-performance core (for example Cortex-M7 or Cortex-M4)

  • A companion core (for example Cortex-M4 or Cortex-M0+)

Typical responsibilities:

  • Primary core

    • System initialization (clock, memory, peripherals)

    • Boot sequence and security configuration

    • Coordination with the secondary core

  • Secondary core

    • Application-specific tasks (for example wireless stack, control loop, low-power tasks)

    • May be started, stopped, or reset by the primary core

Each core executes a separate image with its own:

  • Vector table

  • Linker script

  • Startup code

  • Reset and boot address

A dual-core project must therefore handle:

  • Two separate builds and binaries

  • A known boot strategy (which core starts first, and which core starts the other one)

  • A consistent inter-core communication mechanism (mailboxes, shared RAM, hardware IPs, and so on).

Dual-core project structure in STM32CubeIDE for Visual Studio Code

STM32CubeIDE for Visual Studio Code represents a dual-core application as a set of two projects that are logically linked:

  • One project per core (for example Core A and Core B)

  • Each project has its own configuration:

    • Source files and include paths

    • Linker script and memory mapping

    • Build configurations (Debug and Release)

    • Debug launch configuration

Common organization patterns:

  • Single workspace, two projects

    Both core projects sit in the same Visual Studio Code workspace and typically in the same folder hierarchy, for example:

    <root>/
      Core_CM7/
      Core_CM4/
      shared/
    
  • Shared code

    Shared headers and C files (for example inter-core communication, protocol definitions) are placed in a shared directory and referenced in both projects’ include paths.

STM32CubeIDE for Visual Studio Code keeps the association between the two core projects so that:

  • When importing a dual-core example, both projects are created and configured together.

  • Build, flash, and debug workflows can be coordinated

Note

In the project explorer, ensure both core projects appear together with clear naming, such as MyApp_CM7 and MyApp_CM4, or MyApp_M4App and MyApp_M0Plus.

Creating or importing a dual-core project

Creating a new dual-core project

Depending on the device, STM32CubeIDE for Visual Studio Code can offer:

  • Board-based dual-core templates (for specific Nucleo/Discovery/Eval boards)

  • MCU-based dual-core templates (for any supported dual-core device)

Typical creation flow:

  1. Create a new project using the STM32Cube project wizard

  2. In the device or board selector, choose a dual-core MCU or board

  3. When prompted for core selection:

    • Select the option corresponding to a dual-core application (for example Generate project for both cores)

  4. Configure the project name and location:

    • Decide on a naming convention for each core (for example suffix CM7 and CM4, or M0 and M4).

  5. Finish the wizard to generate:

    • One project per core

    • Generated startup files, linker scripts, and initialization code for each core

Importing an existing dual-core project

If you already have a dual-core project created with STM32CubeIDE (Eclipse-based) or STM32CubeMX:

  1. Use Import existing STM32Cube project from STM32CubeIDE for Visual Studio Code

  2. Point to the original project or workspace directory

  3. The import assistant detects that the project is dual-core and:

    • Creates two VS Code projects

    • Maps each core’s build and debug settings

  4. Verify:

    • Paths to toolchains and packs

    • The linker scripts for each core

    • That both core projects appear and build independently

Boot sequence and inter-core startup

Dual-core devices usually follow one of these boot models:

  1. Primary-core-first boot

    The primary core (for example CM7) boots from reset, configures the system, and then explicitly:

    • Releases the secondary core from reset

    • Sets the secondary core’s boot address

    • Optionally sends a startup signal through shared memory or a mailbox

  2. Bootloader or ROM involvement

    On some devices, a ROM bootloader or wireless stack may start one core and then launch the user application on the other core.

In the generated code you typically find:

  • In the primary core startup sequence:

    • Functions to configure the secondary core boot address

    • Code to release the secondary core from reset

  • In the secondary core:

    • A loop that waits for initial synchronization from the primary core (for example checking a flag in shared RAM) before proceeding

Tip

Document in your project which core is considered primary, which function in the primary core starts the secondary core, and how the two cores synchronize at startup (flags, semaphores, interrupts).

Building dual-core projects

Each core is built separately, producing one output binary (ELF/HEX/BIN) per core.

Per-core build configurations

For each core project you have:

  • Separate Debug and Release configurations

  • Separate compiler options (optimization level, warnings)

  • Separate linker script and memory layout

To build:

  • Build a single core

    Run the build task for that core’s project (for example MyApp_CM7: Build)

  • Build both cores

    Either:

    • Use a workspace task that triggers both builds sequentially, or

    • Use a multi-target build command provided by the extension (if available).

Tip

Define a Build all cores task that builds the primary core first and then the secondary core. This task can be bound to a convenient keybinding.

Memory mapping considerations

Because both cores share the same physical memories, linker scripts must be aligned:

  • Ensure no overlapping sections (for example code or data placed at the same address in both cores)

  • Reserve specific RAM regions for inter-core communication (see Inter-core communication)

When using generated linker scripts, STM32Cube tools usually provide sane defaults. If you customize them, ensure you maintain compatibility with:

  • Boot ROM expectations

  • Inter-core communication libraries

  • Any wireless stack or shared firmware

Inter-core communication

Inter-core communication (ICC) allows the two cores to exchange data and events. On STM32 dual-core devices this often uses:

  • Shared RAM regions

  • Dedicated hardware mailboxes or IP (for example hardware semaphore (HSEM), IPCC, and so on)

  • Interrupts to signal events

A typical scheme:

  1. Shared data structures (for example command queues, status flags) placed in a shared RAM section

  2. Synchronization primitives:

    • Hardware semaphores (HSEM) or mailboxes to protect shared data

    • Interrupts to signal new command or data ready

  3. Protocol definition:

    • Commands (IDs, payloads)

    • Error handling

    • Versioning

In STM32CubeIDE for Visual Studio Code, ICC is implemented in your application code, not by the extension itself. However, the extension can help by:

  • Providing example projects demonstrating ICC for your target

  • Keeping shared headers and sources accessible to both core projects

  • Offering simple navigation between the two cores’ code

Best practice

  • Place your ICC API (headers and sources) in a shared/ directory

  • Use identical header files in both core projects

  • Keep the protocol documented in a dedicated header or Markdown file

Debugging dual-core projects

STM32CubeIDE for Visual Studio Code supports debug workflows tailored to dual-core devices. There are three main patterns:

Debugging a single core

You can debug each core independently:

  • Use the debug configuration for MyApp_CM7 or MyApp_CM4 only

  • The non-debugged core runs whatever firmware is currently flashed

This is useful when:

  • The other core runs stable, production firmware

  • You focus on one side of the inter-core communication

Simultaneous dual-core debugging

For deeper analysis you can debug both cores at the same time:

  1. Ensure both binaries are built and flashed to the device

  2. Start a multi-core debug configuration, or start two debug sessions, one for each core, depending on your tooling

  3. Each core:

    • Has its own debug console, call stack, and breakpoints

    • Can be halted or resumed independently

Common operations:

  • Set breakpoints in both cores at points where they exchange data

  • Step on one core while letting the other run

  • Check shared memory content from both debug sessions

Tip

Mark the primary core as your main debug session so that Run to main and reset operations are primarily controlled from that side.

Boot and attach scenarios

When debugging complex boot sequences or wireless stacks, you may:

  • Flash firmware first, then reset and attach to one or both cores without re-flashing

  • Attach to the secondary core only after the primary core has released it from reset and started its firmware

Document in your project:

  • Whether the debug configuration performs reset and flash both cores, reset only, or attach only

  • Any special sequence needed (for example: start the CM7 debug first, then attach to CM4 after a delay)

Typical dual-core use cases

These are common patterns that example projects or templates can showcase:

  1. Control and communication split

    • Primary core: motor control or real-time tasks

    • Secondary core: communication stack (USB, Ethernet, Wi-Fi)

  2. Wireless stack on one core

    • Secondary core runs a manufacturer-provided wireless stack

    • Primary core runs the user application and interacts with the stack via ICC

  3. Safety or isolation

    • One core performs safety-critical functions

    • The other core handles user interface or non-critical features

Documenting such use cases in examples helps users understand how to partition their applications

Troubleshooting dual-core projects

Secondary core never starts

Possible causes:

  • Primary core did not release the secondary core from reset

  • Boot address for the secondary core not configured or incorrectly configured

  • The secondary core image was not flashed or is corrupt

  • Option bytes or security configuration prevent secondary core execution

What to check:

  • Confirm the primary core executes the start secondary core code

  • Verify that the secondary core’s reset or boot address matches the linker script

  • Check debug logs or ST-LINK output for errors

Debug session freezes or fails on one core

Possible causes:

  • Both cores are not correctly stopped or resumed in sync

  • Hardware resources are used concurrently without proper synchronization

  • The debug interface is overloaded by high interrupt or trace activity

What to check:

  • Try debugging each core separately to localize the issue

  • Temporarily disable inter-core communication or heavy interrupt sources while debugging

  • Reduce SWO or trace bandwidth or disable trace to see if the problem disappears

Inter-core communication not working

What to check:

  • Validate that shared memory is placed in a region accessible by both cores

  • Ensure cache and MPU settings do not prevent shared memory access

  • Check that hardware semaphores or mailboxes are correctly configured and cleared

  • Add simple heartbeat messages or counters to verify one core sees updates from the other