Portfolio About me Blog

Today, I would like to talk about working with STM32 microcontrollers using the VSCode source code editor in the Linux operating system. This article is intended for beginners and will provide a step-by-step guide to creating a simple project, highlighting some of the less obvious points. Questions about how it works and how to set everything up as flexibly as possible will be left for the next article.

Since this article is aimed at beginners, we will follow the path suggested by STMicroelectronics. Specifically, we will use their extension for VSCode to manage the project.

Installation

VSCode

First, you need to install VSCode. You can download it from the official website or via Flatpak. For Debian or Ubuntu users, you can also add the repository to ensure VSCode updates along with the rest of your system:

sudo apt-get install wget gpg
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
sudo install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg
echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" |sudo tee /etc/apt/sources.list.d/vscode.list > /dev/null
rm -f packages.microsoft.gpg
# And install vscode
sudo apt install code

However, this will not work for Ubuntu, as Canonical is aggressively promoting snap, so you can only install vscode using it.

snap install code

Unfortunately, we need to use VSCode specifically. VSCodium (a clean build of VSCode without telemetry) cannot be used because the extension from ST requires IntelliSense, which is a Microsoft extension and only works on Microsoft builds. Due to this dependency not being met, the stm32-vscode-extension is simply absent from the market.

Next, you need to install a special extension - STM32 VS Code Extension. In 2024, ST released a new version 2.0.0 of this extension, essentially rewriting it from scratch. Now it does not create a project in its own format, which could only be opened using this extension, but rather just adds a debugger configuration for a project generated by CubeMX. This became possible because in early 2024, CubeMX added support for generating CMake projects (this extension can only work with CMake).

For the full functionality of this extension, several additional programs need to be installed:

  • CubeMX - a well-known tool for generating project templates.
  • CubeCTL - a toolchain designed for working with STM32 controllers.
  • MCU Finder - a utility for finding alternative controllers.

CubeMX

The latest version of CubeMX can be downloaded here. You need to download the archive, extract it, and run SetupSTM32CubeMX-*, where the asterisk should be replaced with the version number you downloaded earlier. It's recommended to run the installation as a regular user (not as root) to avoid issues with CubeMX updates in the future. During the installation process, you need to agree to the license and choose a folder for installing CubeMX.

However, after installation, the CubeMX icon might not appear in the application menu or in rofi. You can fix this manually by creating a file ~/.local/share/applications/stm32cubemx.desktop:

[Desktop Entry]
Name=STM32CubeMX
GenericName=STM32CubeMX
Comment=Graphical tool for configuration of STM32 Microcontrollers
Exec=/home/STM32CubeMX/STM32CubeMX
Terminal=false
X-MultipleArgs=false
Type=Application
Icon=/home/STM32CubeMX/help/STM32CubeMX.ico
StartupWMClass=STM32CubeMX
StartupNotify=true

Instead of /home/STM32CubeMX/, you will need to use your installation path for STM32CubeMX.

CubeCTL

In addition to the code generator, you need a toolchain, which is provided by STM32CubeCLT. It consists of a set of console utilities (GCC, GDB), a set of SWD files, and STM32CubeProg in a console version. You can download it here. It is recommended to install the generic version for Linux, not a specific distribution version, and also not as root.

After extracting the archive, you will have access to a file with a .sh extension. You need to make this script executable and run it for installation:

unzip en.st-stm32cubeclt*.zip
chmod +x st-stm32cubeclt*.sh
./st-stm32cubeclt*.sh

The commands above use a wildcard symbol - *, which the shell will replace with the rest of the file name. However, if there are files in the folder with matching patterns, this might not work correctly, and in that case, you'll need to enter the full path manually.

Next, you need to agree to all the license-related questions (and there will be many) and not miss the moment when the question about the installation location is asked - STM32CubeCLT install dir?. You will need to either agree with the default option or specify your path.

After installation, the paths to the compilers and other software should appear in PATH, which is necessary for the extension to work. You can check this with the command echo $PATH. If this does not happen (as it did on my Debian with ZSH), you need to do it manually by copying the contents of /etc/profile.d/cubeclt-bin-path_1.15.1.sh (substitute the necessary version) to the end of .bashrc or .zshrc if you use ZSH.

MCU Finder

Additionally, if needed, you can install the ST-MCU-FINDER-PC package. This package is optional and intended for finding microcontrollers based on specific criteria. It can be launched from the extension menu. Personally, I do not use or install it, as its functionality is duplicated in CubeMX.

Creating a Project

Now, let's create a simple project that blinks an LED once per second and outputs a message to the console. Along the way, I will discuss some nuances of working with CubeMX that are not immediately obvious. Full documentation can be found in this document.

CubeMX

To start, it's a good idea to go to the settings by pressing alt + s and change the Repository folder so that the packages downloaded by CubeMX don't end up cluttering your home directory.

Let's create a project for the STM32G431RBT6 microcontroller. I will be using the NUCLEO-G431RB board.

First, select the microcontroller:

cubemx_select

Then you will enter the main configuration menu of the microcontroller.

cube

Here, everything is quite simple:

  1. In the left menu (1), select the specific peripheral block.
  2. In the menu (2), choose which functional blocks of this peripheral will be enabled.
  3. In the menu below (3), select specific parameters of the peripheral.
  4. In the window (4), you can choose specific pins and their functions.

There is also a convenient fuzzy search for pins (5). For example, you can start typing ETR, and the search will show all pins with timers that have this function.

For this project, you need to enable the core clock through Pinout & Configuration -> System Core -> RCC -> High Speed Clock (HSE) and the real-time clock through Pinout & Configuration -> System Core -> RCC -> Low Speed Clock (LSE). Since this board has crystals for both HSE and LSE, select Crystal/Ceramic Resonator for both. However, to ensure proper operation, you need to make sure that the correct frequency is selected for these clock sources, which will be mentioned later.

Next, you need to enable debugging, otherwise, you will not be able to connect to the microcontroller for debugging. To do this, set Pinout & Configuration -> System Core -> SYS -> Debug to Serial Wire.

If you accidentally flashed the microcontroller without enabling debugging and the programmer can no longer detect it, it's not critical. You can fix this using the CubeProgrammer by erasing the firmware with the Reset mode set to Hardware reset. To make this work, you need to connect the RST pin to the programmer (in addition to SWDIO and SWCLK).

At this point, the mandatory part is over, and now you need to choose what to activate based on your project requirements. I will add a button, an LED, and UART.

  • For the LED, select pin PA5, set it to GPIO_Output, and assign a user label - LED (right-click on the pin).

  • For the button, select pin PC13, switch it to GPIO_EXTI mode, and also assign a user label - BTN. However, for the interrupt to work, it needs to be activated: Pinout & Configuration -> System Core -> NVIC and then set EXTI line [15:10] interrupts (mine is line 13).

  • For UART, you need to activate Pinout & Configuration -> Connectivity -> LPUART1 -> Mode and set it to Asynchronous. Also, in the Configuration window, set the speed to 115200 Bits/s. We'll leave the rest as default. I also needed to change the default pins from PC0 and PC1 to PA3 and PA2 respectively (this is done by holding Alt + Click on the desired pin to move it).

Here are a few interesting points that might not be immediately obvious:

  • Alternate pins for a peripheral block are selected via Alt + Left Click, as described in the example above.
  • With Ctrl + K, you can set or unset the flag Pinout -> Keep Current Signal Placement. If this flag is set, no already assigned pins will be automatically changed. If it is not set, pins marked with the Signal Pinning button will not be changed, but others may automatically change when selecting peripherals that use them. I recommend always setting this flag to prevent pins from changing their positions unexpectedly.
  • It is better to assign user names (LED and BTN in the example above) to pins used as GPIO. This allows for easy identification when writing code using auto-completion. For other peripherals, this does not make sense, as they are accessed using the IP block number.
  • Using Alt + L or Pinout -> List pinout comparable MCU's, you can find pin-to-pin compatible controllers for the current pin configuration.
  • Alt + D or Help -> Docs & Resources will open a menu with all the documents for this controller, such as Errata, Reference Manual, Application Note, etc.
  • Ctrl + Left Click allows you to move a pin function to an available position.

Now, a few words about Clock Configuration. Don't be afraid of the abundance of windows with adjustable values; in reality, we need only a few things from here.

cubemx_clk

The most important thing is to check that the quartz crystal frequencies match the actual crystal on the board (1). In my case, they didn't match, so I changed it from 8 MHz to 24 MHz. Next, you can enable PLL clocking from the crystal (2) and set the PLL signal as the clock source for the CPU and buses (3). After this, you can set the maximum possible core frequency (4) to achieve maximum performance.

This is sufficient in 90% of cases. Complications may arise if a peripheral block requires a specific frequency, such as CAN. In this case, you can either clock the peripheral from another part of the PLL (on the screenshot above, this is PLLQ or PLLP) or use an internal generator. CubeMX will try to find the intermediate values automatically, and if it fails, you can manually adjust them.

project_name

Next, we need to configure the project in the Project Manager tab. The following steps are necessary:

  1. Enter the project name in the Project name field (1).
  2. Specify the project location in the Project Location field (2).
  3. In the Toolchain/IDE section, select CMake (3). If you choose another tool, the VSCode extension will not work with the project.

If you select Basic instead of Advanced in the Application Structure section, the generated source files will be placed in the root folder instead of the Core folder. I do not recommend doing this. You can also change the minimum stack and heap size, which can be useful when using RTOS, but we won't change anything for now.

Additionally, several parameters should be changed in Project Manager -> Code Generator:

  • In the STM32Cube MCU packages and embedded software packs section, it is better to select the option Copy only the necessary library files to avoid storing unused libraries in the project.
  • In the Generated files section, check Generate peripheral initialization as a pair of .c/.h files per peripheral so that CubeMX generates separate files for the description of various peripheral blocks instead of putting everything in main.c.
  • Enable Enable Full Assert in HAL Settings. This feature activates the assert_failed callback when an error occurs in the peripheral operation. The template for this callback is located in main.c. The callback returns the file name and the line of code where the error occurred. For this function to work, the names of all project files with their paths need to be stored in flash memory, so it is better to disable this function in the release version or if memory is scarce.

The last tab, Advanced Settings, allows you to choose the HAL or LL library for a specific peripheral block and configure the initialization sequence of the modules, which can be useful if there are initialization problems. In the Register Callback menu, you can select the peripheral for which the new callback mechanism will be used (passing the callback function address through the handler). If you leave it as Disable, the old __weak function mechanism will be used.

VSCode

At this point, the work in CubeMX is complete, so let's consider working with the VSCode editor. After installing the plugin, a new tab with a butterfly icon (1) will become available in VSCode.

vscode_stm32

Immediately after installing the plugin, it will likely ask you to specify the path to the CubeCTL folder. You need to provide the path you chose during installation.

The extension allows you to perform a number of functions:

  • Import a project with CMake (2). This function will be used later.
  • Launch CubeMX (3). This option simply launches CubeMX without opening the current project.
  • Create an empty project (4). This command opens a menu where you can select a microcontroller or debug board, and a bare project without HAL, LL, or even CMSIS will be generated. The project will contain only a linker script, a CMake build script, and a startup file in assembly.
  • Launch MCU Finder (5), if it was installed.
  • Update the stlink firmware.

For further work, you need to select project import (2), specify the path to the project generated by CubeMX, and then confirm the import.

import

This function will generate a .vscode folder in the project root, containing a set of parameters for VSCode.

As can be seen from the description above, the project import function is the only useful one among those presented, so the menu itself is not particularly useful. To hide the icon in the right menu, right-click on it and select Hide STM32 VS Code Extension (you can bring it back by right-clicking in any area of the right menu). To access the project import function, you can use the console by pressing Ctrl + Shift + P and start typing stm32:

alt_exec

After this, the project will be ready to work. However, for the linter to work correctly, the project needs to be configured. This can be done through the CMake menu, but it is more convenient through the command menu: Ctrl + Shift + P -> CMake: Configure. You can build the project using the command Ctrl + Shift + P -> CMake: Build.

There is a chance that the Import CMake project into VS Code command will not be available. In this case, you first need to launch STM32 VS Code Extension: Focus on Project Manager View, after which the command will appear. It seems that this menu triggers some initialization upon the first launch, without which the import is not possible.

Project Organization in C

Let's look at the structure of a typical project. To do this, in addition to what CubeMX has created, we need to add three more folders:

  • App - The main application logic.
  • Bsp - Libraries for devices (chips) located on a specific board.
  • Docs - Documentation required for the project.

In each of these folders (except Docs), create two subfolders: Src and Inc, to maintain consistency with the code generated by CubeMX. The Bsp folder will not be needed in our example, as we will be interacting with peripherals at the HAL level. This is sufficient for our example, but for serious solutions, it is better to wrap such things in BSP level functions.

Next, create two files: App/Src/app.c and App/Inc/app.h:

mkdir App App/Src App/Inc && touch App/Src/app.c App/Inc/app.h

app.h should include stdint.h. This is necessary so that our function definitions can contain additional types such as int8_t, uint32_t, and similar types. Additionally, the file should include the definition void app(void), which will be used as main():

#ifndef __APP_H__
#define __APP_H__

#include <stdint.h>
void app();

#endif /* __APP_H__ */

In app.c, we will implement the working logic:

#include "app.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>

uint32_t press_count = 0;

void app()
{
    while (1)
    {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        static uint32_t i = 0;
        printf("N: %ld, Buttion count: %ld.\r\n", i++, press_count);
        HAL_Delay(500);
    }
}

int _write(int file, char *ptr, int len)
{
    (void)file; // hack for fix warning - unused int file.
    HAL_UART_Transmit(&hlpuart1, (uint8_t *)ptr, len, 1000);
    return len;
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_13)
        press_count++;
}

This program toggles the LED state twice per second and prints a message to the console with its number and the number of button presses.

The function int _write() needs to be defined for printf() to work correctly. It is implemented as blocking, which may not be the most optimal implementation, but it will suffice for our example.

Next, you need to add a call to app() in main.c and add #include "app.h", as well as implement the assert_failed function:

/* USER CODE BEGIN Includes */
#include "app.h"
/* USER CODE END Includes */
// ....
/* USER CODE BEGIN WHILE */
app();
while (1) {
/* USER CODE END WHILE */
// ....
/* USER CODE BEGIN 6 */
printf("Wrong parameters value: file %s on line %ld\r\n", file, line);
/* USER CODE END 6 */

To ensure that our code is not removed when peripheral parameters are changed, it must be placed between USER CODE BEGIN and USER CODE END blocks. Therefore, the example above includes the lines that need to be inserted in the correct places in the generated main.c. By creating a separate folder for the project's source code, we avoid the need to track CODE BEGIN -> CODE END blocks, which greatly simplifies life.

The last block of code with Wrong parameters value is the implementation of void assert_failed(uint8_t *file, uint32_t line). This function will be called in case of an error inside HAL and will output the line number and the path to the source file in the project. Since all paths are stored in flash memory, it is better to disable this function in the release.

Next, to ensure our new code is built and the linter understands it correctly, the information about the new files needs to be added to the lists of header and source directories within CMake, as follows:

# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    "App/Src/app.c"
)

# Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    "App/Inc"
)

While writing this code, you might have noticed (there is a chance this has been fixed by the time you read this article) that the PROBLEMS tab shows one issue - a warning in CMakeLists.txt related to enable_language() being called before project(). This can be easily resolved by swapping these lines:

# Core project settings
project(${CMAKE_PROJECT_NAME})
message("Build type: " ${CMAKE_BUILD_TYPE})

# Enable CMake support for ASM and C languages
enable_language(C ASM)

This file is generated only once, and changes will not be overwritten when the project is regenerated.

Next, let's consider some useful but not immediately obvious things:

  • Ctrl + Shift + I will trigger auto-formatting. In the VS Code settings, you can choose the formatting style, but I usually use the standard one.
  • Ctrl + P - search for a file by name in the project.
  • Alt + Backspace - can be set as the hotkey for returning to the previous cursor position. To do this, set the hotkey for Go Back (open the keybindings menu with Ctrl + K -> Ctrl + S).
  • To enable the display of all characters (spaces, tabs, etc.), set editor.renderWhitespace to all.
  • To disable middle-click paste, uncheck editor.selectionClipboard.
  • To display a vertical ruler at 80 characters, set "editor.rulers": [80]. You can also set multiple values separated by commas to display several vertical lines, for example: "editor.rulers": [100, 80].

If you need spell checking, you can install the streetsidesoftware.code-spell-checker extension. However, after installation, you might find that all spell checker warnings are displayed in the PROBLEMS panel, making it difficult to see linter warnings. To solve this problem, switch to the beta version of the extension, which will display warnings in a separate window.

Project Organization in C++

To add support for C++, you need to create the same files (delete the previous ones), but with .cpp instead of .c and .hpp instead of .h.

mkdir App App/Src App/Inc && touch App/Src/app.cpp App/Inc/app.hpp

In app.hpp, add the definition for the app() function, as well as a wrapper for calling this function from C code app_c():

#ifndef __APP_H__
#define __APP_H__

#include <stdint.h>

#ifdef __cplusplus
extern "C"
{
#endif
    void app_c(void);
#ifdef __cplusplus
}
#endif

void app(void);

#endif /* __APP_H__ */

In the app.cpp file, define the app() function, which will become our new main(), and also add a TEST class as an example. Naturally, in normal code, this class should be moved to a separate file, but for illustration purposes, this will do.

app.cpp:

#include "app.hpp"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>

uint32_t press_count = 0;

extern "C" void app_c(void)
{
    app();
}

class TEST
{
public:
    void toggle_print();
};

void app(void)
{
    TEST blink;
    while (1)
    {
        blink.toggle_print();
        HAL_Delay(100);
    }
}

void TEST::toggle_print()
{
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    static uint32_t i = 0;
    printf("N: %ld, Buttion count: %ld.\r\n", i++, press_count);
}

extern "C" int _write(int file, char *ptr, int len)
{
    (void)file; // hack for fix warning - unused int file.
    HAL_UART_Transmit(&hlpuart1, (uint8_t *)ptr, len, 1000);
    return len;
}

extern "C" void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_13)
        press_count++;
}

Also, in main.c, replace the call to the app() function with app_c(), and include app.hpp instead of app.h. It is important to note that app.hpp is added to main.c, so it cannot contain any classes or other C++ specific constructs. It is better not to add anything else to this file, and implement additional program logic in other files.

To override C functions within C++, you must use extern "C", otherwise the linker will not be able to find the desired function. This is related to the implementation of static polymorphism in C++ - the language saves the type of parameters within the function label name (name mangling). You can read more about this here.

Also, you need to change CMakeLists.txt:

# Enable CMake support for ASM and C languages
enable_language(CXX C ASM)

# .......

# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    "App/Src/app.cpp"
)

# Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    "App/Inc"
)

CXX adds the C++ compiler.

Debugging the Project

To flash and debug, you can use the side debug menu (1) -> (2) or the F5 button:

debug

Here you might encounter an issue where the debugger simply does not start and no error messages are displayed. I faced this problem on Ubuntu 24.04 LTS and spent a lot of time resolving it. It turned out that the error could be found by running the arm-none-eabi-gdb debugger directly from the console:

zen@pc:~$ arm-none-eabi-gdb
arm-none-eabi-gdb: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory

If you search for a solution on the ST forum, they will kindly inform you that Ubuntu 24.04 LTS is simply not supported. Instead, we will just download and install the missing components manually:

wget http://archive.ubuntu.com/ubuntu/pool/universe/n/ncurses/libtinfo5_6.4-2_amd64.deb && sudo dpkg -i libtinfo5_6.4-2_amd64.deb && rm -f libtinfo5_6.4-2_amd64.deb
wget http://archive.ubuntu.com/ubuntu/pool/universe/n/ncurses/libncurses5_6.4-2_amd64.deb && sudo dpkg -i libncurses5_6.4-2_amd64.deb && rm -f libncurses5_6.4-2_amd64.deb

Next, let's take a closer look at the debugging process:

debug_view

  1. Local, static, global variables and processor registers. This field is available in any IDE.
  2. Watch specific variables. In this field, you can select specific variables to watch and specify the output format. For example, var,h will display the variable in hex format. Available formats are b (binary), o (octal), c and s (string). You can also use C type casts, for example, *var will display what is at the address in var, *(char *)(0x2002ff64) will display the 8-bit value at the raw address 0x2002ff64, *(uint32_t[10] *)(0x2002ff64) will display an array of 10 elements at the raw address 0x2002ff64.
  3. Call stack. An interesting feature is that you can open the disassembly of a specific function via Right click -> Open Disassembly View.
  4. List of breakpoints.
  5. Field for real-time monitoring of global variables. However, to enable this feature, you need to add the following to the .vscode/launch.json file under configurations:
"liveWatch": {
    "enabled": true,
    "samplesPerSecond": 4   // частота опроса в секунду
}
  1. View peripheral register values with addresses taken from SVD files in CubeCTL.

The following windows are always available, not just in debug mode, but most of them are needed during debugging, so I will describe them here.

  1. Problems - This window will display all errors and warnings from the compiler or linter. Ideally, it should be empty.
  2. Terminal - This window shows the output of console utilities used for building and debugging, such as CMake and GDB server. You can also create your own terminal here if needed.
  3. Output - Shows plugin outputs. Like the previous window, it's not particularly useful.
  4. Memory - This tab allows you to read large chunks of memory starting from a specific address and save it for further analysis. For example, in the above project, you can specify the address as &press_count and see the memory chunk located at that address.

mem

  1. Xrtos - Allows you to monitor tasks when using RTOS. However, for this to work, RTOS must be built with certain parameters to include the necessary additional information.
  2. Debug console - This is the GDB debugger console. You can directly enter commands and see the results. For example, the command set output-radix 16 will display everything in HEX format, while set output-radix 10 will return everything to the default format. The same effect can be achieved by clicking 0x in the header of window (1), (2), or (5).
  3. Serial monitor - This is a serial console built into VSCode. However, you need to install a separate plugin for it - ms-vscode.vscode-serial-monitor.

serial

  1. Spell Checker - This window appears if you install the streetsidesoftware.code-spell-checker plugin, as described above.
  2. Menu with standard actions such as stopping execution, continuing, stepping forward, etc.

This setup is sufficient for comfortable work, but I would like to highlight a few more points:

  • You might not always need the program to stop at main() when debugging starts. To disable this breakpoint, you need to comment out the parameter in the .vscode/launch.json file:
// "runToEntryPoint": "main",
  • To build not only elf but also bin and hex, you need to add the following to the end of CMakeLists.txt:
# Add custom commands to generate .bin and .hex files
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:${CMAKE_PROJECT_NAME}> ${CMAKE_PROJECT_NAME}.bin
    COMMAND ${CMAKE_OBJCOPY} -O ihex $<TARGET_FILE:${CMAKE_PROJECT_NAME}> ${CMAKE_PROJECT_NAME}.hex
    COMMENT "Building ${CMAKE_PROJECT_NAME}.bin and ${CMAKE_PROJECT_NAME}.hex"
)
  • Sometimes it is useful to add new compilation flags. For example, you can add -fsingle-precision-constant, which makes the compiler treat all floating-point constants as float. This is extremely useful because the STM32G4 FPU only works with FLOAT, and now you won't need to write f after each constant. To do this, add a couple of lines in the middle (after the description of Debug and Release) of cmake/gcc-arm-none-eabi.cmake:
# Add new C & C++ flag
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsingle-precision-constant")

GIT

To work with any Git service, you first need to create an empty project on the service with the same name as your current project. For me, this will be stm32_vscode_guide_exemple (1). It's better to use lowercase letters in the name since it will become the URL address.

git

Next, you can fill in the Description (a brief project description) (2) and choose the project type (3), either private or public. I choose public so that you can view this project on my GitHub.

The following options (4), (5), and (6) should not be selected, because if you do, files will be created in the repository, and we won't be able to commit the existing project.

Then, create the repository using the Create repository button.

The next steps will take place locally with the project. First, you need to create two files: README.md and .gitignore.

touch README.md .gitignore

The first file will contain the text displayed on the main page, and the second file is a list of files that will be ignored by the Git system. You can write anything you like in the first file. In the second file, list the files and folders that are temporary and don't need to be included in the project, primarily the build folder.

build/
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
build.ninja
.mxproject

After this, you need to initialize the project and push it to the server.

git init
git add -A
git commit -m "first commit"
git remote add origin https://{your_git_address}/stm32_vscode_guide_exemple.git
git push -u origin master

Replace {your_git_address} with your Git server address.

There is a possibility that your default branch will be main instead of master. You can check this using the git status command. In that case, use main instead of master in the last command.

Also, if you are using Git for the first time, it will prompt you to enter your name and email.

You can learn more about Git on githowto. Common problems are described in Oh Shit, Git!?!.

Links