Template repo for STM32 projects on the team. Based on Processor 2025 Firmware. For firmware dev (ie build, flash, debug), it uses PlatformIO. For unit testing, it uses CMake in a devcontainer. Firmware dev and unit testing are completely separate, so projects with 0 unit tests don't need to touch the devcontainer.
-
Inside
platformio.ini, address all the "TODO:" comments:- Change the placeholder RocketCAN definitions
- Customize build flags (ex, remove FPU flag if not supported)
- Change the
board_buildsettings according to the actual MCU being used - Change the
board = ...to be the actual MCU being used, or a similar one if that doesn't exist in PlatformIO - Customize canlib inclusion in
build_src_filter =to be the correct MCU family
-
Open STM32CubeMX and generate a fresh .ioc file for your specific MCU. Use
File > Save Project Asto save it to a temporary empty folder (ie, make a new folder anywhere else on your laptop). DoingSave Project Asis REQUIRED or else the next steps break. -
Ensure these settings are correct in CubeMX:
-
In
Project Manager->Project- Project Name - anything
- Application Structure -> Basic. Leave "Do not generate the main" unchecked
- Toolchain -> STM32CubeIDE, check "Generate Under Root"
-
In
Project Manager->Code Generator- Select "Copy only the necessary files"
- Check all 4 boxes under "Generated files"
- Leave everything else unchanged
-
In
Pinout & Configuration, configure as needed. -
Save the ioc using
Ctrl-Sbut do not generate code yet.
-
-
Move the new .ioc file into this folder, replacing
PLACEHOLDER.ioc. Delete the placeholder. -
Re-open the new ioc in CubeMX, and click
Generate Code. -
After code-generation is done, you MUST run the script
./scripts/cubemx_cleanup.shin terminal.
src/drivers/: custom peripheral driver modulessrc/application/: high-level application logic modulessrc/third_party/: third-party librariessrc/common/: shared resources specific to this projecttests/: everything for testing- Everything else is autogenerated by STM32CubeIDE with few modifications
All firmware dev (build, flash, debug) is done using platformio in vscode.
- Clone repo and initialize submodules:
git clone --recurse-submodules https://github.com/waterloo-rocketry/stm32_template_platformio- (Note: if you choose to clone with ssh instead, you have to manually setup ssh forwarding in the devcontainer.)
- Install the platformio extension in vscode
- Open this project folder
- Platformio should automatically detect the
platformio.inifile and configure everything
- Use an ST-Link programmer to connect to the target board
- Use the platformio sidebar tab
Buildto only build - Use
Uploadto build + upload together
- Use ST-Link to connect to the board
- Use vscode
Run and Debugsidebar tab to build, upload, and start the debugger
TODO: make this exist for platformio...
We use GoogleTest and Fake Function Framework (fff) for unit testing. All testing-related files are in tests/.
- Tests are built from
tests/CMakeLists.txtwhich is separate from the project's main build config. Building and running tests is done via cmake. - Test source code should be written in
tests/unit/. - Mocks should be made with fff in
tests/mocks/.
- Add a new test group file in
tests/unit/. Seetest_dummy.cppfor example of test structure. - Add the test group to the cmake build system by editing
tests/CMakeLists.txt:- At the bottom of the file, add the new test group using the
add_test_group()helper. (Read the comments + existing examples explaining how it works)
- At the bottom of the file, add the new test group using the
We do not include the STM32 HAL library nor FreeRTOS when compiling the project for unit tests. So if a source file uses a HAL or FreeRTOS file, those files and their functions must be mocked using fff. This works similarly for mocking other proc modules that a test interacts with but doesn't test.
Example 1:
-
src/drivers/gpio/gpio.cuses FreeRTOS semaphores via#include "semphr.h.- In the actual firmware, the real
semphr.his included when compiling. But for unit tests, the realsemphr.his not included when compiling. So, the unit tests fail to compile (it can't find asemphr.hfile).- To correct this we add a "fake"
semphr.hintests/mocks/semphr.h. All files in this folder are included when compiling unit test, so the tests now compile.
- To correct this we add a "fake"
- The gpio code uses functions from the real
semphr.hlikexSemaphoreTake(). These don't exist in our fakesemphr.hyet.-
To correct this we need to create a mock
xSemaphoreTake()function using fff. fff's Readme describes how to create fake functions. Here's the mock forxSemaphoreTake():// The func to mock: BaseType_t xSemaphoreTake(SemaphoreHandle_t arg0, TickType_t arg1) DECLARE_FAKE_VALUE_FUNC(BaseType_t, xSemaphoreTake, SemaphoreHandle_t, TickType_t);First we put the declaration (
DECLARE_FAKE...) inmocks/semphr.h. Then, put the actual definition (DEFINE_FAKE...) in the correspondingmocks/semphr.c.
-
- In the actual firmware, the real
-
Now in the gpio tests, we can access the mocked semaphore functions via fff to test that the gpio code uses semaphores correctly.
Example 2:
src/application/estimator/estimator.ctakes input from flightphase via#include "application/flight_phase/flight_phase.h.- To test estimator, we will test various inputs from flightphase. But we don't want to be testing flightphase at the same time. So, mock flightphase.
- The real
flight_phase.hheader is built in unit tests, but the source fileflight_phase.cis not.- Since a function declaration for
get_flight_phase()exists in the header, we can use fff'sDEFINE_FAKE...to create a fake definition of thatget_flight_phase(). That definition should be in the test .cpp file, or in the mocks folder if it's applicable for wider use. - (Note: unlike example 1 where the header isn't included in unit tests, the flightphase header is included so it doesnt need a
DECLARE_FAKE...)
- Since a function declaration for
- Build in vscode using cmake (see step 3 above)
- The default
Build Unit Tests With Coverage presetalso automatically runs all tests and generates coverage report. - View the coverage report html pages in
build/test/coverage_reportin a local browser
- The default
- Use the vscode cmake
LaunchandDebugtabs to run/debug individual test groups (cmake shows the available test groups)
- Use STM32CubeIDE debugging as directed above
- The ST-link programmer has a serial output so you can listen to uart4 from a laptop COM port. The printf library (NOT THE STDLIB PRINTF) is configured to print strings to that COM port. Use
printf_("string to print..")- note the_character.- This should rarely be used. Please instead learn how to use the debugger (breakpoints, dynamic print breakpoints, step, etc) for efficient and pleasant debugging.
This project follows the team-wide embedded coding standard.
-
The devcontainer sets up vscode format-on-save to automatically use the team's clang-format.
- In case you want to format manually, the script can be run from the project root directory:
./scripts/format.sh
-
Rocketlib is included at
src/third_party/rocketlib. -
Developers should be aware of the BARR Standard and make an effort to follow it.
When adding a new type of data log message, all of the following should be updated:
- src/application/logger/log.h
- Add a new enum value to
log_data_type_t:typedef enum { + LOG_TYPE_XXX = M(unique_small_integer), } log_data_type_t; - Add a struct definition of your message's data fields to
log_data_container_t:typedef union __attribute__((packed)) { + struct __attribute__((packed)) { + uint32_t l; + float f; + // ... + } typename; } log_data_container_t;
- Add a new enum value to
- scripts/logparse.py
- Add a new format spec to the
FORMATSdict:FORMATS = { + M(unique_small_integer): Spec(name, format, [field, ...]), }
- Add a new format spec to the
- Auto-gen stm32 files used STM32CubeMX Version: 6.12.1-RC4, STM32CubeIDE version 1.16.1
- canlib and rocketlib submodules in
src/third_party - Example initialization of a freertos blinky task in
src/application/init - FreeRTOS
- CubeMX cleanup script in
scripts - A basic Github actions workflow for CI pipeline
- Coming soon: unit test set up and examples
- Not included: LittleFS or any file system module