Skip to content

Template Repo for STM32 projects with platform configured

License

Notifications You must be signed in to change notification settings

waterloo-rocketry/stm32_template_platformio

Repository files navigation

STM32 Embedded Project Template

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.

READ FIRST: How to customize this template

  1. Inside platformio.ini, address all the "TODO:" comments:

    1. Change the placeholder RocketCAN definitions
    2. Customize build flags (ex, remove FPU flag if not supported)
    3. Change the board_build settings according to the actual MCU being used
    4. Change the board = ... to be the actual MCU being used, or a similar one if that doesn't exist in PlatformIO
    5. Customize canlib inclusion in build_src_filter = to be the correct MCU family
  2. Open STM32CubeMX and generate a fresh .ioc file for your specific MCU. Use File > Save Project As to save it to a temporary empty folder (ie, make a new folder anywhere else on your laptop). Doing Save Project As is REQUIRED or else the next steps break.

  3. 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-S but do not generate code yet.

  4. Move the new .ioc file into this folder, replacing PLACEHOLDER.ioc. Delete the placeholder.

  5. Re-open the new ioc in CubeMX, and click Generate Code.

  6. After code-generation is done, you MUST run the script ./scripts/cubemx_cleanup.sh in terminal.

Project Structure

  • src/drivers/: custom peripheral driver modules
  • src/application/: high-level application logic modules
  • src/third_party/: third-party libraries
  • src/common/: shared resources specific to this project
  • tests/: everything for testing
  • Everything else is autogenerated by STM32CubeIDE with few modifications

Developer Setup - Firmware

All firmware dev (build, flash, debug) is done using platformio in vscode.

1. Clone repo

  • 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.)

2. Open project in vscode with platformio extension

  • Install the platformio extension in vscode
  • Open this project folder
  • Platformio should automatically detect the platformio.ini file and configure everything

3. Build, upload to board

  • Use an ST-Link programmer to connect to the target board
  • Use the platformio sidebar tab Build to only build
  • Use Upload to build + upload together

4. Debug

  • Use ST-Link to connect to the board
  • Use vscode Run and Debug sidebar tab to build, upload, and start the debugger

Unit Testing

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.txt which 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 test

  • Add a new test group file in tests/unit/. See test_dummy.cpp for 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)

Add a mock

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.c uses FreeRTOS semaphores via #include "semphr.h.

    • In the actual firmware, the real semphr.h is included when compiling. But for unit tests, the real semphr.h is not included when compiling. So, the unit tests fail to compile (it can't find a semphr.h file).
      • To correct this we add a "fake" semphr.h in tests/mocks/semphr.h. All files in this folder are included when compiling unit test, so the tests now compile.
    • The gpio code uses functions from the real semphr.h like xSemaphoreTake(). These don't exist in our fake semphr.h yet.
      • 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 for xSemaphoreTake():

        // 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...) in mocks/semphr.h. Then, put the actual definition (DEFINE_FAKE...) in the corresponding mocks/semphr.c.

  • 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.c takes 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.h header is built in unit tests, but the source file flight_phase.c is not.
      • Since a function declaration for get_flight_phase() exists in the header, we can use fff's DEFINE_FAKE... to create a fake definition of that get_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...)

Run/debug tests

  • Build in vscode using cmake (see step 3 above)
    • The default Build Unit Tests With Coverage preset also automatically runs all tests and generates coverage report.
    • View the coverage report html pages in build/test/coverage_report in a local browser
  • Use the vscode cmake Launch and Debug tabs to run/debug individual test groups (cmake shows the available test groups)

Debugging on hardware

  • 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.

Code Standards

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.

Adding Log Messages

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;
  • scripts/logparse.py
    • Add a new format spec to the FORMATS dict:
       FORMATS = {
      +    M(unique_small_integer): Spec(name, format, [field, ...]),
       }

Notes

  • Auto-gen stm32 files used STM32CubeMX Version: 6.12.1-RC4, STM32CubeIDE version 1.16.1

Things included in this template project

  • 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

About

Template Repo for STM32 projects with platform configured

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages