Programming STM32 Using VSCode on Linux
Although there is the STM32CubeIDE, some prefer not to be locked into a specific IDE. So, I wanted to write a post on how to setup VSCode on Linux to program STM32s. (Does this mean I’m now locked into VSCode?)
I referred to the following guides to create this post:
Pre-Installation
First, install VSCode to get started. In addition to VSCode, some packages must be installed in order to utilize the tools and extensions later on.
- Make
- CMake
- libusb-1.0-0-dev
- libtinfo5
- libncurses5 & libncursesw5
the following tools must be installed as well:
The ARM GNU Toolchain provides the cross-compiler for ARM and handles building the binary, which will be flashed onto the MCU. It has support for a wide variety of ARM architectures, so it’s not limited to just STM products. One thing to note is that GDB in version 13.2.Rel1 uses Python 3.8. Try opening the GDB server, and if you encounter an error, I recommend following this answer from StackOverflow.
OpenOCD has its own mainstream branch as well, but this repository from STM is what is used in the STM32CubeIDE to program and debug STM32s. To install OpenOCD, run the following commands in the downloaded directory:
./bootstrap
./configure
make
sudo make install
Once the external tools have been installed, we now need to configure the USB rules so that we have permission to read and write to the MCU connected via USB.
The necessary files are STSW-LINK007.
After downloading and extracting the files, follow the instructions in the readme.txt
to add the USB rules for ST-Link devices.
Once complete, the added rules for udev must appear in /etc/udev/rules.d
.
Run the following command to reload the rules.
sudo udevadm control --reload-rules
If you look at one of the newly added udev rules, you may notice that the permission mode is set to 660.
This means that others (i.e., those not in the group plugdev
) do not have permission to read and write to the devices.
If your user is not in the group plugdev
, please add it.
Just to make sure everything has been setup properly, we can run the command below to connect to the MCU.
Depending on your MCU, change the target/stm32f3x.cfg
accordingly.
The configuration files are in /usr/local/share/openocd/scripts/target
by default.
Check the directory to see if your MCU is supported.
openocd -s /usr/local/share/openocd/scripts -f interface/stlink.cfg -f target/stm32f3x.cfg
Once the device rules and OpenOCD has been installed properly, you should be able to see the following output:
Info : Listening on port 3333 for gdb connections
This means that you can now attach GDB to your localhost’s port 3333 to debug remotely. Since we don’t have an image to flash nor any code to compile, let’s move on to generating the template code.
Code Generation
Although you can start from scratch, you can also utilize the code generation offered by STM32CubeMX. The output may not be pretty, but it really does help tremendously with hardware initialization. Using the graphical interface, you can configure the clock rates and peripherals. Then, the tool will generate template code to initialize the hardware so that you can start writing application code.
First, select the target MCU or board to generate the code for. I only have the Nucleo boards, so I usually enter the board name and select the board. Then, set the peripherals in the Pinout & Configuration tab, and clock-related settings under Clock Configuration.
In the Project Manager tab, you can set the name for your project (which will eventually be the name of the output binary) and more.
To use the generated code with the VSCode extensions, make sure to select the Toolchain / IDE under Project as CMake
.
Freely modify other settings to match your preference, and click Generate Code when ready.
From the generated files, there are a couple of things to modify in order to use it with the installed toolchain.
First, the default cmake/gcc-arm-none-eabi.cmake
has the following line:
set(TOOLCHAIN_PREFIX arm-none-eabi-)
Update the TOOLCHAIN_PREFIX
so that it points to the ARM GNU Toolchain we installed earlier.
Next, in the CMakePreset.json
file, update the configurePresets.generator
to Unix Makefiles
if you want to use Make instead of Ninja for the build system.
Now that the files have been appropriately modified to be used with the extensions, let’s move on to installing and using the VSCode extensions.
VSCode Extensions
The VSCode extensions to install are as follows:
- Cortex Debug : Debugging via VSCode.
- C/C++ Extension Pack : CMake extension to build profiles and binaries.
- LinkerScript : Linker script syntax highlighting.
- ARM Assembly : Assembly (ARM) syntax highlighting.
Once the extensions have been installed, configurations are required for the extensions to work with the target.
Create a .vscode
directory to store all the VSCode extension-specific configurations.
To start with, in order to debug with VSCode (using the Cortex Debug extension), we need a launch.json
file.
Create launch.json
inside .vscode
and fill it with the following texts:
{
"version": "0.2.0",
"configurations": [
{
"cwd": "${workspaceRoot}",
"executable": "./build/Debug/f303re.elf",
"name": "Debug (OpenOCD)",
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd",
"configFiles": [
"/usr/local/share/openocd/scripts/interface/stlink.cfg",
"/usr/local/share/openocd/scripts/target/stm32f3x.cfg"
],
"searchDir": [],
"runToEntryPoint": "main",
"showDevDebugOutput": "raw"
}
]
}
You can also use the Add Configuration… button on the bottom right to add new launch configurations. For more information on configurations, refer to the manual. As mentioned above, make sure you have correctly set the executable path and configuration file paths.
Optionally, you can also provide an SVD (System View Description) file to get hardware register information for the peripherals. The SVD files are usually provided by the manufacturer or can be found in some Github repositories on the net.
Now that the extensions have been configured, we can now run and debug. If you’ve used VSCode before, you probably know how it is done. If not, it’s not easy to explain in words, so I’ll leave a link to the official guide.
The code used in this post is on Github.