PlatformIO with STM32Cube and Ceedling

I frequently use STM32 based cards in my personal projects. It is also an opportunity to experience and use different tools that I appreciate. However, I often have to pause certain projects for a few weeks. Therefore, it is essential to use testing and documentation tools.
For all these reasons, I created this tutorial for using PlatformIO with STM32cube and Ceedling.
This set of tools allows for development of embedded software for STM32 driven testing with VSCode editor.
Indeed, using a test-driven development method brings a lot of benefits.

List of some advantages of test-driven development (TDD):

  • Avoids forgetting to implement features.
  • Productivity is enhanced with the division into small features.
  • The code is cleaner, tidier, and has fewer errors.
  • Dependencies and interfaces are better managed.
  • Portability and refactoring are easier.
  • The evolution of the software is secure.
  • The tests act as documentation.

PlatformIO already integrates a unit test system with Unity. However, Ceedling adds a CMock simulator which saves a lot of time when testing functions with a lot of dependencies.

Tool installation

VSCode

VSCode and PlatformIO have already been the subject of a previous article. They can replace the Arduino IDE: VSCode and PlatformIO

Windows :

Download and install VSCode.

Linux :

sudo apt-get install vscode

PlatformIO

First, install PlatformIO in VSCode. In the extensions menu, search for “PlatformIO” and click on “Install”.

VSCode: Installing the PlatformIO extension

Then, install the Python environment PlatformIO in the system because you have to use the command line:

Windows :

pip install -U platformio

Linux :

sudo pip3 install -U platformio

STM32CubeMX

STM32CubeMX is graphical C code configuration and generation software for STM32 microcontrollers and microprocessors.
Download link: STM32CubeMX

Ceedling

Ceedling is an automated test environment for programs written in C. It is particularly suitable for embedded software and test-driven development. It integrates Unity, CMock, and CException tools and provides source code simulation and test execution functionality.
You can find more information on the Throw The Switch website.

To start, you need to have Ruby installed. Then run the following command:

gem install ceedling

Creating the Project

I used a NUCLEO-L432KC board to write this tutorial. All the files are available on the pio_nucleo_l432 repository.
The commands and configurations are adapted to this card. You should adapt the content according to your target.

First, create the Ceedling project. Go to the project location folder and run the command:

ceedling new pio_nucleo_l432
Terminal: Ceedling project creation

Then, create the PlatformIO project in the directory created by Ceedling:

Terminal : PlatformIO project creation

Initial configuration of PlatformIO and Ceedling

To use the STM32Cube framework, you must modify the “platformio.ini” file as follows:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:nucleo_l432kc]
platform = ststm32
board = nucleo_l432kc
framework = stm32cube

Ceedling must know the location of the tested code.
Open the “project.yml” file and modify the “: paths:” part:

:paths:
  :test:
    - +:test/**
    - -:test/support
  :source:
    - src
    - lib/**
  :include:
    - include
  :support:
    - test/support
  :libraries: []

Now the project is ready to receive the source code.

Creation of the STM32Cube code

With the stm32pio utility, STM32CubeMX can be used directly with PlatformIO.
However, I don’t appreciate that a code generator directly modifies the code in my project. This can be dangerous and cause reliability problems.
Therefore, I prefer to generate a separate STM32CubeMX project and take the code from the source files.

Create an STM32CubeMX project and configure the devices.
Before generating the code, configure the “Project Manager” part as follows:

STM32Cube project configuration
Configuration of the STM32Cube code generator

At the beginning our project is empty, there is no problem to directly copy the files located in the “Src” and “Inc” directories of the STM32CubeMX project in the “src” and “include” directories of the PlatformIO project.

Thereafter, if we modify the configuration of the peripherals in STM32CubeMX, we must not simply copy the generated files. You have to analyze the generated code and copy portions into the source code of the project. To facilitate this task, I advise you to add as little as possible code specific to your application in the files generated by STM32CubeMX. Ideally, the application code is created in separate files.

At this point, open the project in VSCode and the compilation should succeed:

Compiling the PlatformIO project

Finally, the project is ready to use PlatformIO with STM32cube and Ceedling.

Ceedling usage

Unit test

Before running the test of our program, we must create functions to test. The following examples incorporate simple mathematical functions.
Good practice is to separate functions and write the code in different files. This forces us to write more generic functions, classify the functions and separate the hardware side from the application side. Finally, we obtain a portable code, easily usable with other microcontrollers and easier to test.
Besides, PlatformIO provides this way of doing. We are going to write the code modules in the “lib” library directory provided for this purpose.

Place the following files in the “lib/Calculator/src” directory:

calculator.h :

/**
 * @file calculator.h
 * @author David LEVAL (dleval@dle-dev.com)
 * @brief calculation function 
 * @version 1.0
 * @date 2021-05-17
 * @copyright Copyright (c) 2021
 */

#ifndef CALCULATOR_H
#define CALCULATOR_H

#include <stdint.h>

uint32_t addition(uint32_t a, uint32_t b);
uint32_t substraction(uint32_t a, uint32_t b);
uint32_t multiplication(uint32_t a, uint32_t b);
uint32_t division(uint32_t a, uint32_t b);

#endif

calculator.c :

/**
 * @file calculator.c
 * @author David LEVAL (dleval@dle-dev.com)
 * @version 1.0
 * @date 2021-05-17
 * @copyright Copyright (c) 2021
 */

#include "calculator.h"

uint32_t addition(uint32_t a, uint32_t b)
{
    return a + b;
}

uint32_t substraction(uint32_t a, uint32_t b)
{
    return a - b;
}

uint32_t multiplication(uint32_t a, uint32_t b)
{
    return a * b;
}


uint32_t division(uint32_t a, uint32_t b)
{
    return a / b;
}

Ceedling automatically runs the tests located in the “test” directory. However, it is important to respect the test file naming convention. You must add “test_” before the name of the C file to test.

Create the associated test file “test_calculator.c” in the “test” directory:

#ifdef TEST

#include "unity.h"

#include "calculator.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test_addition(void)
{
    TEST_ASSERT_EQUAL_UINT32(5, addition(2,3));
}

void test_substraction(void)
{
    TEST_ASSERT_EQUAL_UINT32(3, substraction(6,3));
}

void test_multiplication(void)
{
    TEST_ASSERT_EQUAL_UINT32(6, multiplication(2,3));
}

void test_division(void)
{
    TEST_ASSERT_EQUAL_UINT32(4, division(8,2));
}

#endif // TEST

With “TEST_ASSERT_EQUAL_UINT32” functions, the expected result is defined in relation to the calls to the functions to be tested.

To start the unit test of the functions, just run the following command in the VSCode terminal:

ceedling test:all
PlatformIO unit test with STM32cube and Ceedling

All the tests present in our test file “test_calculator.c” were executed without problem. It seems that we have verified the behavior of all the functions of our module “calculator”. However, to ensure that all the possibilities have been tested, the test coverage calculation tool must be used.

Test coverage

Sometimes you need to know the test coverage of a project’s code. This allows you to know if all the possibilities of the functions have been tested.
Ceedling integrates a tool allowing to know the test coverage.

To add a test coverage analysis, open the Ceedling configuration file “project.yml” and add the gcov module:

:plugins:
  :load_paths:
    - "#{Ceedling.load_path}"
  :enabled:
    - stdout_pretty_tests_report
    - module_generator
    - gcov

Start the analysis by running the following command in the VSCode terminal:

ceedling gcov:all
PlatformIO test coverage with STM32cube and Ceedling

In the example, 100% of the code of the “calculator” module is tested.

Mock

With the CMock module integrated into Ceedling, it is possible to simulate functions. In the tested function, the dependencies are replaced by simulations in which the parameters and the returns are defined. This is very useful, for example, for hardware dependent functions or communications functions.
To do this, add “mock_” before the name of the header file of the functions to be simulated in the test file.

Create a function to test that uses the previous “calculator” module. Place the following files in the “lib/Algorithm/src” directory:

algorithm.h :

/**
 * @file algorithm.h
 * @author David LEVAL (dleval@dle-dev.com)
 * @brief calculation function 
 * @version 1.0
 * @date 2021-05-17
 * @copyright Copyright (c) 2021
 */

#ifndef ALGORITHM_H
#define ALGORITHM_H

#include <stdint.h>

uint32_t compute(uint32_t a, uint32_t b);

#endif

algorithm.c :

/**
 * @file algorithm.c
 * @author David LEVAL (dleval@dle-dev.com)
 * @version 1.0
 * @date 2021-05-17
 * @copyright Copyright (c) 2021
 */

#include <calculator.h>
#include "algorithm.h"

uint32_t compute(uint32_t a, uint32_t b)
{
    uint32_t result = addition(a,b) + division(a,b);

    return result;
}

Let’s create the test file “test_algorithm.c” in the test directory:

#ifdef TEST

#include "unity.h"

#include "mock_calculator.h"
#include "algorithm.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test_compute(void)
{
    addition_ExpectAndReturn(6,3,9);
    division_ExpectAndReturn(6,3,2);

    TEST_ASSERT_EQUAL_UINT32(11, compute(6,3));
}


#endif // TEST

With the addition of “mock_” in the call of the header of the “calculator” module, Ceedling will create a simulation environment for all the functions present in “calculator.h”. Now you need to write a function call scenario for each test function. In our case, the test function uses the simulation of the “addition” and “division” functions.

Debugging

PlatformIO integrates a very usefull debugging system. To activate the debugger, open the PlatformIO menu and click on “Start Debugging”.

PlatformIO debugger start

The initialization which can take several seconds. Then, it is possible to add breakpoints, variable monitoring …

PlatformIO debugger run

Conclusion

PlatformIO with STM32Cube and Ceedling are a very powerful set of tools. Their combined use facilitates the realization of developments driven by the test. In addition, the debugging functions built into PlatformIO are of great help. In conclusion, it is possible to reach an almost professional level of development with free and modern tools like PlatformIO with STM32Cube and Ceedling.

Leave a Reply

Your email address will not be published. Required fields are marked *