mobile app

DevOps pour l’embarqué : CI/CD, tests automatisés et déploiement firmware

DevOps embarqué : pipeline CI/CD firmware, tests automatisés, analyse statique, hardware-in-the-loop et déploiement OTA. Guide pratique AESTECHNO Montpellier.

« Ça marche sur ma machine. » Cette phrase, familière dans le développement web, est encore plus dangereuse en embarqué — parce que « ma machine » peut signifier un poste de développement avec un J-Link branché, un firmware compilé avec des flags de debug, et une version du SDK que personne d’autre dans l’équipe n’utilise. Ce n’est pas un processus de release. C’est un accident qui attend de se produire.

Les équipes de développement web ont adopté le DevOps il y a plus de dix ans. Intégration continue, déploiement automatisé, tests à chaque commit — ces pratiques sont devenues des évidences. En embarqué, nous en sommes encore trop souvent au build manuel, au test sur le bureau de l’ingénieur, et à la release par clé USB. L’écart est frappant, et il n’est plus justifiable. Les outils existent, les méthodologies sont éprouvées, et les bénéfices sont considérables pour la qualité, la vélocité et la fiabilité des produits livrés.

Chez AESTECHNO, nous avons constaté que l’adoption d’une pipeline CI/CD structurée transforme la dynamique d’un projet firmware. Avec plus de 10 ans d’expérience en développement de logiciels embarqués industriels, nous détaillons dans ce guide les pratiques, les outils et l’architecture à mettre en place pour amener vos équipes firmware au niveau DevOps qu’exige l’industrie moderne.

Besoin de structurer votre pipeline CI/CD firmware ?

Nous mettons en place l’intégration continue adaptée à votre projet embarqué :

  • Architecture de pipeline GitLab CI / GitHub Actions pour firmware
  • Analyse statique, tests unitaires automatisés, builds reproductibles
  • Hardware-in-the-loop et déploiement OTA sécurisé

Échangeons sur votre projet — 30 min gratuites

Pourquoi le CI/CD est indispensable pour le firmware

L’intégration continue et le déploiement continu (CI/CD) appliqués au firmware désignent l’ensemble des pratiques d’automatisation qui garantissent que chaque modification de code est compilée, testée et validée automatiquement avant d’être intégrée dans la branche principale du projet. En embarqué, cette discipline est d’autant plus critique que les bugs livrés en production sont souvent impossibles à corriger sans intervention physique sur le produit.

Sans CI/CD, un projet firmware typique souffre de problèmes récurrents :

  • Régressions silencieuses : un développeur modifie le driver SPI et casse le protocole BLE sans s’en apercevoir — parce que personne ne teste l’ensemble à chaque commit
  • Builds non reproductibles : le firmware compilé sur le poste de l’ingénieur A ne donne pas le même binaire que celui compilé par l’ingénieur B, à cause de versions différentes du toolchain
  • Intégration tardive : deux développeurs travaillent en parallèle pendant des semaines, puis découvrent des conflits majeurs au moment du merge
  • Release manuelle : le processus de release implique une checklist papier, des copier-coller de commandes, et une prière silencieuse

Le CI/CD résout ces problèmes en automatisant ce qui doit l’être : la compilation est déclenchée à chaque push, les tests s’exécutent sans intervention humaine, et le binaire de release est produit par la pipeline — pas par le poste d’un développeur. Pour les projets embarqués où la stratégie de test et validation est déjà critique, l’automatisation CI/CD est le prolongement naturel.

Architecture d’une pipeline CI/CD embarquée

Une pipeline CI/CD pour firmware se compose de plusieurs étapes séquentielles, chacune filtrant un type de défaut spécifique avant que le code ne progresse vers l’étape suivante. Cette architecture en entonnoir garantit que seul du code propre, compilable et testé atteint les phases de validation matérielle, où chaque itération coûte du temps et mobilise des ressources physiques.

Voici l’architecture que nous recommandons, implémentée en GitLab CI :

stages:
  - lint
  - static-analysis
  - unit-test
  - build
  - flash
  - hil-test
  - release

Chaque stage a un rôle précis :

  1. Lint : vérification du formatage (clang-format, uncrustify), des conventions de nommage, de la cohérence des headers. Rapide — moins de 30 secondes.
  2. Static analysis : analyse du code source à la recherche de bugs potentiels, de fuites mémoire, de dépassements de buffer. Quelques minutes.
  3. Unit test : exécution des tests unitaires sur le host (x86), sans matériel. Validation de la logique métier, des parsers, des machines d’état.
  4. Build : compilation croisée pour la cible (ARM Cortex-M, RISC-V, etc.). Génération des binaires debug et release.
  5. Flash : programmation du firmware sur la carte cible connectée au runner CI via J-Link ou ST-Link.
  6. HIL test : tests hardware-in-the-loop sur la cible réelle — UART, BLE, GPIO, capteurs.
  7. Release : génération de l’artefact signé, publication dans le registre, notification de l’équipe.

Les trois premières étapes ne nécessitent aucun matériel et s’exécutent sur n’importe quel runner CI. C’est le filet de sécurité minimal que tout projet firmware devrait avoir, même sans investir dans un banc de test automatisé. Les étapes flash et HIL nécessitent un runner dédié avec du matériel connecté — ce que nous détaillons plus loin.

Analyse statique : détecter les bugs avant qu’ils n’atteignent le matériel

L’analyse statique consiste à examiner le code source sans l’exécuter, en utilisant des outils spécialisés qui détectent des catégories entières de bugs : déréférencement de pointeurs nuls, dépassements de buffer, variables non initialisées, fuites de ressources, et violations des règles de codage MISRA ou CERT C. C’est la première ligne de défense automatisée contre les défauts logiciels les plus courants en firmware.

Les outils que nous intégrons dans nos pipelines :

cppcheck — l’outil open source incontournable

Cppcheck est un analyseur statique C/C++ qui excelle dans la détection des erreurs que le compilateur ne signale pas : fuites mémoire, utilisation après free, dépassements de tableaux, et conditions logiques suspectes. Il s’intègre nativement dans GitLab CI et produit des rapports au format SARIF ou XML :

static-analysis:
  stage: static-analysis
  script:
    - cppcheck --enable=all --error-exitcode=1
      --suppress=missingIncludeSystem
      --xml --xml-version=2 src/ 2> cppcheck-report.xml
  artifacts:
    reports:
      codequality: cppcheck-report.xml

PC-lint Plus — la rigueur MISRA

PC-lint Plus est un outil commercial qui va plus loin que cppcheck en appliquant les règles MISRA C:2012 et CERT C de manière exhaustive. Pour les projets soumis à des exigences normatives (automobile, médical, ferroviaire), c’est souvent un passage obligé. Son intégration CI nécessite une licence, mais le retour sur investissement est rapide : les bugs détectés en analyse statique coûtent des ordres de grandeur moins cher que ceux découverts en phase de validation ou en production.

Coverity — l’analyse en profondeur

Coverity (Synopsys) est un outil d’analyse statique de grade industriel qui détecte des défauts complexes grâce à une analyse inter-procédurale du flux de données. Il identifie des bugs que les outils plus légers ne voient pas : race conditions, deadlocks potentiels, et chemins d’exécution menant à des états indéfinis. Son coût est significatif, mais il est justifié pour les produits critiques où un bug terrain peut avoir des conséquences graves.

Notre recommandation : commencer par cppcheck (gratuit, immédiat), puis ajouter PC-lint ou Coverity selon les exigences normatives du projet.

Tests unitaires sur host : valider sans matériel

Les tests unitaires sur host consistent à compiler et exécuter la logique applicative du firmware sur la machine de développement (x86/x64) plutôt que sur la cible embarquée. Cette approche permet de tester rapidement et massivement sans mobiliser de matériel, ce qui est exactement ce dont une pipeline CI a besoin pour fournir un feedback en quelques minutes plutôt qu’en quelques heures.

La clé est de structurer le code firmware pour séparer la logique métier (portable) des drivers matériels (spécifiques à la cible). Cette séparation — un principe fondamental d’architecture logicielle — rend le code testable et, par extension, plus maintenable.

Zephyr et twister : le framework de test intégré

Le RTOS Zephyr intègre twister, un outil de test qui compile et exécute les tests unitaires sur la plateforme native_posix — un émulateur POSIX qui simule le kernel Zephyr sur le host. C’est un atout considérable : les tests s’exécutent en quelques secondes, sans matériel, et couvrent la logique applicative, les machines d’état, et même certains subsystèmes Zephyr (logging, settings, shell) :

unit-test:
  stage: unit-test
  image: ghcr.io/zephyrproject-rtos/ci:latest
  script:
    - west twister -p native_posix -T tests/
  artifacts:
    reports:
      junit: twister-out/twister.xml

Unity et CMock : le duo pour le bare-metal

Pour les projets bare-metal (sans RTOS) ou basés sur FreeRTOS, le framework Unity associé à CMock fournit un environnement de test unitaire léger et efficace. Unity gère les assertions et le reporting, CMock génère automatiquement des mocks à partir des headers pour isoler les modules sous test de leurs dépendances matérielles :

void test_temperature_conversion_celsius_to_raw(void) {
    /* 25.0°C doit donner la valeur raw attendue */
    uint16_t raw = temp_celsius_to_raw(25.0f);
    TEST_ASSERT_EQUAL_UINT16(0x1900, raw);
}

void test_protocol_parser_valid_frame(void) {
    uint8_t frame[] = {0xAA, 0x03, 0x01, 0x02, 0x03, 0xBB};
    parsed_msg_t msg;
    TEST_ASSERT_EQUAL(PARSE_OK, protocol_parse(frame, sizeof(frame), &msg));
    TEST_ASSERT_EQUAL(3, msg.length);
}

L’objectif est de couvrir en priorité les modules critiques : parsers de protocoles, machines d’état, algorithmes de conversion, et logique de décision. Ce sont ces modules qui causent les bugs les plus subtils en production, et ce sont aussi les plus faciles à tester sans matériel.

Hardware-in-the-loop : tester sur le vrai matériel depuis la CI

Le hardware-in-the-loop (HIL) consiste à connecter une carte cible réelle au runner CI pour flasher le firmware compilé et exécuter des tests automatisés sur le matériel. C’est la validation ultime avant la release : le firmware tourne sur le vrai processeur, avec les vrais périphériques, dans les vraies conditions de timing et de mémoire.

Mettre en place un banc HIL demande un investissement en matériel et en infrastructure, mais le retour est immédiat : les régressions matérielles sont détectées automatiquement, sans qu’un ingénieur doive brancher manuellement une sonde et lancer des tests depuis son poste.

Architecture du banc HIL

Un banc HIL minimal pour un projet firmware typique comprend :

  • Runner CI dédié : un PC ou un Raspberry Pi connecté au réseau, enregistré comme runner GitLab
  • Sonde de programmation : J-Link, ST-Link ou DAPLink connecté en USB au runner
  • Carte cible : la board de développement ou le prototype du produit
  • Interface série : adaptateur USB-UART pour capturer les logs de la cible
  • Outils de test : scripts Python pour piloter les tests et vérifier les résultats

Flash automatisé depuis la pipeline

Le flash de la cible depuis le runner CI utilise les outils en ligne de commande fournis par les fabricants de sondes. Avec J-Link :

flash:
  stage: flash
  tags: [hil-runner]
  script:
    - JLinkExe -device NRF52840_XXAA -if SWD -speed 4000
      -CommandFile flash.jlink
  needs: [build]

Le fichier flash.jlink contient les commandes de programmation :

connect
erase
loadfile build/zephyr/zephyr.hex
reset
exit

Tests automatisés BLE et UART

Une fois le firmware flashé, les tests HIL vérifient le comportement du produit via ses interfaces réelles. Un script Python peut scanner le BLE, se connecter au device, lire une caractéristique, et vérifier la réponse :

hil-test:
  stage: hil-test
  tags: [hil-runner]
  script:
    - python3 tests/hil/test_ble_advertising.py
    - python3 tests/hil/test_uart_protocol.py
    - python3 tests/hil/test_gpio_outputs.py
  artifacts:
    reports:
      junit: tests/hil/results.xml

La capture de logs UART permet de diagnostiquer les échecs sans avoir à reproduire le problème manuellement. Le runner enregistre tout le flux série pendant le test et l’attache comme artefact de la pipeline — ce qui facilite considérablement le debug à distance, un avantage crucial pour les équipes distribuées.

Déploiement OTA : de l’artefact CI à la flotte en production

Le déploiement Over-The-Air (OTA) ferme la boucle DevOps en permettant de livrer le firmware validé par la pipeline directement sur les produits déployés sur le terrain. C’est le dernier maillon de la chaîne CI/CD embarquée — et le plus critique du point de vue de la sécurité IoT, car une mise à jour défaillante peut bricker un produit à distance.

L’architecture OTA repose sur deux composants fondamentaux :

MCUboot : le bootloader sécurisé

MCUboot est un bootloader open source conçu pour la mise à jour firmware sécurisée sur microcontrôleur. Il gère le swap d’images (slot A/B), la vérification de signature cryptographique, et le rollback automatique en cas d’échec de boot. Intégré nativement dans Zephyr, il s’interface avec la pipeline CI pour signer les artefacts de release :

release:
  stage: release
  script:
    - west build -b nrf52840dk_nrf52840 -- -DCONFIG_BOOTLOADER_MCUBOOT=y
    - imgtool sign --key signing-key.pem --version $CI_COMMIT_TAG
      build/zephyr/zephyr.hex signed-firmware.hex
  artifacts:
    paths:
      - signed-firmware.hex

SWUpdate : la mise à jour pour Linux embarqué

Pour les systèmes basés sur Linux embarqué (Yocto, Buildroot), SWUpdate fournit un framework de mise à jour atomique avec support du schéma A/B, des images delta, et de la vérification d’intégrité. La pipeline CI génère le package SWU signé, le publie dans un dépôt, et les devices en production le récupèrent via HTTPS.

Le principe fondamental est le même, quelle que soit la plateforme : la pipeline produit un artefact signé, l’artefact est distribué aux devices, le device vérifie la signature avant d’appliquer la mise à jour, et un mécanisme de rollback protège contre les mises à jour défaillantes. Sans cette chaîne de confiance, le déploiement OTA est un vecteur d’attaque plutôt qu’un outil de maintenance.

Pre-commit hooks : le premier filet de sécurité

Les pre-commit hooks sont des scripts qui s’exécutent automatiquement sur le poste du développeur avant chaque commit git. Ils constituent la première ligne de défense — avant même que le code n’atteigne la pipeline CI — en bloquant les erreurs triviales qui feraient échouer les étapes ultérieures et gaspilleraient du temps de CI.

Un bon ensemble de pre-commit hooks pour un projet firmware comprend :

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
      - id: detect-private-key

  - repo: https://github.com/pocc/pre-commit-hooks
    rev: v1.3.5
    hooks:
      - id: cppcheck
        args: [--enable=warning,style,performance]
      - id: clang-format
        args: [--style=file]

  - repo: local
    hooks:
      - id: brace-check
        name: Check matching braces
        entry: python3 scripts/check_braces.py
        language: python
        files: \.(c|h)$

Chaque hook a un rôle précis :

  • trailing-whitespace / end-of-file-fixer : évitent les diffs parasites qui polluent les merge requests
  • check-yaml : valide la syntaxe des fichiers de configuration (devicetree overlays, CI config)
  • detect-private-key : empêche le commit accidentel de clés cryptographiques — un risque réel dans les projets IoT qui manipulent des clés de signature
  • cppcheck : détecte les erreurs évidentes avant que le code n’atteigne la CI
  • clang-format : applique le style de code du projet automatiquement
  • brace-check : vérifie l’appariement des accolades — un classique des erreurs de merge en C

L’investissement est minimal (un fichier .pre-commit-config.yaml à la racine du repo), mais l’impact est significatif : les pipelines CI échouent moins souvent pour des raisons triviales, et la qualité de base du code est garantie dès le commit.

Outillage : construire une chaîne DevOps embarquée

Le choix des outils conditionne la facilité de mise en place et la maintenabilité à long terme de la pipeline CI/CD. L’écosystème DevOps embarqué a considérablement mûri ces dernières années, et les options ne manquent pas. Voici les choix que nous recommandons en fonction du contexte projet.

Plateformes CI/CD

  • GitLab CI : notre choix par défaut. Les runners self-hosted permettent de connecter du matériel directement au serveur CI, le fichier .gitlab-ci.yml versionné avec le code garantit la reproductibilité, et les environnements protégés gèrent les secrets (clés de signature, tokens OTA) de manière sécurisée.
  • GitHub Actions : excellente alternative, surtout pour les projets open source. Les self-hosted runners supportent le HIL, et le marketplace propose des actions prêtes à l’emploi pour les toolchains embarqués (Zephyr, ESP-IDF, STM32CubeIDE).
  • Jenkins : encore très présent dans l’industrie, surtout dans les organisations qui ont un historique d’automatisation. Plus complexe à maintenir, mais offre une flexibilité maximale grâce à son écosystème de plugins.

Docker pour des builds reproductibles

Le problème numéro un des builds firmware est la reproductibilité : « ça compile chez moi mais pas sur le CI » est le symptôme d’un environnement non maîtrisé. Docker résout ce problème en encapsulant l’ensemble du toolchain dans une image versionnée :

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
    cmake ninja-build gcc-arm-none-eabi \
    python3-pip git wget
RUN pip3 install west
RUN west init /opt/zephyr && cd /opt/zephyr && west update

Chaque développeur et le runner CI utilisent la même image. Le résultat : un build identique, quel que soit l’environnement. C’est un prérequis pour un CI fiable.

Outils de build

  • west : l’outil de meta-build de Zephyr. Gère les manifests de modules, la compilation, le flash, et l’exécution de twister. Indispensable pour tout projet Zephyr.
  • CMake : le standard de facto pour les projets C/C++ embarqués. Zephyr, ESP-IDF et de nombreux SDKs l’utilisent comme système de build sous-jacent.
  • Ninja : générateur de build rapide, souvent utilisé en backend de CMake pour des compilations parallèles optimisées.

L’écosystème est riche, mais la clé est la cohérence : choisir une stack et s’y tenir. Un projet qui mélange Make, CMake et des scripts shell custom pour le build accumule une dette technique qui finit par paralyser la pipeline.

Notre approche chez AESTECHNO

Nos projets firmware intègrent une pipeline CI/CD complète : analyse statique, tests unitaires automatisés, builds debug et release, et pre-commit hooks. Cette approche systématique est un différenciateur fort dans le monde de l’embarqué, où trop de bureaux d’études livrent encore du firmware sans aucune automatisation de test.

Concrètement, cela signifie que :

  • Chaque commit est vérifié : analyse statique cppcheck, tests unitaires, compilation croisée — tout est automatisé
  • Les builds sont reproductibles : Docker et west garantissent qu’un build d’aujourd’hui pourra être reproduit dans deux ans
  • Les releases sont traçables : chaque binaire livré est lié à un commit, une pipeline, et un jeu de tests qui ont tous passé
  • Le code est propre dès le commit : nos pre-commit hooks vérifient le formatage, les accolades, les secrets accidentels et la syntaxe YAML

Ce niveau de rigueur n’est pas un luxe réservé aux grandes organisations. C’est une pratique accessible à tout projet firmware, et nous accompagnons nos clients dans sa mise en place dès le démarrage du projet — pas en fin de développement quand la dette technique est déjà installée.

Pourquoi faire confiance à AESTECHNO ?

  • 10+ ans d’expertise en conception électronique et logiciel embarqué
  • Pipelines CI/CD firmware intégrées dans nos projets Zephyr, FreeRTOS et Linux
  • Analyse statique et tests automatisés sur chaque commit, pas en fin de projet
  • Builds Docker reproductibles et déploiement OTA sécurisé avec MCUboot
  • Bureau d’études français basé à Montpellier

Modernisez votre workflow firmware

Un processus de build manuel n’est pas une stratégie — c’est un risque. Nous mettons en place l’infrastructure CI/CD adaptée à votre projet embarqué, de l’analyse statique au déploiement OTA.

Contactez-nous pour un audit de votre workflow

Questions fréquentes

Peut-on faire du CI/CD sans matériel connecté au runner ?

Oui, et c’est même le point de départ recommandé. Les trois premières étapes d’une pipeline — lint, analyse statique et tests unitaires sur host — ne nécessitent aucun matériel. Elles s’exécutent sur n’importe quel runner CI standard (cloud ou on-premise) et couvrent déjà la majorité des régressions logicielles. Le hardware-in-the-loop est un ajout précieux, mais il n’est pas un prérequis pour commencer à bénéficier du CI/CD embarqué.

Combien de temps faut-il pour mettre en place une pipeline CI/CD firmware ?

Une pipeline de base (lint + analyse statique + build) peut être opérationnelle en quelques jours si le projet utilise déjà CMake ou west. L’ajout de tests unitaires demande un effort de refactoring pour séparer la logique métier des drivers, ce qui peut prendre une à deux semaines selon la taille du codebase. Le hardware-in-the-loop nécessite un investissement supplémentaire en matériel et en scripts de test, mais peut être ajouté de manière incrémentale. L’essentiel est de commencer simple et d’itérer.

Quels sont les risques du déploiement OTA firmware ?

Le risque principal est le bricking du device — un firmware défaillant qui empêche le produit de fonctionner et de recevoir une nouvelle mise à jour corrective. La parade est un mécanisme de rollback automatique : MCUboot ou SWUpdate vérifient que le nouveau firmware boote correctement et reviennent à la version précédente en cas d’échec. Le second risque est la sécurité : une mise à jour non signée peut être un vecteur d’attaque. La signature cryptographique des artefacts et la vérification côté device sont indispensables.

GitLab CI ou GitHub Actions pour un projet embarqué ?

Les deux plateformes conviennent parfaitement. GitLab CI a l’avantage d’une gestion native des runners self-hosted (essentiel pour le HIL), d’un registre de conteneurs intégré, et d’une instance on-premise possible pour les projets sensibles. GitHub Actions offre un marketplace plus riche en actions prêtes à l’emploi et une communauté open source très active. Le choix dépend surtout de l’écosystème existant dans l’organisation. Jenkins reste pertinent dans les structures industrielles avec un historique fort d’automatisation.

Comment tester du code firmware qui dépend du matériel ?

La stratégie repose sur deux niveaux. Premièrement, séparer la logique métier des drivers matériels et tester cette logique sur le host avec des frameworks comme Unity/CMock ou twister (Zephyr). Les dépendances matérielles sont remplacées par des mocks qui simulent le comportement attendu. Deuxièmement, pour les interactions matérielles qui ne peuvent pas être mockées de manière fiable (timing BLE, comportement analogique, interruptions), le hardware-in-the-loop avec une carte cible connectée au runner CI est la solution. Les deux approches sont complémentaires.

L’analyse statique remplace-t-elle les tests unitaires ?

Non, les deux sont complémentaires et détectent des catégories de bugs différentes. L’analyse statique identifie les défauts structurels (pointeurs nuls, fuites mémoire, violations MISRA) sans exécuter le code. Les tests unitaires vérifient le comportement fonctionnel : est-ce que le parser décode correctement une trame ? Est-ce que la machine d’état gère les transitions edge-case ? Un projet firmware robuste a besoin des deux. L’analyse statique est plus facile à mettre en place (pas de code de test à écrire), ce qui en fait un bon point de départ.

Articles connexes