This project offers a complete build environment using Meson, specifically for C/C++ projects in embedded systems. Using this project should make it easy to cross-compile on different platforms, with minimal effort to build and test your code.
This project acts as an example/guide to build stm32 with Meson. Meson is a modern build system, based on python, which uses Ninja as backend. The reason for using Meson is because it's fast, multi-platform, supports cross-compiling, and is easy to maintain because of its good documentation. In addition to Meson, this project uses Doxygen for automatic documentation generation, and CppUTest as unit test framework.
Other build systems that have been evaluated are CMake and Make. CMake lacks documentation and good examples. The syntax is not intuitive, especially before modern CMake 3.0. Make is still a powerfull tool, but implementing non-recursive Make for large projects, especially with multiple targets, becomes complex, and maintaining it is a nightmare.
Goals of this project:
- Build speed is king
- Configuring and maintaining the build system should take minimal time
- Cross-platform support (Linux/Windows)
- Easy integration of submodules or third-party software
- Includes tools for testing and analyzing code
- Wel documented
Features:
- Cross-compiling
- Unit testing framework
- Code coverage
- Static code analysis
- Documentation with doxygen
- Auto-formatting code
- Dockerfile for dependencies
- Jenkinsfile
This sections describes how to get started with this project by installing the right dependencies. Since Meson supports multiple platforms, some subsections contain instructions for both Linux(recommended) and Windows.
If you have git installed, start with cloning this repository:
git clone https://github.com/FlyingBBQ/stm32_meson.git
The project has the following dependencies:
- Meson
- Ninja
- GNU Arm Embedded toolchain
- Doxygen
Optional for debugging and flashing
- gdb
- openocd
- STM32CubeProgrammer
In this section the installation of the dependencies is explained for both Linux and Windows
For Arch based systems:
Meson and Ninja
sudo pacman -S meson ninja
GNU Arm Embedded toolchain
sudo pacman -S arm-none-eabi-binutils arm-none-eabi-gcc arm-none-eabi-newlib
Doxygen
sudo pacman -S doxygen graphviz
Debugging
sudo pacman -S arm-none-eabi-gdb openocd
For APT based systems:
Meson and Ninja
sudo apt-get install python3 python3-pip python3-setuptools \
python3-wheel ninja-build
Then install Meson with pip
pip3 install --user meson
GNU Arm Embedded toolchain
sudo apt-get install gcc-arm-none-eabi binutils-arm-none-eabi
Doxygen
sudo apt-get install doxygen graphviz
Debugging
sudo apt-get install gdb-arm-none-eabi openocd
Intalling STM32CubeProgrammer:
Download STM32CubeProgrammer from ST (make an account in case you do not have one).
unzip en.stm32cubeprog.zip -d <destination_dir>
Run the installer
./SetupSTM32CubeProgrammer-2.1.0.linux
There are two ways to install the project on Windows: Native, or using virtualization. It is recommended to use virtualization, as this eliminates differences in software versions, and does not interfere with installed toolchains for other projects. Unfortunately, it will have a small impact on performance.
-
The easiest way to intall Meson and Ninja on windows is by using the MSI installer
-
A native compiler is needed to compile the test framework for the build machine (Windows). The Choice is either MinGW or Cygwin.
-
The GNU Arm Embedded toolchain can be downloaded from arm (win32-sha2 is recommended). Check the box to add the toolchain to your path!
-
Doxygen can be downloaded from the official website. Check the box to add Doxygen to your path!
- In order to generate graphs with Doxygen, Graphiz is needed.
-
Download STM32CubeProgrammer from ST (make an account in case you do not have one).
-
To debug your code OpenOCD is needed. Download the latest release (win32.zip recommended) and make sure to add the
bin
directory, containing theopenocd.exe
, to your path. For more information see Debugging.
Setting up the virtual development environment for the project is achieved with Vagrant and Virtualbox. The virtualization software is Virtualbox, which creates and runs the actual Virtual Machine VM. Vagrant is a scripting engine on top of a VM, which allows for easy setup and install of images.
Navigate to the config directory and start Vagrant:
cd <project-root>/config
vagrant up
Vagrant will now create the Virtualbox image and set up the VM. The initial setup will mainly install docker. This may take up to 5 min.
NOTE: If Vagrant fails to start the VM becuase of problems related to VT-x, you have to disable Hyper-V (Windows). Because of this, it is not possible to run Docker for Windows and Virtualbox simultaneously. Open a command prompt and run:
bcdedit /set hypervisorlaunchtype off
Reboot!
If Vagrant successfully started the VM, SSH into it:
vagrant ssh
You should now be greeted by a linux prompt, the project should be mounted in /project
:
cd /project
From the project we create the docker container with the build environment.
The provided script env.sh
automates the process of building the Docker container and running it.
On Windows, you might have to change the file-ending to be able to run the script: dos2unix env.sh
.
# on Windows convert the file endings (only need to do this once)
dos2unix env.sh
# set up the build environment
./env.sh
After successfully building the Docker container, you will be greeted by a new prompt root@build
Again, cd to the project directory.
cd /project
From here you can continue following the usage steps, or for a quick-start run:
. ./build.sh
To exit the VM, simply type exit
.
Stopping the VM is done with vagrant halt
.
Make sure to run vagrant provision
once in a while to keep packages up to date.
The complete build environment can also be build as Docker container. This drastically reduces setup time for the build environment as the only dependency needed is Docker. It is assumed Docker is already installed on your system.
To build and run the container:
cd <project_root>
# build the container from config/Dockerfile with the name "build_env"
docker build config/ -t build_env
# run the container and mount the <project_root> dir to /code
docker run -it -v $(pwd):/code build_env bash
You can now run meson and ninja from this container where your project can be found in the /code
directory.
After cloning this project and installing the dependencies, it's time to build the project. Meson only works from the Terminal / Command Line, which keeps usage on both Windows and Linux the same.
Start by navigating to the root directory of this project:
cd <root_directory>
Before you can compile your code, Meson needs to configure the project based on your system. The syntax to do this looks as follows:
meson <source_dir> <build_dir> [options]
As an example, have a look at the command found in build.sh
:
meson . build/debug --cross-file config/cross_linux.txt --buildtype=debugoptimized
This command will take the current directory .
as source directory, which is the default if no source directory is given.
It will configure the project in the build directory build/debug
.
The project will be cross-compiled with the config/cross_linux.txt
file, which contains all the compiler settings for the GNU Arm Embedded toolchain.
The last option configures the compiler optimization for this project, which is debugoptimized
.
It is strongly advised to always configure a new build directory when changing cross-compile or optimization settings. for example:
meson build/release --buildtype=release
After Meson has configured the project successfully, navigate to the build directory.
cd build/debug
From here, we can run Ninja to compile our code, similar to GNU Make:
ninja
ninja clean
Additional Ninja commands can be added by using run_target
from Meson.
ninja size
ninja flash # if STM32CubeProgrammer is installed and added to path
Writing good unit tests and using simple printf()
over UART should catch 90% of your bugs.
The debugger should always be your last line of defence.
This sections describes how to debug your code using VScode as graphical frontend.
Reasons to use VScode over, for example, IDE's like Eclipse:
- It's a simple text editor, no bloat
- Awesome extensions
- Integrates easily with external tools
- Can configure it as a full IDE
Assuming you have VScode, the GNU Arm Embedded toolchain, and OpenOCD installed, the following configuration is needed:
- Open VScode and open the root folder of your Meson project
- Install the
Cortex-Debug
extension - Go to Debug > Add Configuration and pick Cortex Debug: OpenOCD
- Make the configuration the following:
{
"name": "Cortex Debug",
"cwd": "${workspaceRoot}",
"executable": "${workspaceRoot}/build/debug/main.elf",
"request": "attach",
"type": "cortex-debug",
"servertype": "openocd",
"configFiles": [
"interface/stlink.cfg",
"target/stm32h7x.cfg"
]
}
Where executable
and configFiles
should match your board and .elf
You can find the available .cfg
configFiles in the location where you installed OpenOCD in the scripts directory.
Run the debugger!
The VScode debugging instructions for Windows are identical for Linux, since VScode is cross platform. Debugging from the command line is also possible with the following commands:
- Open a terminal and run
openocd -f interface/stlink.cfg -f target/stm32h7x.cfg -c "init"
- Open a new terminal, and
cd
to the directory of your.elf
- run
arm-none-eabi-gdb
then:target remote localhost:3333
file <yourfile.elf>
monitor arm semihosting enable
monitor reset halt
- From this point you can set breakpoints, and step through your code.
Of course typing these commands every time is quite cumbersome. Since we are on linux we can automate this with a simple shell script like:
#!/bin/sh
arm-none-eabi-gdb --eval-command="target remote localhost:3333" $1 \
--eval-command="monitor arm semihosting enable" \
--eval-command="monitor reset halt"
Let's assume this script is called debug.sh
.
You could then run debug.sh <yourfile.elf>
and start debugging!
The current test framework is CppUTest, a C++ unit testing framework desinged for embedded systems.
It was added as a git subtree
as we will only pull from this repository.
If we would like to push as well, it should be added as submodule
.
Adding the test repository was done with the following command:
git subtree add --prefix test/framework https://github.com/onqtam/doctest.git master --squash
Updating the test framework is simply done by using the same command, but this time a pull:
git subtree pull --prefix test/framework https://github.com/onqtam/doctest.git master --squash
Running the test can be done directly with Ninja:
ninja test
It is not easy to see which parts of the project are git subtrees. The built-in solution Meson offers for this are subprojects; which can be included either from files or git repositories by using a wrap-file.
This does require the subproject to have a meson.build
file.
Building a Test Framework as subproject in Meson: mesonbuild/meson#4605 (comment)
Another good option for adding a Test Framework is to use it as a subproject that you build with a native configuration. You could then install the Test Framework libraries, and search for them from other projects. For example:
if not meson.is_cross_build()
install test libraries
endif
Then build and install them with a non cross-build: meson build/testframework
.
From your cross-builds you just search for the depencencies: test_framework = dependency(test_libs)
.