STM32 development and debugging using VSCode

From Stm32World Wiki
Jump to navigation Jump to search
DIY ST-Link connected to Black Pill board

ST provide an IDE (Integrated Development Environment) for STM32 development. As a beginner's environment to learn embedded programming on STM32 MCUs this IDE is not half bad.


This example is developed on a standard Debian desktop system. On an Ubuntu system it should be almost the same.


The toolchain refers to the compiler and the tools to manipulate binary images. On Debian those are available in the standard repository:

$ sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi

GNU Debugger (GDB)

The GNU Debugger (GDB) is also available:

$ sudo apt install gdb-multiarch


To communicate with the actual STM32 MCU a JTAG/SWD tool is necessary. Fortunately the standard OpenOCD in Debian support STM32/ST-Link:

$ sudo apt install openocd


STM32CubeMX can be downloaded directly from ST's Website.

Visual Studio Code

The final prerequisite will be VSCode itself. On a Debian system, the easiest way to install VSCode is to use the repository provided by Microsoft. In my case, I've added the repository:

lth@ncpws04:~$ sudo cat /etc/apt/sources.list.d/vscode.list
deb [arch=amd64,arm64,armhf] stable main

Having that added, installation becomes a simple matter of:

$ sudo apt update && sudo apt install code

After installing VSCode a few extensions need to be installed. Those are:

  • C/C++ Extension
C++ Extension.png
  • Cortex-Debug
Cortex-Debug Extension.png
  • Makefile Tools
Makefile Tools.png

Starting a project with STM32CubeMX

When starting STM32CubeMX we can select "Access to MCU Selector":

STM32CubeMX New Project.png

This will bring us to the MCU selector:

STM32CubeMX MCU Selector.png

Having selected the appropriate MCU or development board, we finally end up at the core of STM32CubeMX where we can configure the MCU and it's peripherals. In our case, this being a simple "blink" project, we enable PC13 as a GPIO output (labelled LED) and USART1 for serial communications. PC13 is also configured as "Open Drain" (not overly important in this case as pulling low will work too).

VSCode Example - pinout.png

When done configuring the MCU and it's peripherals, move to the "Project Manager" tab and make a few changes:

VSCode Exmple Project Manager.png

The important ones are the Project Name, Project Location and Toolchain/IDE. When done click "Generate Code".

Notice, that it is always possible to go back and make changes in STM32CubeMX. Just start it again, load the project, make the changes and generate code again.

Editing Project in VSCode

When starting VSCode, select "Open Folder" and open the folder generated by STM32CubeMX in the previous section:

VSCode Open Folder.png

Before doing anything else, let us check if the system build by running "make":

Vscode make.png

The result, should be a binary image (.bin) file used for flashing the device.

Vscode successful make.png

Now that we know the project will build, let us make a few changes to the source. First of all, following the approach described here we create a "DBG" macro and ensure that printf prints on the serial console.

In 'main.h':

/* USER CODE BEGIN Private defines */

#ifdef DEBUG
#define DBG(...)    printf(__VA_ARGS__);\
#define DBG(...)

/* USER CODE END Private defines */

In 'main.c' we first include "stdio.h":

/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */

Then we create the redirection of printf to USART1:

/* Private user code ---------------------------------------------------------*/

// Send printf to uart1
int _write(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;

  if (fd == 1 || fd == 2) {
    hstatus = HAL_UART_Transmit(&huart1, (uint8_t *) ptr, len, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return len;
      return -1;
  return -1;


We are now in business and can modify our main loop as we please:

/* Infinite loop */

  uint32_t now = 0, last_blink = 0, last_print = 0;

  while (1)

    now = HAL_GetTick();

    if (now - last_blink >= 500) { // Every half second or 500 ms

      HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);

      last_blink = now;


    if (now - last_print >= 1000) {
      DBG("Tick %lu", now / 1000);

      last_print = now;



    /* USER CODE BEGIN 3 */

Again we run make to ensure the project still build.

Finally let us try to run a debugging session. First click Run and Debug:

Vscode run and debug.png

The first step is to let cortex-debug know where to find our debugger. In the case of Debian, create the following in .vscode/settings.json:

    "cortex-debug.gdbPath": "gdb-multiarch", 
    "makefile.extensionOutputFolder": "./.vscode"

Since we have not yet configured debugging click Create a launch.json. Fill in the following:

    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit:
    "version": "0.2.0",
    "configurations": [
            "type": "cortex-debug",
            "request": "launch",
            "name": "Debug (OpenOCD)",
            "servertype": "openocd",
            "cwd": "${workspaceFolder}",
            "runToEntryPoint": "main",
            "executable": "./build/blink.elf",
            "device": "STM32F411CEU6",
            "configFiles": [
            "showDevDebugOutput": "none",

We can now fire up a debugging session:

Vscode debugging session.png

That is about it - we can now develop and debug applications on the STM32 using free tools.

Miscellaneous Links