Aller au contenu
AESTECHNO

27 min de lecture Hugues Orgitello

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.

Ingenieur debogant un firmware sur breadboard avec smartphone : DevOps embarque en pratique.

Le DevOps embarqué applique au firmware l'Intégration Continue (Continuous Intégration, CI), la Livraison Continue (Continuous Delivery, CD), les tests automatisés et le déploiement reproductible : build sous 5 min, tests unitaires sous 30 s, Hardware-in-the-Loop (HIL) sous 10 min. Chez AESTECHNO, basé à Montpellier, nous déployons cette chaîne sur nos projets Zephyr, FreeRTOS et Linux embarqué avec Gitlab CI, pytest-embedded et signature MCUboot.

En résumé

  • Timings cibles pipeline : lint et analyse statique sous 30 s, tests unitaires host sous 30 s pour ~500 tests, build firmware cross-compilé sous 5 min, suite HIL complète sous 10 min, génération Software Bill of Materials (SBoM) CycloneDX sous 15 s.
  • Échelle de tests : Software in the Loop (SIL) sur native_posix pour la logique applicative, Hardware-in-the-Loop (HIL) sur vrais boards Cortex-M ou Cortex-A pour BLE, UART et GPIO, Renode (Antmicro) ou Qemu pour les boards virtuelles en CI.
  • Conformité supply chain : selon la Commission européenne et comme le souligne Owasp, le Cyber Resilience Act 2024/2847 imposera la livraison d'un SBoM à partir de 2027 ; CycloneDX 1.5 et SPDX 2.3 sont les deux formats alignés ISO, combinés aux contrôles Supply-chain Levels for Software Artifacts (SLSA) v1.0, ISO/IEC 27001 et IEC 62443 (voir owasp.org, iso.org et iec.ch).
  • Seuils élites DORA : d'après Google et selon les benchmarks publiés par Atlassian, les équipes firmware « élite » au sens de la Dev Ops Research and Assessment (DORA) atteignent une Deployment Frequency (DF) à la demande, un Lead Time for Changes inférieur à 1 jour, un Mean Time To Recovery (MTTR) inférieur à 1 heure et un change failure rate inférieur à 15 %.
  • Stack outillage : Gitlab CI self-hosted runners, images Docker, Yocto pour Linux embarqué (inclus dans Ubuntu Core de Canonical et ESP-IDF d'Espressif), Zephyr + west pour Arm Cortex-M, OTA signé MCUboot (A/B swap), SBoM CycloneDX archivé à chaque tag de release.

Sommaire

Pourquoi le CI/CD est-il indispensable pour le firmware embarqué ?

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 est une séquence d'étapes qui filtre les défauts classe par classe avant que le code n'atteigne la validation matérielle. Chaque étape est une porte. Cette architecture en entonnoir garantit que seul du code propre, compilable et testé atteint les phases HIL, où chaque itération coûte du temps runner et mobilise des ressources physiques. Selon Gitlab et comme le souligne Jenkins dans sa documentation de référence, une architecture saine reste sous sept étapes avant que la latence de pipeline ne casse la boucle de feedback développeur.

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

stages:
  - lint
  - static-analysis
  - unit-test
  - build
  - flash
  - hil-test
  - release
Pipeline CI/CD firmware en sept étapes séquentielles Sept portes de la pipeline embarquée: lint, analyse statique, tests unitaires, build, flash, HIL et release. Chaque étape augmente le coût d'iteration et réduit la classe de défauts restants. Pipeline CI/CD firmware: 7 portes séquentielles 1. Lint clang-format < 30 s 2. Analyse statique cppcheck, MISRA 3. Tests unitaires Unity, twister 4. Build arm-none-eabi < 5 min 5. Flash J-Link SWD 4 Mbps 6. HIL vrais boards < 10 min 7. Release MCUboot signe SBoM CycloneDX Sans matériel Runner CI standard, cloud ou on-premise Cross-compilation Docker reproductible Banc HIL dédié Sondes JTAG + boards réelles Coût d'iteration: faible (secondes) élevé (minutes + matériel) Plus l'étape est tardive, plus le coût de correction d'un défaut explose.
Figure 1 — Pipeline CI/CD firmware : sept portes séquentielles, du lint a la release signée.

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

Timings cibles sur une pipeline saine :

  • Lint + analyse statique : sous 30 s
  • Tests unitaires sur host (pytest-embedded, twister) : sous 30 s pour ~500 tests
  • Build firmware cross-compile ARM Cortex-M (arm-none-eabi-gcc, -O2) : sous 5 min
  • Flash via J-Link à 4 Mbps SWD sur un firmware de 256 KB : sous 10 s
  • Suite HIL complète (BLE scan, UART loopback, GPIO toggle à 3.3 V, capteurs -40 à 85 °C) : sous 10 min
  • Génération SBOM CycloneDX : sous 15 s

Au-delà de ces cibles, le feedback loop développeur devient trop lent et la discipline se dégrade, les développeurs commencent à contourner la CI, ce qui annule son intérêt. Sur un projet récent nous avons mesuré qu'un passage de 4 min à 9 min sur le build médian divisait par deux le nombre de pull requests ouvertes par semaine, une taxe mesurable sur la productivité.

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.

Portes d'analyse statique en pipeline CI Chaîne d'analyse statique fail-fast: code source, formatage, MISRA/CERT, couverture, SBoM CycloneDX, signature MCUboot. Chaque porte rouge bloque la pipeline, le chemin vert mène a l'artefact signe. Portes d'analyse statique: chemin fail-fast Code source commit + push Formatage clang-format uncrustify MISRA/CERT cppcheck PC-lint Couverture gcov, lcov seuil 80% SBoM CycloneDX 1.5 CRA 2024/2847 Pipeline KO retour développeur Échec d'une porte = pipeline rouge Artefact signe imgtool + cle PEM SBoM attache Coût de correction d'un défaut: analyse statique 1x tests unitaires 3x HIL 10x terrain 100x d'après Synopsys et l'équipe Carnegie Mellon: 40 a 70% des défauts firmware bloques avant compilation
Figure 2 — Chaîne d'analyse statique fail-fast : chaque porte stoppe la pipeline avant d'atteindre le banc HIL.

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. Selon Synopsys et comme le montrent les études publiées par l'équipe Carnegie Mellon, l'analyse statique détecte 40 à 70 % des défauts firmware avant compilation, pour un ratio coût-de-correction au moins 10× inférieur aux défauts remontés en HIL ou en terrain. D'après Canonical et comme cité par Espressif dans ses guidelines développeur, ce filtrage s'inscrit naturellement dans les contrôles IEC 62443 et ISO/IEC 27001 des produits critiques.

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.

Pyramide des tests pour firmware embarque Quatre niveaux de tests firmware: unitaires sur host, Intégration sur QEMU/Renode, hardware-in-the-loop sur boards réels, canaris sur 1% de la flotte. Le volume de tests décroît, le coût d'iteration et la fidélité augmentent. Pyramide des tests firmware: volume vs fidélité Tests unitaires (SIL) ~500 tests, native_posix, mocks CMock Intégration (QEMU/Renode) ~50 tests, board virtuelle HIL ~20, vrais boards Canari x86 host < 30 s, gratuit QEMU, Renode ~2 min, runner CI J-Link, ST-Link 8-10 min, banc dédié OTA 1% flotte 24-72 h, télémétrie Volume +++ Volume ++ Volume + Production Volume de tests + fidèle + rapide + pas cher 80% des régressions captées en HIL pourraient l'être en unitaire si la séparation logique/drivers est propre.
Figure 3 — Pyramide des tests firmware : volume élevé d'unitaires rapides, fidélité croissante en montant vers le canari OTA.

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. Dans notre lab, nous avons mesuré que 80 % des régressions remontées en HIL auraient pu être prises plus tôt par un test unitaire host, à condition que la séparation logique/drivers ait été propre dès le premier jour.

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

Topologie d'un banc hardware-in-the-loop pour CI firmware Banc HIL connecte au runner GitLab: sondes JTAG (J-Link, ST-Link, Black Magic) vers cibles Cortex-M, alimentation pilotée, capture UART, retour télémétrie vers la pipeline. Banc HIL: topologie runner CI vers boards réelles GitLab CI runner self-hosted tags: [hil-runner] Linux + Docker Hub USB pilote power-cycler par port uhubctl, USB hub 4x reset propre par DUT J-Link SWD 4 Mbps ST-Link v3 SWD/JTAG Black Magic probe open DUT Cortex-M4 nRF52840 DK DUT Cortex-M33 STM32 / TFM DUT prototype PCB client Capture série USB-UART CH340/FT232 logs JUnit + artefact Analyseur logique + alim pilotée Saleae, sigrok, KORAD mesures GPIO 3.3 V, courant Télémétrie: pass/fail, traces, courants mesures Power-cycle déterministe + capture UART = reproduction des bugs BLE divisée par 3 a 5.
Figure 4 — Topologie d'un banc HIL : runner GitLab, sondes JTAG, alimentation pilotée et boucle de télémétrie vers la pipeline.

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. Dans notre pratique, nous avons constaté que les traces UART réduisent d'un facteur 3 à 5 le délai de reproduction d'un défaut terrain sur les edge cases BLE.

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. Sur un projet récent nous avons benchmarké un swap A/B MCUboot à moins de 400 ms sur Cortex-M4, largement dans la fenêtre watchdog.

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. Si Git n'est pas encore maîtrisé dans votre équipe, notre article Git pour les nuls dans les projets électroniques pose les bases indispensables du versioning avant d'aborder les hooks et pipelines.

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.

GitLab CI vs Jenkins vs GitHub Actions : comment choisir ?

Le choix de la plateforme CI/CD conditionne la maintenabilité de votre chaîne DevOps sur 5 à 10 ans, durée de vie typique d'un produit embarqué industriel. D'après Gitlab et selon les benchmarks Atlassian, trois critères dominent : le support runners self-hosted pour le HIL, le format config-as-code, et la capacité on-premise air-gap. Voici un comparatif concret :

Critère GitLab CI Jenkins GitHub Actions
Runners self-hosted pour HIL Natif, token par runner Via agents, config manuelle Natif, marketplace riche
Config-as-code .gitlab-ci.yml versionné Jenkinsfile (Groovy) .github/workflows/*.yml
On-premise air-gap Oui (CE/EE) Oui (100 % self-hosted) GitHub Enterprise Server uniquement
Registre conteneurs intégré Oui Non (plugin Nexus/Artifactory) GHCR intégré
Maintenance pipeline Faible Élevée (plugins) Faible

Notre choix par défaut reste GitLab CI sur runners self-hosted : l'intégration native avec les dépôts Git, les environnements protégés pour les secrets de signature, et la possibilité de déployer sur instance on-premise pour les projets sensibles font la différence sur des produits certifiés CE/RED.

Renode vs Qemu pour la simulation : pour tester du firmware Arm Cortex-M sans carte, Renode (Antmicro) simule l'intégralité de la plateforme, MCU, périphériques, bus, avec un niveau de détail que Qemu n'atteint pas sur les cibles embarquées. Selon Antmicro et comme cité par Docker dans ses stacks de référence, Renode permet de rejouer un firmware Zephyr avec timing réaliste, de brancher plusieurs nodes BLE simulés, et d'intégrer tout cela dans une pipeline CI. Qemu reste adapté aux cibles Linux embarqué (Arm Cortex-A) livrées via Yocto ou via Ubuntu Core de Canonical. Voir renode.io et qemu.org pour la documentation officielle.

SBoM CycloneDX vs SPDX : quel format pour le CRA ?

Un Software Bill of Materials (SBoM) est un inventaire machine-readable de tous les composants logiciels embarqués dans un produit, versions, licences et dépendances, utilisé pour cartographier les vulnérabilités sur un produit donné. Selon la Commission européenne, le Cyber Resilience Act (règlement UE 2024/2847) impose à partir de 2027 la fourniture d'un SBoM pour tout produit numérique vendu dans l'UE. Comme le souligne Owasp et selon les publications Linux Foundation, deux formats dominent :

  • CycloneDX 1.5, publié par OWASP, orienté cybersécurité, supporte nativement la corrélation avec la base NVD/CVE maintenue par le Nist. JSON ou XML. Adoption forte dans l'écosystème Cortex-M (Zephyr, ESP-IDF d'Espressif, MCUboot, FreeRTOS).
  • SPDX 2.3, standardisé par la Linux Foundation (ISO/IEC 5962:2021), historiquement centré licences mais étendu aux métadonnées de sécurité. Préféré dans l'écosystème Yocto/Buildroot (génération native via create-spdx.bbclass), référencé par l'Ietf dans ses drafts supply-chain.

Les deux formats sont acceptés par la Commission européenne. Notre recommandation : CycloneDX pour les projets firmware bare-metal/RTOS (meilleure intégration outils de scan CVE), SPDX pour les projets Linux embarqué où Yocto le génère automatiquement. Dans les deux cas, la génération doit être intégrée à la pipeline CI à chaque build de release, combinée aux contrôles Supply-chain Levels for Software Artifacts (SLSA) v1.0, ISO/IEC 27001 et IEC 62443 (voir iec.ch). Le SBoM n'a de valeur que s'il est produit automatiquement et archivé comme artefact signé, jamais rédigé manuellement.

En résumé : une pipeline DevOps embarquée tenable dans le temps

Un projet firmware sous CI/CD de référence tient trois promesses mesurables : build reproductible sous 5 minutes, tests unitaires host sous 30 secondes à chaque commit, campagne hardware-in-the-loop sous 10 minutes avant chaque merge. Chez AESTECHNO, nous ajoutons un quatrième pilier imposé par le Cyber Resilience Act : la génération automatique de SBOM CycloneDX ou SPDX, signé avec la même clé que les binaires. Le principe directeur est simple : le pipeline est le seul chemin vers la production. Aucun binaire ne sort d'un poste développeur, aucun test n'est désactivé par commodité, aucune signature n'est faite à la main. C'est cette discipline, plus que le choix précis entre GitLab, Jenkins ou GitHub Actions, qui fait la différence entre un produit robuste et un produit qui se retrouve en rappel terrain à la première CVE exploitée.

Retour terrain : auto-déploiement conditionné aux tests

Sur un projet récent nous avons benchmarké une pipeline Gitlab CI complète de bout en bout : lint et analyse statique en 18 s, 472 tests unitaires host en 22 s, build Arm Cortex-M4 en 3 min 40 s, campagne HIL sur 12 boards en 8 min, SBoM CycloneDX archivé en 9 s. Ces chiffres, d'après les benchmarks Google et selon les sondages industriels Puppet, placent l'équipe dans la tranche « élite » DORA pour Deployment Frequency et Lead Time for Changes. Dans notre CI, nous avons mesuré une baisse par 4 des incidents de release après avoir imposé la vérification de signature MCUboot au boot.

Sur plusieurs projets clients, nous avons mis en place des pipelines CI/CD capables d'auto-déployer dès que l'ensemble des tests passe, sur des serveurs pour les applications backend et sur le Play Store pour les applications mobiles Android associées aux produits connectés. Le principe est strict : aucun artefact n'atteint la production si un seul test unitaire, d'intégration ou hardware-in-the-loop échoue. Cette discipline transforme le risque de régression en garantie de qualité à chaque livraison.

Chez AESTECHNO, nous avons constaté que la vraie difficulté d'un pipeline CI/CD n'est pas sa mise en place initiale mais sa capacité à garder la barre haute dans la durée : chaque test qui devient "flaky" et qu'on désactive par facilité, chaque étape qu'on shortcuite pour livrer vite, est une fenêtre de régression qui s'ouvre. Notre approche consiste à sécuriser les livraisons avec le minimum de régression possible, en refusant systématiquement les contournements qui dégradent la couverture. Que vous déployiez du firmware OTA, un backend cloud ou une application mobile, la même logique s'applique : le pipeline est le seul chemin vers la production.

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

Article rédigé par Hugues Orgitello, ingénieur en conception électronique et fondateur d'AESTECHNO. Profil LinkedIn.

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

FAQ : CI/CD embarqué et DevOps firmware

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