Advanced Embedded CMake Project Structure

This page explains the typical structure of VisualGDB Embedded projects based on the Advanced CMake Project Subsystem, and gives an overview of common features and settings.

Contents

Project Structure
Reusing Code
Debugging CMakeLists.txt
Configuration
Managing BSPs
Cloning Shared Code
Managing Platforms
Unit and Integration Tests
Embedded Resources
Code Coverage
Reference

Project Structure

Embedded CMake projects target barebone devices such as STM32. They produce binaries that can run directly on the target device without any external OS or other components. To facilitate that, each embedded project includes the following components:

  1. The project file itself (<Project>.vgdbcmake). It stores global settings (such as the toolchain used to build the project and the setup used to debug it). Note that the project structure (executables, libraries and BSP references) is defined in CMakeLists.txt files and is not duplicated in the project file.
  2. The BSP (Board Support Package) referenced via find_bsp(). It contains the startup code, drivers for peripherals, and is typically split into multiple frameworks, such as STM32F4 HAL Library or FreeRTOS. BSPs are automatically installed by VisualGDB and cannot be edited unless you convert the project into a stand-alone one. Normally, one embedded project only contains one BSP and every BSP-based executable or library implicitly references it. However, it is possible to manually pull multiple BSPs with different aliases into the same project, and then change the referenced BSP for each executable or library.
  3. One or more executable targets defined via add_bsp_based_executable(). Only one executable can be programmed into the device’s FLASH memory at the same time, however you can build several executables within the same project. Each executable must reference a BSP and will be linked with all libraries included in that BSP.
  4. Zero or more static libraries defined via add_bsp_based_library(). They contain reusable code that could be shared between multiple executables. Each static library must reference a BSP and will automatically receive include directories and preprocessor macros defined by it (e.g. will be able to call the STM32 HAL functions).
  5. Zero or more stand-alone frameworks. These frameworks are created by forking the frameworks included in a BSP (e.g. STM32F4 HAL Library) and can be edited arbitrarily. You can read more about stand-alone frameworks in this tutorial.

Reusing Code

The CMake-based embedded projects are designed to maximize reuse of code and have as little hardcoded parameters as possible. Normally, the project structure is defined in a CMakeLists.txt file using the VisualGDB Embedded CMake Framework statements. Below is a sample CMakeLists.txt file:

cmake_minimum_required(VERSION 3.15)
project(EmbeddedCMakeDemo LANGUAGES C CXX ASM)
find_bsp(ID com.sysprogs.arm.stm32
	VERSION 2020.06
	MCU STM32F407VG
	FRAMEWORKS com.sysprogs.arm.stm32.hal com.sysprogs.arm.stm32.ll)
add_bsp_based_executable(NAME EmbeddedCMakeDemo
	SOURCES EmbeddedCMakeDemo.c
	GENERATE_BIN)

Note that the CMakeLists.txt file references a specific version of the STM32 BSP, but does not explicitly list the exact list of source files included in it (such as stm32f4xx_hal.c). The exact files will be computed and shown in Solution Explorer when the project is loaded. If you change the BSP configuration (e.g. use a BSP version, or include FreeRTOS), VisualGDB will update the find_bsp() statement accordingly, and the exact list of files will be automatically recomputed.

You can move the definitions of targets (e.g. add_bsp_based_library()) to CMakeLists.txt files in other directories and reference them via add_subdirectory(). This way, multiple embedded projects can reuse the same definition of a static library. Because the target definitions implicitly reference the default BSP, you can share the same library definition between projects targeting different devices, or event between Embedded and Linux projects. See the tutorial on multi-target projects for more details.

Debugging CMakeLists.txt

You can step through CMakeLists.txt files, set breakpoints and evaluate variables using VisualGDB’s CMake debugger. Simply select “Launch CMake Debugger” in Solution Explorer to begin:

Configuration

You can configure most of the project-level settings (that apply to all targets) via the VisualGDB Project Properties window. This includes the toolchain, build command customization, debugging settings and many other parameters. You can also change the settings for the default BSP (e.g. target a different device or reference additional frameworks):You can also configure individual BSPs or frameworks via varous context menu commands in Solution Explorer: In order to change properties for individual targets (e.g. optimization level, or include directories), right-click on a specific targets and select “Properties”:Changing these properties will automatically update the corresponding CMake statements, so you won’t need to edit them manually.

Since typically all targets targeting the same device would share many common settings (e.g. language standard, use of exceptions, etc), VisualGDB allows editing them via the BSP properties:BSP-level settings will automatically apply to all targets within the project referencing a specific BSP instance, so you won’t need to specify them separately for each target.

Managing BSPs

Once a BSP is pulled into the project via the find_bsp() statement, it will act like a CMake library. Referencing this library from any target will make sure that the target is built for that device. The same project can have multiple find_bsp() statements with different aliases:Each statement will bring in its own instance of the BSP, completely separate for other instances. Different BSP instances can target different devices (from the same vendor or different vendors), reference different sets of frameworks (e.g. with FreeRTOS vs. barebone) or have different toolchain parameters (e.g. Software FP vs Hardware FP).

BSPs can be converted to stand-alone BSPs via a context menu command:Stand-alone BSPs reside in a subdirectory inside the project directory, and can be edited arbitrarily.

Stand-alone BSPs (including manually created projects) can be exported into regular shared BSPs by selecting “Export to a regular BSP” in Solution Explorer:

You can find a detailed list of different strategies for managing BSPs and other shared embedded code on this page.

Cloning Shared Code

Although the exact list of files inside the BSP is controlled by VisualGDB and cannot be manually edited, you can easily copy individual files (1), linker scripts (2) or embedded frameworks (3) under the project directory, making them editable:  Note that if you later update to a newer BSP version, you will need to manually merge the changes introduced in it to the cloned files.

Managing Platforms

A single Embedded CMake project can define multiple platforms and configurations targeting other embedded devices, Linux boards, or creating Windows executables for simulation. You can edit the platforms and configurations via VisualGDB Project Properties -> Configuration -> Manage:You can also use the “Create Simulation Platform” command in Solution Explorer to quickly create a Windows platform for simulating your device. See this tutorial for a detailed explanation.

Unit and Integration Tests

Embedded CMake projects can have one or more test executables. Tests from test executables will automatically appear in the Test Explorer window, and can also be launched programmatically on a build server. Each test executable must reference a test framework that is imported into the project using the find_test_framework() statement:See our documentation on Unit Tests for more details or follow our Embedded Simulation tutorial for a detailed step-by-step introduction to unit tests.

Embedded Integration tests can be recorded via the Test->Record Embedded Integration Test command:Integration tests record how the program responds to various debugger actions (e.g. setting breakpoints, changing variables) and can later automatically verify that this behavior has not changed (e.g. all threads have started successfully and all periodically called functions are still called).

Embedded Resources

You can embed arbitrary files into your projects via the Add->Add Embedded Resource commands:Embedded resources are internally implemented via the add_embedded_resource() statement and can be accessed from the application code by including the <EmbeddedResources.h> file and using variables from the EmbeddedResources namespace (C++) or top-level variables (Plain C):You can embed an output from one target into another target using the Embed Another Target command described in our Bootloader Tutorial.

Code Coverage

Embedded CMake projects support Code Coverage and Live Coverage via the bsp_configure_code_coverage() statement. You can configure it via the Code Coverage page of VisualGDB Project Properties: Enabling Code Coverage will affect the entire BSP and all targets using it. You can disable it, or exclude individual files/targets via VS properties for the BSP node:Alternatively, use the Disable Code Coverage command on individual files or targets:

Reference

You can find detailed reference of all CMake statements used by the VisualGDB Embedded CMake framework on the following page: https://visualgdb.com/reference/cmake/.