PlatformIO avec STM32cube et Ceedling

J’utilise fréquemment des cartes à base de STM32 dans mes réalisations personnelles. C’est aussi l’occasion d’experimenter dans l’utilisation de différents outils que j’apprécie. Cependant il m’arrive souvent de devoir laisser de coté certains projets pendant quelques semaines. C’est pourquoi, il m’est indispensable d’utiliser des outils de test et de documentation.
Pour toutes ces raisons, j’ai créé ce tutoriel pour pouvoir utiliser PlatformIO avec STM32cube et Ceedling.
Cet ensemble d’outils permet de réaliser un développement de logiciel embarqué pour STM32 piloté par le test avec l’éditeur VSCode.
En effet, utiliser un méthode de développement piloté par le test apporte beaucoup de bénéfices.

Voici une liste de quelques avantages :

  • Cela permet d’éviter l’oubli d’implémentation de fonctionnalités.
  • La productivité est amélioré grâce au découpage en petites fonctionnalités.
  • Le code généré est plus propre, mieux ordonné et comporte moins d’erreurs.
  • Les dépendances et les interfaces sont mieux gérées.
  • La portabilité et le refactoring sont facilités.
  • L’évolution du logiciel est sécurisée.
  • Les tests se comportent comme une documentation.

Il est vrai que PlatformIO intègre déjà un système de test unitaire avec Unity. Cependant, Ceedling ajoute un simulateur de fonction CMock qui permet de gagner beaucoup de temps lors du test de fonctions avec beaucoup de dépendances.

Installation des outils

VSCode

VSCode et PlatformIO ont déjà été le sujet d’un précédent article décrivant leurs utilisation comme remplaçant de l’IDE Arduino : VSCode et PlatformIO

Windows :

Télécharger et installer VSCode.

Linux :

sudo apt-get install vscode

PlatformIO

Premièrement, il faut installer PlatformIO dans VSCode. Dans le menu des extensions, rechercher « PlatformIO » et cliquer sur « Install ».

Installation de PlatformIO dans VSCode

Ensuite, il faut installer PlatformIO dans l’environnement Python du système pour pouvoir l’utiliser en ligne de commande :

Windows :

pip install -U platformio

Linux :

sudo pip3 install -U platformio

STM32CubeMX

STM32CubeMX est un outil graphique de configuration et de génération du code C pour les microcontrôleurs et microprocesseurs STM32.
Lien de téléchargement : STM32CubeMX

Ceedling

Ceedling est un environnement de test automatisé pour les projets écrit en C. Il est particulièrement adapté pour les logiciels embarqués et le développement piloté par le test. Il intègre les outils Unity, CMock et CException et fournit des fonctionnalités de simulation de code source et d’exécution de tests.
Vous pouvez trouver plus d’information sur le site Throw The Switch.

Pour commencer, il faut que Ruby soit installé sur l’ordinateur. Ensuite lancer la commande suivante :

gem install ceedling

Création du projet

Lorsque j’ai écrit ce tutoriel, j’ai utilisé une carte NUCLEO-L432KC. L’ensemble des fichiers sont disponibles sur le dépôt pio_nucleo_l432.
Ainsi, les commandes et les configurations sont adaptées a cette carte. Libre à vous d’adapter le contenu en fonction de votre cible.

Premièrement, créons le projet Ceedling. Placez vous dans le répertoire où vous souhaitez enregistrer le projet et lancer la commande :

ceedling new pio_nucleo_l432
Terminal : Création du projet Ceedling

Ensuite, il ne reste plus qu’a créer le projet PlatformIO dans le répertoire créé par Ceedling :

cd pio_stm32cube_ceedling
pio init -b nucleo_l432kc --ide vscode
Terminal : Création du projet PlatformIO

Configuration initial de PlatformIO et Ceedling

Comme on souhaite utiliser le framework STM32Cube, il faut modifier le fichier « platformio.ini » de la manière suivante :

; 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

Pour que Ceedling puisse fonctionner, il faut lui indiquer l’emplacement du code à tester.
Ouvrez le fichier « project.yml » et modifier la partie « :paths: » :

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

Maintenant notre projet est prêt à accueillir le code source.

Création du code STM32Cube

Il est possible d’utiliser stm32pio pour lié directement un projet STM32CubeMX avec PlatformIO.
Cependant, je n’apprécie pas qu’un générateur de code, aussi performant qu’il soit, puisse modifier directement le code de mon projet. Cela peut être dangereux et amener des soucis de fiabilité.
C’est pourquoi, je préfère générer un projet STM32CubeMX à part et venir piocher dans les fichiers sources ainsi créer.

Créer un projet STM32CubeMX puis configurer les périphériques.
Avant de générer le code, configurer la partie « Project Manager » de la manière suivante :

Configuration du projet STM32Cube
Configuration du générateur de code STM32Cube

Comme pour l’instant notre projet est vide, il n’y a pas de problème à copier directement les fichier située dans les répertoires « Src » et « Inc » du projet STM32CubeMX dans les répertoires « src » et « include » du projet PlatformIO.

Par la suite, si on modifie la configuration des périphériques dans STM32CubeMX, il ne faudra plus simplement copier les fichiers générés. Mais, il faudra analyser le code généré et copier des portions dans le code source du projet. Pour facilité cette tache, je vous conseille d’ajouter le moins possible de code propre à votre application dans les fichier générés par STM32CubeMX. Idéalement, le code de l’application est créé dans des fichiers séparés.

A ce stade, il est possible d’ouvrir le projet dans VSCode et de lancer une compilation sans erreur :

Compilation du projet PlatformIO

Finalement, le projet est prêt à utiliser PlatformIO avec STM32cube et Ceedling.

Utilisation de Ceedling

Test unitaire

Avant de pouvoir lancer le test de notre programme, il faut créer des fonctions à tester. Les exemples suivants intègrent des fonctions mathématiques simples.
Il est de bon usage de bien séparer les fonctions et d’écrire le code dans des fichiers différents. En effet, cela nous force à écrire de fonctions plus générique, à bien classer les fonctions et à bien séparé le coté hardware du coté applicatif. Finalement, on obtient un code portable, facilement utilisable avec d’autres microcontrôleurs et plus facile à tester.
D’ailleurs, PlatformIO prévoit cette manière de faire. Nous allons écrire les modules de code dans le répertoire de librairie « lib » est prévu à cet effet.

Placer dans le répertoire « lib/Calculator/src » les fichiers suivants :

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 exécute automatiquement les tests situés dans les fichier C du répertoire « test ». Néanmoins, il est important de respecter la convention de nommage des fichier de test, en ajoutant « test_ » devant le nom du fichier C à tester.

Créer le fichier de test associé « test_calculator.c » dans le répertoire « test » :

#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

Grace aux fonctions « TEST_ASSERT_EQUAL_UINT32 », il est possible de définir le résultat attendu par rapport au appels des fonctions à tester.

Pour lancer le test de notre programme, il suffit d’exécuter la commande suivante dans le terminal de VSCode :

ceedling test:all
Test unitaire PlatformIO avec STM32cube et Ceedling

On constate que l’ensemble des tests présents dans notre fichier de test « test_calculator.c » se sont déroulés sans problème. Ainsi, il semble que nous avons vérifié le comportement de l’ensemble des fonctions de notre module « calculator ». Cependant pour s’assurer que toute les possibilités ont été testées, il faut utiliser l’outil de calcul de couverture de test.

Couverture de test

Parfois, il est utile de connaitre la proportion testé du code d’un projet. Cela permet de savoir si l’ensemble des possibilités des fonctions ont été testées.
C’est pourquoi Ceedling intègre un outils permettant de connaitre la couverture de test (Code Coverage).

Pour ajouter une analyse de la couverture des tests, ouvrez le fichier de configuration de Ceedling « project.yml » et ajoutez le module gcov :

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

Pour lancer l’analyse, il suffit d’exécuter la commande suivante dans le terminal de VSCode :

ceedling gcov:all
Couverture de test PlatformIO avec STM32cube et Ceedling

Finalement, dans notre exemple, 100% du code du module « calculator » est testé.

Mock

Grace au module CMock intégré à Ceedling, il est possible de simuler des fonctions. Ainsi dans la fonction testée, les dépendances sont remplacés par des simulations dans lesquelles sont définit les paramètres et les retours. Cela est très utile par exemple pour les fonctions dépendantes du hardware ou des fonctions de communications.
Pour ce faire, il faut ajouter « mock_ » devant le nom du fichier d’entête des fonctions à simuler dans le fichier de test.

Créons une fonction à tester qui utilise le module « calculator » précédent . Placer dans le répertoire « lib/Algorithm/src » les fichiers suivants :

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;
}

Maintenant, créons le fichier de test « test_algorithm.c » dans le répertoire test :

#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

Grace à l’ajout de « mock_ » dans l’appel de l’entête du module « calculator », Ceedling va créer un environnement de simulation de toutes les fonctions présentes dans « calculator.h ».
A présent, il faut écrire un scénario d’appel de fonction pour chaque fonction de test. Dans notre cas, la fonction de test utilise la simulation des fonctions « addition » et « division ».

Débogage

PlatformIO intègre un système de débogage très simple à utiliser.
Pour activer le debugger, ouvrez le menu de PlatformIO et cliquer sur « Start Debugging ».

PlatformIO debugger start

Après l’initialisation qui peut prendre plusieurs secondes. Il est possible par exemple d’ajouter des points d’arrêts, des surveillance de variables …

PlatformIO debugger run

Conclusion

En fin de compte, PlatformIO avec STM32Cube et Ceedling forme un ensemble d’outils très performant. Leur utilisation combinée facilite la réalisation de développements pilotés par le test. De plus les fonctions de débogage intégrés à PlatformIO sont d’une grande aide.
En conclusion, il est possible d’atteindre un niveau de développement quasi professionnel avec des outils gratuits et moderne comme PlatformIO avec STM32Cube et Ceedling.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.