Using Visual Studio Code with STM32CubeMX for ARM Development

Every day, the software world is moving away from traditional software and hardware tools by replacing them with better and faster open source alternatives. Today, the most compelling open source alternatives for ARM embedded development are based on Eclipse. Setting up a perfect workflow for embedded software development with no licensing costs can be a daunting task if you don’t know where to start.

As I’m not a fan of Eclipse, I’ll show you how to configure VS Code for this task. I chose this IDE because it’s blazing fast, Intellisense always works and it supports extensive customisation through plugins and configuration files.

In this tutorial we’ll use the ST-Link adapter for programming and debugging. If you want to go completely open-source, black magic probe has you covered.

 

Should I do this?

Well, it depends. If you are working on a large scale project you’re probably better off using traditional paid IDEs like uVision or IAR because of their wider compatibility across many product versions and operating systems. Also, if you are looking for a method of programming ARM Cortex devices which is royalty free and easier to setup you may want to check out VisualGDB, SW4STM32 or other Eclipse based alternative.

However, if you want the full power of Intellisense at your fingertips and faster build times this is the way to go. Trust me, it makes a whole lot of difference in development.

NUCLEO F303K8 Programmed from VS Code

 

Prerequisites

A NUCLEO-F303K8 development kit or similar ST board. For other boards you’ll need to update the configuration files with your processor name and series. All the external tools necessary to build and flash ARM Cortex embedded software will be placed in the VSARM folder.

  1. Create a folder named VSARM in C:/
  2. Visual Studio Codehttps://code.visualstudio.com/
  3. STM32Cube initialization code generator – https://www.st.com/en/development-tools/stm32cubemx.html
  4. GNU Embedded Toolchain for ARM – https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads\
    • Change Install Location to C:\VSARM\armcc\
  5. Texane’s ST-Link Toolshttps://github.com/texane/stlink/releases
    • Get the binaries and extract the release in C:\VSARM\stlink\
  6. MinGW-W64https://sourceforge.net/projects/mingw-w64/
    • Change the install location to C:\VSARM\mingw\
  7. ST-Link drivers – https://www.st.com/en/development-tools/st-link-v2.html

 

Environment configuration

Open Environment variables setup dialog. Depending on your system configuration and administrative rights you may choose to modify either the User variables or the System variables. Make the following changes:

  1. Create a new variable named VSARM pointing to C:\VSARM\

VSARM Variable

  1. Add C:\VSARM\stlink\bin to the Path variable in the variables pane
  2. Add C:\VSARM\armcc\bin to the Path variable in the variables pane
  3. Add C:\VSARM\mingw\mingw32\bin to the Path in the variables pane

environment variables path

 

Make sure executable files are present in all of the three sub-folders included above.

 

VS Code Configuration

After installing Visual Studio Code, open the IDE, navigate to Extensions tab or press Ctrl + Shift + X.

Install the following extensions:

Press F1 and type user settings json. Select Open Settings (JSON). In the opened file, add the following property and save.

"cortex-debug.armToolchainPath": "${env:VSARM}\\armcc\\bin\\"

 

STM32 Workspace Setup

We will create a folder to serve as a workspace for our projects. We will configure two projects to get the development going. As a first step, create a folder named STM32_Projects on your desktop.

Then, create two subfolders, one named template_F3 and one named blink. Within VS Code navigate to files tab or press Ctrl + Shift + E. Click on Add folder to the workspace. Navigate to your STM32_Projects folder and select it.

Press F1 and type save workspace. Select Workspaces: Save Workspace As. Navigate to your STM32_Projects folder and save the file with an appropriate name.

Saving workspace in VS Code

Press F1 and type save project. Tap Enter and set the project name to template_F3.

Press F1 to edit projects. Modify the rootPath option to point to the template_F3 subfolder like below.

[
  {
    "name": "template_F3",
    "rootPath": "c:\\Users\\cristi.dbr\\Desktop\\STM32_Projects\\template_F3",
    "paths": [],
    "group": ""
  },
  {
    "name": "blink",
    "rootPath": "c:\\Users\\cristi.dbr\\Desktop\\STM32_Projects\\blink",
    "paths": [],
    "group": ""
  }
]

Duplicate the JSON project entry to create another project named blink pointing to our blink subfolder. Save the file and press F1 to List projects to open and click on the template_F3 project to open it. Your template project is now open.

You will need to update this file when you create new projects to let Project Manager extension know where your stuff is.

 

STM32 Project Setup

Press F1, type edit config and select C/CPP: Edit Configurations…. Replace the contents of c_cpp_properties.json with the following configuration.

{
    "configurations": [
    {
    "name": "STM32 Debug",
    "includePath": [
        "${env:VSARM}/armcc/arm-none-eabi/include/c++/7.3.1",
        "${env:VSARM}/armcc/arm-none-eabi/include/c++/7.3.1/arm-none-eabi",
        "${env:VSARM}/armcc/arm-none-eabi/include/c++/7.3.1/backward",
        "${env:VSARM}/armcc/lib/gcc/arm-none-eabi/7.2.1/include",
        "${env:VSARM}/armcc/lib/gcc/arm-none-eabi/7.2.1/include-fixed",
        "${env:VSARM}/armcc/arm-none-eabi/include",
        "${workspaceRoot}/Drivers/CMSIS/Include/",
        "${workspaceRoot}/Drivers/CMSIS/Include/",
        "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F3xx/Include/",
        "${workspaceRoot}/Core/Inc",
        "${workspaceRoot}/Core/Src",
        "${workspaceRoot}/Inc",
        "${workspaceRoot}/Src",
        "${workspaceRoot}/Drivers/STM32F3xx_HAL_Driver/Inc",
        "${workspaceRoot}/Drivers/STM32F3xx_HAL_Driver/Inc/Legacy/",
        "${workspaceRoot}/Drivers/STM32F3xx_HAL_Driver/Src"
    ],
    "defines": [
        "DEBUG",
        "DEFAULT_STACK_SIZE=2048",
        "HSE_VALUE=8000000",
        "OS_INCLUDE_STARTUP_INIT_MULTIPLE_RAM_SECTIONS",
        "PB_MSGID",
        "STM32F303",
        "STM32F303x8",
        "USE_DEVICE_MODE",
        "USE_FULL_ASSERT",
        "USE_HAL_DRIVER",
        "USE_USB_OTG_FS"
    ],
    "intelliSenseMode": "clang-x64",
    "browse": {
    "path": [
        "${workspaceRoot}",
        "${env:VSARM}/armcc"
    ],
    "limitSymbolsToIncludedHeaders": false,
    "databaseFilename": ""
    }
    }
    ],
    "version": 4
}

Adjust the defines section to match the microcontroller used and your HSE frequency. To create a release configuration, duplicate the configuration object, remove USE_FULL_ASSERT define and replace DEBUG with NDEBUG in the defines section.

Press F1 and type config task. Select Tasks: Configure tasks. Click on Create tasks.json file from template and select the Other option. The tasks.json file will open. Replace the contents of this file with the following and save the file. Adjust core parameter -j for you processor and change the optimization level with the OPT variable.

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Make Firmware",
            "type": "shell",
            "command": "mingw32-make -j8 all TARGET=vsarm_firmware OPT=\"-O2\" BINPATH=\"${env:VSARM}armcc\/bin\"",
            "options": {
                "cwd": "${workspaceRoot}"
            }, 
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": []
        },
        {
            "label": "Load Firmware",
            "type": "shell",
            "command": "st-flash write ./build/vsarm_firmware.bin 0x08000000",
            "options": {
                "cwd": "${workspaceRoot}"
            },
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": []
        }
    ]
}

The configuration creates two tasks, one to build the project and another one to flash the microcontroller.

 

Launch configurations

Press F1, type launch and select Debug: Open launch.json. Choose the Cortex Debug option. Replace the contents of launch.json file with the following:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
    {
        "type": "cortex-debug",
        "request": "launch",
        "servertype": "stutil",
        "cwd": "${workspaceRoot}",
        "executable": "./build/vsarm_firmware.elf",
        "name": "Debug (ST-Util)",
        "device": "STM32F303K8",
        "v1": false,
        "svdFile": "${workspaceRoot}/STM32F303x.svd"
        }
    ]
}

Adjust the device option to for your microcontroller. Cortex-Debug extension for Visual Studio Code requires an SVD file to allow the inspection of peripheral registers. You can find a list of the SVD files for the most used STMicroelectronics microcontrollers here: https://github.com/posborne/cmsis-svd/tree/master/data/STMicro.

Download the appropriate file for your processor and paste it in the template_F3 folder. Then, adjust the sdvFile option in the launch configuration.

 

CubeMX blink example

Navigate to the template_F3 subfolder. Copy the contents of this folder to your blink folder. If the .vscode folder is not visible, make sure you have the option to Show hidden files and folders checked in Folder and Search options.

template project STM32F3

Open CubeMX and create a new project. Search for your microcontroller and double click to create the project. Save the project in STM32_Projects/blink/ as blink.ioc.

The GUI to edit peripherals will appear. Check your board schematics to find the pins for the LEDs. For the NUCLEO-F303K8 board, the LED is on PB3 pin. Click on the LED pin to change its function to GPIO_Output.

Save and click on generate the source code from Project / Generate Code menu.

The project settings dialog will appear. Make the following changes.

  • Set the project name the same as the folder name (blink)
  • Set the location to the workspace folder (STM32_Projects)
  • Make sure the Toolchain IDE option is set to Makefile
  • In the Code Generator tab, make sure the Delete previously generated files when not re-generated option is unchecked

STM32Cube makefile project settings

STM32Cube Makefile code generator settings

Click Ok and allow to overwrite. Initialization code is now generated for the project. The first time it generates the code, Cube will delete the .vscode folder. Copy and paste the json files from the template as well as the svd file. 

 

Adding keyboard shortcuts

To make life easier, we’ll add shortcuts for build and flash tasks. Press F1 and search for keybindings. Open the JSON file and add the following shortcuts:

// Place your key bindings in this file to overwrite the defaults
[
    {
        "key": "f5",
        "command": "workbench.action.tasks.runTask",
        "args": "Make Firmware"
    },
    {
        "key": "f6",
        "command": "workbench.action.tasks.runTask",
        "args": "Load Firmware"
    }    
]

 

Programming and debugging

Return to Visual Studio Code, press F1 and type list projects. Select the blink project. Edit the main.c file, then add some code to blink the LEDs.

Programming STM32 from VS Code

For example:

HAL_GPIO_WritePin( GPIOB, GPIO_PIN_3, GPIO_PIN_SET );
HAL_Delay( 100 );
HAL_GPIO_WritePin( GPIOB, GPIO_PIN_3, GPIO_PIN_RESET );
HAL_Delay( 100 );

Save and press F5 to build and F6 to flash. If all has gone well, it should work perfectly on the first try. You can now start debugging by going to the debug pane. If the SVD file is present, it will allow you to inspect the state of the peripheral registers.

Debugging STM32 in VS Code

When your code increases in size, you’ll want to separate it in different modules. For this, you have to add the C files path in the Makefile. STM32Cube does a great job keeping your changes when code is re-generated.

Editing Makefile STM32Cube

While this is an experimental approach and it takes some time to configure, I hope that the performance you can get out of it will convince you to consider this approach in production environments.

Happy coding!