on 2025-08-26 6:15 AM
This article provides a detailed guide on using VS Code to debug code running from external serial flash on the STM32N6. It covers setting up the development environment, setting up the project with CMake, configuring the debugger within VS Code, and validating the setup.
To debug code running from external serial flash effectively, the external memory must be configured in memory-mapped mode. The first stage bootloader (FSBL), running from AXI-SRAM2, initializes the XSPI interface, enables memory-mapped mode, and jumps to the application stored in external flash. Debugging starts with placing the debugger program on this external memory, allowing seamless execution and inspection directly from flash.
A key limitation to be aware of is that breakpoints cannot be set in application code located in external memory before the FSBL configures the memory mapping. Once the FSBL completes this setup and the debugger enters debug mode, breakpoints function normally. This guide covers environment setup, debugger configuration, project setup with CMake, and tips to work around these constraints for an efficient debugging workflow in VS Code.
This article requires that you have the following installed:
The hardware used to showcase is the STM32N6570-DK. Make sure that you have it in DEV boot mode to program the code.
In the [Appli] project, locate the main.c file and add the toggle LED function call in its main loop:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin);
HAL_Delay(200);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
As described here, it is possible to add postbuild steps to generate the *.bin files and sign the binaries for both projects. However, in VS Code the launch.json configuration used for debugging embedded applications only supports *.elf files. To address this limitation, a custom postbuild command is included in the build process:
# Convert the signed Appli .bin back to .elf
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -I binary -O elf32-littlearm -B arm --change-addresses=0x70100000 ${APPLI_OUTPUT} ${CMAKE_BINARY_DIR}/Appli-trusted.elf
COMMENT "Converting signed .bin back to .elf in address [0x70100000]"
)
This custom command converts the signed *.bin output back into an *.elf file, mapping it to the correct memory addresses (in this case 0x70100000 for [Appli]). This ensures that the resulting .elf file contains the necessary information for proper debugger functionality.
In short, these postbuild commands can be added to the CMakeLists.txt files of [FSBL] and [Appli] projects:
# Specify the output ELF file
set(TARGET_ELF "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.elf")
# Command to generate .bin file
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_OBJCOPY}
-O binary ${TARGET_ELF} ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin
COMMENT "Generating .bin file"
)
# Define the paths to the STM32 Signing Tool and the input/output files
set(SIGNING_TOOL "C:/Program Files/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_SigningTool_CLI.exe")
# Define the input and output files for FSBL
set(FSBL_INPUT "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin")
set(FSBL_OUTPUT "${CMAKE_BINARY_DIR}/FSBL-trusted.bin")
# Add a post-build step to sign the FSBL binary
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo y > input.txt
COMMAND "${SIGNING_TOOL}" -bin "${FSBL_INPUT}" -nk -of 0x80000000 -t fsbl -o "${FSBL_OUTPUT}" -hv 2.3 -dump "${FSBL_OUTPUT}" < input.txt
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Signing FSBL binary: ${FSBL_INPUT} -> ${FSBL_OUTPUT}"
)
# Convert the signed FSBL .bin back to .elf
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -I binary -O elf32-littlearm -B arm --change-addresses=0x70000000 ${FSBL_OUTPUT} ${CMAKE_BINARY_DIR}/FSBL-trusted.elf
COMMENT "Converting signed FSBL .bin back to .elf in address [0x70000000]"
)
# Specify the output ELF file
set(TARGET_ELF "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.elf")
# Command to generate .bin file
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary ${TARGET_ELF} ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin
COMMENT "Generating .bin file"
)
# Define the paths to the STM32 Signing Tool and the input/output files
set(SIGNING_TOOL "C:/Program Files/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_SigningTool_CLI.exe")
# Define the input and output files for Appli
set(APPLI_INPUT "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin")
set(APPLI_OUTPUT "${CMAKE_BINARY_DIR}/Appli-trusted.bin")
# Add a post-build step to sign the Appli binary
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
# Step 1: Create a file with 'y'
COMMAND ${CMAKE_COMMAND} -E echo y > "${CMAKE_BINARY_DIR}/input.txt"
# Step 2: Run the signing tool, redirecting input from the file
COMMAND "${SIGNING_TOOL}" -bin "${APPLI_INPUT}" -nk -of 0x80000000 -t fsbl -o "${APPLI_OUTPUT}" -hv 2.3 -dump "${APPLI_OUTPUT}" < "${CMAKE_BINARY_DIR}/input.txt"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Signing Appli binary: ${APPLI_INPUT} -> ${APPLI_OUTPUT}"
)
# Convert the signed Appli .bin back to .elf
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -I binary -O elf32-littlearm -B arm --change-addresses=0x70100000 ${APPLI_OUTPUT} ${CMAKE_BINARY_DIR}/Appli-trusted.elf
COMMENT "Converting signed .bin back to .elf in address [0x70100000]"
)
The default linker script generated by the CMake for the [Appli] project is configured for the selected operation configured in the STM32CubeMX’s EXTMEM_MANAGER, where the selected was XIP. Double check that the linker script used in [Appli] CMakeLists.txt is indeed the correct one:
Note: STM32CubeMX version 6.15 has a minor bug on the linker script creation for CMake that causes a build error on VS Code. I recommend changing the content of said linker script with the equivalent one available in our GitHub here or use the attached on in this article (available at the end of the article.)
Now that all steps are configured, it is time to build the application, locate the [CMake] icon on the left, then expand the [PROJECT STATUS] to locate the [Build] [All] to rebuild the projects. Alternatively, press Ctrl+Shift+P and then type/click [CMake: Clean Rebuild] for a clean and rebuild action.
Edit the generated launch.json to support debugging with all features enabled:
{
"version": "0.2.0",
"configurations": [
{
"type": "stlinkgdbtarget",
"request": "launch",
"name": "STM326570-DK Debug",
"origin": "snippet",
"cwd": "${workspaceFolder}",
"preBuild": "${command:st-stm32-ide-debug-launch.build}",
"runEntry": "BOOT_Application", // Set in BOOT_Application
"svdPath": "C:/ST/STM32CubeCLT_1.18.0/STMicroelectronics_CMSIS_SVD/STM32N657.svd", // Replace with your .svd path
"imagesAndSymbols": [
{
"symbolFileName": "${workspaceFolder}/Appli/build/XIP_Appli.elf", // Replace this with your path and your_Appli.elf symbol
"imageFileName": "${workspaceFolder}/Appli/build/Appli-trusted.elf",
},
{
"symbolFileName": "${workspaceFolder}/FSBL/build/XIP_FSBL.elf", // Replace this with your path and your_FSBL.elf symbol
"imageFileName": "${workspaceFolder}/FSBL/build/XIP_FSBL.elf", // Replace this with your path and your_FSBL.elf image
}
],
"serverExtLoader": [
{
"loader": "C:/Program Files/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/ExternalLoader/MX66UW1G45G_STM32N6570-DK.stldr", // Replace with your external loader path
"initialize": false
}
]
}
]
}
The "name" field was updated from a generic label to "STM326570-DK Debug" to identify the target board clearly and make the configuration easier to recognize in the VS Code interface.
As placing breakpoints in code positioned in the external memory region before the memory is configured in memory-mapped mode, a change on the entry point has to be made. Set the first breakpoint in the FSBL's function called “BOOT_Application” instead of the usual "main" as this is the first function after the memory is in memory-mapped mode.
A new "svdPath" field was introduced, pointing to the SVD (system view description) file for the STM32N657 device. This enables the debugger to display peripheral registers and improves the debugging experience by providing detailed hardware information in the UI.
Instead of using a dynamic command to resolve the binary, the configuration now explicitly lists both "symbolFileName" and "imageFileName" for two separate images (application and FSBL). This allows for multi-image debugging and ensures that the correct symbol and image files are used, which is especially important when dealing with signed binaries.
A "serverExtLoader" section was added, specifying the path to an external loader (*.stldr file). This is required for programming external memory so that the debugger can access and debug code stored outside the MCUs.
Note: Remember that keeping existing breakpoints between debug sessions can cause failure messages when entering debug mode. If a failure message appears when attempting to enter debug mode, try power cycling the board.
By following these steps, you can successfully debug code running on the STM32N6 using VS Code. This tutorial provides a practical approach, covering environment setup, debugger configuration, and validation to ensure a smooth and effective debugging experience.
Hi @B.Montanari ,
Nice and detailed tutorial! Bravo!
That ‘first’ breakpoint isn’t necessary if you use a simple trick in the Appli main() with a debug flag or a user button.
BSP_PB_Init (BUTTON_USER1, BUTTON_MODE_GPIO);
// loop that can be used to catch the application for debug.Set mini to 1, build and flash target as normally.
// Set BOOT1 switch to the left position, press reset without removing cable.
// Use the Connect to running debug config to connect, then change mini value from watch.
// For breakpoints to work they seem to need to be manually changed to ahrdware breakpoint
static volatile int debugFlag = 1;
while (debugFlag == 1 && BSP_PB_GetState(BUTTON_USER1) == 0)
{
__NOP();
}