Setting Up VS Code for STM32H755ZI Debugging on macOS

Introduction and prerequisites

The STM32H755 is a dual-core microcontroller featuring a Cortex-M7 and a Cortex-M4. While STM32CubeIDE provides an integrated environment for developing on this chip, many embedded engineers prefer VS Code for its flexibility and extension ecosystem. Getting a proper debug environment working on macOS, however, requires navigating several toolchain and configuration hurdles that are not well documented.

This is part 1 of a series on dual-core STM32H7 development on macOS using VS Code. This post covers the toolchain setup, Cortex-Debug configuration, and flashing. Subsequent posts will address VTOR configuration, linker script issues, dual-core debugging, and DMA UART.

The target hardware for this series is the STM32 Nucleo H755ZI-Q development board, which includes an onboard ST-LINK V3 programmer.

We need three components installed via Homebrew:

# ARM GCC toolchain
$> brew install --cask gcc-arm-embedded

# OpenOCD
$> brew install openocd

# ST-LINK tools
$> brew install stlink

In VS Code, install the Cortex-Debug extension (by marus25) and the C/C++ extension (by Microsoft).

Verifying the ST-LINK connection

Before configuring VS Code, we should confirm that the host machine can communicate with the board. Connect the Nucleo via USB and run:

$> st-info --probe

The expected output should be:

Found 1 stlink programmers
  version:    V3J16
  serial:     003300214142500620353451
  flash:      2097152 (pagesize: 131072)
  sram:       131072
  chipid:     0x450
  dev-type:   STM32H74x_H75x

This confirms the ST-LINK V3 is detected, the chip ID matches the H755, and we have access to the full 2 MB of flash. If st-infodoes not detect the board, you will need to verify the USB connection before proceeding.

Configuring launch.json

The H755 is a dual-core chip, so we need separate debug configurations for each core. The Cortex-Debug extension supports this through the ST-LINK GDB server, which accepts a -m flag to select the target core.

Create .vscode/launch.json with the following:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug CM7 - ST-Link",
            "cwd": "${workspaceFolder}",
            "type": "cortex-debug",
            "executable": "${workspaceFolder}/build/your-firmware-m7.elf",
            "loadFiles": [
                "${workspaceFolder}/build/your-firmware-m7.hex",
                "${workspaceFolder}/build/your-firmware-m4.hex"
            ],
            "request": "launch",
            "servertype": "stlink",
            "device": "STM32H755ZI",
            "interface": "swd",
            "runToEntryPoint": "main",
            "svdFile": "${workspaceFolder}/tools/STM32H755_CM7.svd",
            "v1": false,
            "showDevDebugOutput": "none",
            "serverArgs": [
                "-l", "1",
                "-m", "0",
                "-k", "-t", "-s"
            ]
        },
        {
            "name": "Debug CM4 - ST-Link",
            "cwd": "${workspaceFolder}",
            "type": "cortex-debug",
            "executable": "${workspaceFolder}/build/your-firmware-m4.elf",
            "loadFiles": [
                "${workspaceFolder}/build/your-firmware-m7.hex",
                "${workspaceFolder}/build/your-firmware-m4.hex"
            ],
            "request": "launch",
            "servertype": "stlink",
            "device": "STM32H755ZI",
            "interface": "swd",
            "runToEntryPoint": "main",
            "svdFile": "${workspaceFolder}/tools/STM32H755_CM4.svd",
            "v1": false,
            "showDevDebugOutput": "none",
            "serverArgs": [
                "-l", "1",
                "-m", "3",
                "-k", "-t", "-s"
            ]
        },
        {
            "name": "Attach CM4 - ST-Link",
            "cwd": "${workspaceFolder}",
            "type": "cortex-debug",
            "executable": "${workspaceFolder}/build/your-firmware-m4.elf",
            "request": "attach",
            "servertype": "stlink",
            "device": "STM32H755ZI",
            "interface": "swd",
            "svdFile": "${workspaceFolder}/tools/STM32H755_CM4.svd",
            "v1": false,
            "showDevDebugOutput": "none",
            "serverArgs": [
                "-l", "1",
                "-m", "3",
                "-t", "-s"
            ]
        }
    ]
}

The executable, loadFiles, and svdFile paths should be updated to match your project structure.

Some additional fields are explaiend further below:

Core selection: The -m flag tells the ST-LINK GDB server which core to target. -m 0 selects the Cortex-M7 and -m 3 selects the Cortex-M4.

loadFiles: Even when debugging only the CM4, both hex files must be flashed. On the H755, the CM7 is the master core -- it boots first and is responsible for starting the CM4. If only the CM4 binary is present, the system will not function.

launch vs. attach: A launch request flashes the firmware and starts a fresh debug session. An attach request connects to an already-running core without reflashing. The latter is useful when the CM7 debug session is already active and we want to connect to the CM4 separately.

SVD files: These provide peripheral register descriptions that the Cortex-Debug extension uses to display register contents in the debug panel. The SVD files for the H755 can be downloaded from ST's website. Having these available is valuable when debugging peripheral configuration issues, as we will see in later posts in this series.

Resolving ST-LINK GDB server failure

With the configuration in place, pressing F5 should start a debug session. In my case, it failed immediately:

ST-LINK: GDB Server Quit Unexpectedly.

The gdb-server terminal tab in VS Code showed the following:

STMicroelectronics ST-LINK GDB server. Version 7.11.0
Starting server with the following options:
        Persistent Mode            : Disabled
        SWD Debug                  : Enabled
Not able to connect to server
ST-Link enumeration failed
Error in initializing ST-LINK device.
Reason: ST-LINK DLL error.

The root cause was that STM32CubeIDE was installed on the system, and Cortex-Debug was picking up its bundled ST-LINK GDB server from deep within the application bundle:

/Applications/STM32CubeIDE.app/Contents/Eclipse/plugins/
  com.st.stm32cube.ide.mcu.externaltools.stlink-gdb-server.macos64_.../
  tools/bin/ST-LINK_gdbserver

This bundled server could not enumerate the ST-LINK, even though the Homebrew-installed st-info --probe had no issues communicating with the same board.

The solution is to ensure Cortex-Debug uses the Homebrew-installed tools. Setting "servertype": "stlink" in launch.jsonand confirming the Homebrew ST-LINK tools are on the PATH resolved the issue. If the extension continues to pick up the wrong server, the path can be set explicitly:

"stlinkPath": "/opt/homebrew/bin/st-util"

Alternatively, switching to "servertype": "stutil" uses the open-source st-util server instead of ST's proprietary one.

Flashing the firmware

With a working debug configuration, flashing occurs automatically when starting a debug session. The loadFiles array specifies which hex files to program before the debugger attaches.

For cases where we want to flash without starting a debug session, there are two command-line options.

Using st-flash:

$> st-flash --format ihex write build/your-firmware-m7.hex
$> st-flash --format ihex write build/your-firmware-m4.hex

Using STM32CubeProgrammer CLI (you will need to specify the full PATH to the binary)

$> STM32_Programmer_CLI -c port=SWD \
  -w build/your-firmware-m7.hex \
  -w build/your-firmware-m4.hex \
  -rst

When I attempted to flash the CM4 binary using st-flash, I saw the following error:

Flash programming error: 0x00040000
Failed to erase_flash_page(0x8100000) == -1
Failed to erase the flash prior to writing

This error persisted even when attempting a mass erase. The STM32H7 has flash protection mechanisms that the open-source st-flash tool didn’t seem to handle correctly. Luckily, the STM32CubeProgrammer handles H7 flash protection correctly:

$> stm32prog -c port=SWD \
  -w build/your-firmware-m7.hex \
  -w build/your-firmware-m4.hex \
  -rst

If flashing continues to fail, the option bytes may need to be checked. On the H755, the CM4 boot bit (BCM4) controls whether the CM4 core is allowed to boot. A mass erase can reset this bit:

# Display current option bytes
$> stm32prog -c port=SWD -ob displ

# Enable CM4 boot
$> stm32prog -c port=SWD -ob BCM4=1

VS Code tasks

For convenience, we can add flash and erase tasks to .vscode/tasks.json:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Flash CM7+CM4",
            "type": "shell",
            "command": "stm32prog -c port=SWD -w ${workspaceFolder}/build/your-firmware-m7.hex -w ${workspaceFolder}/build/your-firmware-m4.hex -rst",
            "problemMatcher": []
        },
        {
            "label": "Mass Erase",
            "type": "shell",
            "command": "stm32prog -c port=SWD -e all",
            "problemMatcher": []
        }
    ]
}

We can execute these tasks by hitting Cmd+Shift+P and selecting Tasks: Run Task

Summary

Setting up VS Code for STM32H755 debugging on macOS requires:

  1. The ARM GCC toolchain and ST-LINK tools installed via Homebrew

  2. The Cortex-Debug extension configured with the correct core selection flags (-m 0 for CM7, -m 3 for CM4)

  3. STM32CubeProgrammer for reliable flashing, as st-flash does not handle H7 flash protection

  4. SVD files for peripheral register inspection during debug sessions

With this environment in place, we can flash and step through code on either core of the H755. However, on a dual-core device, getting to a breakpoint in main() is only the beginning. In the next post, we will look at a problem that is specific to the CM4: the Vector Table Offset Register (VTOR) must be configured before the startup code calls any functions, and the default configuration does not do this correctly. The fix involves modifying the startup assembly and, ultimately, configuring VTOR properly through the CMake build system.

Previous
Previous

Bringing Up an AR0521 Camera on the Digi ConnectCore 6

Next
Next

Advice on Using AI and An Example