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:
Create a new project using the STM32Cube project wizard
In the device or board selector, choose a dual-core MCU or board
When prompted for core selection:
Select the option corresponding to a dual-core application (for example Generate project for both cores)
Configure the project name and location:
Decide on a naming convention for each core (for example suffix
CM7andCM4, orM0andM4).
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:
Use Import existing STM32Cube project from STM32CubeIDE for Visual Studio Code
Point to the original project or workspace directory
The import assistant detects that the project is dual-core and:
Creates two VS Code projects
Maps each core’s build and debug settings
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:
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
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:
Shared data structures (for example command queues, status flags) placed in a shared RAM section
Synchronization primitives:
Hardware semaphores (HSEM) or mailboxes to protect shared data
Interrupts to signal new command or data ready
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/directoryUse 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_CM7orMyApp_CM4onlyThe 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:
Ensure both binaries are built and flashed to the device
Start a multi-core debug configuration, or start two debug sessions, one for each core, depending on your tooling
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:
Control and communication split
Primary core: motor control or real-time tasks
Secondary core: communication stack (USB, Ethernet, Wi-Fi)
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
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
Recommended project practices ¶
To keep dual-core projects maintainable:
Use clear naming for projects, binaries, and linker scripts (for example suffix
_CM7and_CM4)Centralize shared definitions (messages, registers, memory layout) in a single header
Provide a short architecture note in your project:
Responsibilities of each core
Boot sequence
Inter-core communication mechanism and memory map
Maintain synchronized versions:
When the ICC protocol changes, bump a simple protocol version so that mismatched cores can detect incompatibility