Streamlining STM32 Projects: VS Code, CMake and clangd
Tags: stm, vscode, cmake, clangd
Published: 2024-06-02 12:31:32

The STM32CubeIDE software serves as an excellent starting point for STM32 development. However, when it comes to developer experience, the IDE provided by ST can feel sluggish and resource-intensive.

In this article, we’ll explore how to seamlessly transition from STM32CubeIDE to Visual Studio Code for your embedded software development needs. Under the hood, we’ll set up a robust CMake build system, utilize clangd for indexing, formatting and tidy checks, and configure VS Code to efficiently manage the CMake project, clangd, and debugging.

Let’s dive into the details!

## Creating a project with STM32CubeIDE

In this chapter, we’ll walk through the process of setting up a C++ project using STM32CubeIDE. If you’ve already configured a project, feel free to skip ahead to the next section.

**New STM32 project**
Let's create a new project in STM32CubeIDE by clicking on _File -> New -> STM32 Project_

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3g4xn2b5swefj112cx91.png)

**Micro-controller choice**
In the micro-controller search we are gonna enter the name of the micro-controller that we are using. In my case it's the NUCLEO board with the micro-controller STM32L4A6ZGTX.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zkfbcex7mfdr51wxzj6b.png)

**Project name**
Let's now enter the name of the project and it's location.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4dhyd8a2p65y6nanuso.png)

**HAL Configuration**
After clicking finish, the configuration view will open up. Here you will be able to configure your micro-controller hardware. For the purpose of this demo, let's configure a led as GPIO Output and give the pin a name that we can later reference in our source files.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dssdnqopsgdqmo8tt8lc.png)


![GPIO Label](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3uqk4qql6pomv8b3e0ar.png)


**Code generation settings**
As a last step before letting STM32CubeIDE generate the code, select _Project Manager -> Project -> Do not generate the main()_

And _Project Manager -> Project -> Generate peripheral initialization as a pair of '.c/.h' files for peripheral_

![Configure don't generate main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwa85hkqechmrkeehoty.png)


![Configure peripheral initialization as a pair of '.c/.h' files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wa00tt17yld6zoq8dmg3.png)


**Generating the HAL code**
We are now ready to generate the code. Click save or the generate code symbol.

![Generate code symbol](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y40pfgxowqcea0yll0tz.png)

**Application folder**
We selected to not generate the main so that we can write a cpp main ourselves. Start  by creating a source folder in the root directory by right clicking on the project then clicking on _New -> Source Folder_

![Create source folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dodury7p7x3kv8idqeg3.png)

![Create Application source folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7bvxfbedez52v1ioh7wa.png)


**Creating the main**
And create a `main.cpp` file inside the `Application` folder:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j19l4rl24ubkfu8sn24n.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vwlgkm2fj39ukswh28fx.png)

The code for the `main.cpp` used in this demo is the following:
```cpp
#include "main.h"
#include "gpio.h"

extern "C"
{
    extern void SystemClock_Config();
}

int main()
{
    HAL_Init();

    SystemClock_Config();

    MX_GPIO_Init();

    while (true)
    {
        HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
        HAL_Delay(1000);
        HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
        HAL_Delay(1000);
    }
}
```

**Compile and debug**
We are now ready to compile and debug our project with STM32CubeIDE.

## Setting up the CMake project

Let's now close the STM32CubeIDE window and start integrating our STM project in VS code. The first thing to do is to set up the CMake project.

Start by checking if you have CMake and Ninja already installed on your system by typing in the terminal:
```shell
cmake --version
ninja --version
```

If CMake or Ninja are missing, you can install them from your package manager in linux or from the official websites in Windows:
- [https://cmake.org/download/](https://cmake.org/download/)
- [https://ninja-build.org/](https://ninja-build.org/)

In Windows you will have to add the path of your Ninja executable to the system environment variables. For example, if your executable is located in `C:\Ninja\ninja.exe`, you can add the path `C:\Ninja\` to your system environment variables.

Afterward clone the [github folder](https://github.com/MCMattia/STM32-VSCode-CMake-streamlined) associated to this article and copy the following files to your project folder:

- `CMakeLists.txt`
- `CMakePresets.json`
- `gcc-arm-none-eabi.cmake`

### CMakeLists.txt

Open the `CMakeLists.txt` file and if needed adapt the name of the micro-controller from STM32L4A6ZGT to the one that you are using. In the `CMakeLists.txt` file check that your sources are defined in the `PROJECT_SOURCES` variable and that the correct symbols are defined in the `PROJECT_DEFINES` variable.

### CMake toolchain file

The toolchain file `gcc-arm-none-eabi.cmake` defines the location of the compiler toolchain that CMake uses.

The `TOOLCHAIN_PREFIX` variable should point to the location of the compiler included by STM32CubeIDE. In linux it's usually inside `/opt/st/` while in Windows it's usually inside `C:\ST\`.

At this point you should be ready to let CMake configure and build your project. Open a terminal in the root of your project folder and type the following commands:
```shell
cmake --preset Application          # configure project
cmake --build --preset Application  # build project
```

The output of the last command when the compilation finishes should be something like:
```
[25/25] Linking CXX executable STM32-CMake-base.elf
```

Congratulation you compiled your embedded project with CMake and Ninja !

## Setting up VS Code

From the [github repo](https://github.com/MCMattia/STM32-VSCode-CMake-streamlined) associated with this article, copy the `.vscode` folder to your project folder.

In the `.vscode/settings.json` file you can adapt define the location of gdb and gdb server to allow debugging. Additionally the location of the gcc compiler for clangd can also be adapted.
```json
{
    "cortex-debug.gdbPath": "/opt/st/stm32cubeide_1.15.0/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.12.3.rel1.linux64_1.0.100.202403111256/tools/bin/arm-none-eabi-gdb",
    "cortex-debug.JLinkGDBServerPath": "/opt/st/stm32cubeide_1.15.0/plugins/com.st.stm32cube.ide.mcu.externaltools.jlink.linux64_2.2.200.202402092224/tools/bin/JLinkGDBServer",
    "clangd.arguments": ["--query-driver=/opt/st/stm32cubeide_1.15.0/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.12.3.rel1.linux64_1.0.100.202403111256/tools/bin/arm-none-eabi-gcc"]
}
```

The file `.vscode/extensions.json` specifies the recommended extensions to use. If you open the extension menu in VS Code, you will be able to see and install the extension listed in `.vscode/extensions.json`.

After installing the recommended extensions, the VS Code CMake  and clangd integration will start working. By opening a source file, clangd will index it and you will be able to start using the advanced functionality that the clangd language server provides.

Here an example of the sometimes very helpful checking that clangd performs by default:

![Clang will warn if an unsigned if compares against a negative number](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwn28rgmz87rhcnhvk9z.png)


You can now compile the project in VS Code by pressing `CTRL + SHIFT + B`.

### Debugging

The file `.vscode/launch.json` that was copied in the previous chapter from the [github repo](https://github.com/MCMattia/STM32-VSCode-CMake-streamlined) configures the debugging settings. If you open it, you will see two configurations, one for the SEGGER J-Link debugger and the other for the ST-Link debugger.

In the left bar in VS Code, select the Debug symbol, then select the debug configuration compatible with your debugger and finally press the play button to flash the firmware on your micro-controller and start debugging.

![Debug in VS Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bobgcw4wq8p0u02ucy9q.png)


### Formatting and tidy checks

To enable formatting of the sources and clang-tidy checks, copy the following files from the [cloned github folder](https://github.com/MCMattia/STM32-VSCode-CMake-streamlined) to your application folder:

- `Application/.clang-format`: This file defines the formatting rules for your C/C++ code.
- `Application/.clang-tidy`: Here, you’ll find configuration settings for clang-tidy. This tool analyzes your code for potential issues, such as code smells, bugs, and performance bottlenecks.

Note that clang-format and clang-tidy will be active only at the level of the configuration files. In this scenario, their effects will extend to all files residing inside the Application folder. In this way clangd will not format or perform checks on files generated by STM32CubeIDE.

Now you’re all set to streamline your STM32 development workflow using VS Code, CMake, and clangd. Happy coding!

Feel free to ask me any question in the comments.