Test Types & Methods

Tests HIL avec Python

Découvrez comment configurer les tests HIL pour systèmes embarqués avec Python, OpenHTF et TofuPilot, incluant les tests GPIO, ADC, PWM et interfaces de.

JJulien Buteau
intermediate13 min de lecture14 mars 2026

Le test hardware-in-the-loop (HIL) valide le firmware embarqué en stimulant les entrées matérielles réelles et en mesurant les sorties réelles. Contrairement à la simulation pure, les tests HIL s'exécutent sur le matériel cible réel avec de vrais périphériques. Cela détecte des bugs que la simulation manque : problèmes de timing, bruit ADC, conflits d'interruptions et problèmes d'initialisation des périphériques. Ce guide vous montre comment construire un système de test HIL basé sur Python avec OpenHTF et TofuPilot.

HIL vs SIL vs MIL

MéthodeMatérielLogicielFidélitéVitesseCoût
MIL (Model-in-the-Loop)AucunPlante + contrôleur simulésFaibleRapide (secondes)Faible
SIL (Software-in-the-Loop)AucunCode contrôleur réel sur hôteMoyenneRapide (secondes)Faible
HIL (Hardware-in-the-Loop)Carte cible réelleFirmware réelÉlevéeTemps réelMoyen
Test physiqueCible réelle + plante réelleFirmware réelLa plus élevéeTemps réelÉlevé

Le HIL se situe entre le SIL et le test physique. Vous testez du firmware réel sur du matériel réel, mais simulez l'environnement (capteurs, actionneurs, systèmes externes).

Quand utiliser le test HIL

  • Tests de régression firmware. Chaque build de firmware subit les mêmes tests physiques. Détecte les régressions que les tests unitaires manquent.
  • Validation des périphériques. GPIO, ADC, DAC, PWM, UART, SPI, I2C. Le silicium réel se comporte différemment des modèles de simulation.
  • Systèmes critiques pour la sécurité. L'automobile, le médical et l'aérospatial exigent des tests HIL pour la certification (ISO 26262, IEC 62304).
  • Validation pré-production. Validez le firmware sur du matériel réel avant de passer en production.

Architecture de test HIL

Une configuration HIL typique en Python :

ComposantRôleExemple
Carte cible (DUT)Exécute le firmware sous testCarte de dev STM32, ESP32, Raspberry Pi Pico
Hôte de testExécute les scripts de test PythonPC ou Raspberry Pi
DAQ / Adaptateur GPIOStimuler les entrées, lire les sortiesArduino, LabJack, NI DAQ
AlimentationAlimentation contrôlée du DUTAlimentation de banc (programmable)
Série/débogageFlasher le firmware, envoyer des commandesUSB-to-serial, J-Link, ST-Link
InstrumentsMesures de précisionMultimètre, oscilloscope

Étape 1 : Créer les plugs d'interface matérielle

Chaque équipement de test obtient un Plug OpenHTF. Cela garde les phases de test propres et indépendantes de l'instrument.

hil/plugs.py
import openhtf as htf
from openhtf.plugs import BasePlug


class GpioPlug(BasePlug):
    """Interface GPIO pour stimuler les entrées du DUT et lire les sorties.

    Remplacez par un vrai adaptateur GPIO (ex. LabJack, Arduino, RPi.GPIO).
    """

    def setUp(self):
        self._pin_states = {}

    def set_pin(self, pin: int, state: bool):
        """Définir une broche GPIO sur le montage de test (entrée DUT)."""
        self._pin_states[pin] = state

    def read_pin(self, pin: int) -> bool:
        """Lire une broche GPIO du DUT (sortie DUT)."""
        return self._pin_states.get(pin, False)

    def tearDown(self):
        self._pin_states.clear()


class AdcPlug(BasePlug):
    """Interface ADC pour lire les sorties analogiques du DUT.

    Remplacez par un vrai DAQ (ex. NI DAQmx, LabJack, ADS1115).
    """

    def setUp(self):
        self._channels = {0: 1.65, 1: 2.50, 2: 0.33}

    def read_channel(self, channel: int) -> float:
        """Lire la tension d'un canal ADC."""
        return self._channels.get(channel, 0.0)

    def tearDown(self):
        pass


class PwmPlug(BasePlug):
    """Interface de mesure PWM.

    Remplacez par un vrai compteur de fréquence ou oscilloscope.
    """

    def setUp(self):
        pass

    def measure_frequency(self) -> float:
        """Mesurer la fréquence de sortie PWM en Hz."""
        return 1000.0

    def measure_duty_cycle(self) -> float:
        """Mesurer le rapport cyclique PWM en pourcentage."""
        return 50.0

    def tearDown(self):
        pass

Étape 2 : Écrire les tests GPIO

Le test HIL le plus simple : définir une entrée, vérifier que le firmware pilote la bonne sortie.

hil/test_gpio.py
import openhtf as htf


@htf.measures(
    htf.Measurement("gpio_loopback")
    .equals(True)
    .doc("Bouclage GPIO : définir l'entrée, vérifier que la sortie suit"),
)
@htf.plug(gpio=GpioPlug)
def test_gpio_loopback(test, gpio):
    """Vérifier que le firmware route correctement l'entrée GPIO vers la sortie."""
    gpio.set_pin(1, True)  # Stimuler l'entrée du DUT
    readback = gpio.read_pin(1)  # Lire la sortie du DUT
    test.measurements.gpio_loopback = readback

Étape 3 : Écrire les tests ADC/capteur

Simulez les entrées capteur et vérifiez que le DUT les lit correctement.

hil/test_adc.py
import openhtf as htf
from openhtf.util import units


@htf.measures(
    htf.Measurement("adc_channel_0")
    .in_range(1.5, 1.8)
    .with_units(units.VOLT)
    .doc("ADC ch0 : tension de référence 1,65V"),
    htf.Measurement("adc_channel_1")
    .in_range(2.3, 2.7)
    .with_units(units.VOLT)
    .doc("ADC ch1 : sortie capteur 2,5V"),
)
@htf.plug(adc=AdcPlug)
def test_adc_readings(test, adc):
    """Vérifier que les canaux ADC lisent les tensions attendues."""
    test.measurements.adc_channel_0 = adc.read_channel(0)
    test.measurements.adc_channel_1 = adc.read_channel(1)

Étape 4 : Écrire les tests PWM

Vérifiez que le firmware génère le bon signal PWM (fréquence et rapport cyclique).

hil/test_pwm.py
import openhtf as htf
from openhtf.util import units


@htf.measures(
    htf.Measurement("pwm_frequency")
    .in_range(950, 1050)
    .with_units(units.HERTZ)
    .doc("Fréquence de sortie PWM (cible : 1 kHz)"),
    htf.Measurement("pwm_duty_cycle")
    .in_range(48, 52)
    .doc("Rapport cyclique PWM en pourcentage (cible : 50%)"),
)
@htf.plug(pwm=PwmPlug)
def test_pwm_output(test, pwm):
    """Vérifier la fréquence et le rapport cyclique de la sortie PWM."""
    test.measurements.pwm_frequency = pwm.measure_frequency()
    test.measurements.pwm_duty_cycle = pwm.measure_duty_cycle()

Étape 5 : Assembler le test HIL

Connectez toutes les phases avec TofuPilot pour suivre les régressions de versions firmware dans le temps.

hil/main.py
import openhtf as htf
from tofupilot.openhtf import TofuPilot


def main():
    test = htf.Test(
        test_gpio_loopback,
        test_adc_readings,
        test_pwm_output,
        procedure_id="HIL-001",
        part_number="ECU-100",
    )
    with TofuPilot(test):
        test.execute(test_start=lambda: input("Version du firmware : "))


if __name__ == "__main__":
    main()

Utilisez l'invite de numéro de série pour saisir la version du firmware. Cela permet à TofuPilot de suivre quels builds de firmware passent ou échouent à chaque test, construisant un historique de régression.

HIL en CI/CD

La vraie puissance du test HIL vient de son exécution automatique à chaque build de firmware.

Étape CI/CDAction
1. Push de codeLe développeur pousse une modification firmware
2. BuildLe CI compile le binaire firmware
3. FlashLe CI flashe le binaire sur la carte cible (via J-Link/ST-Link)
4. Test HILLe CI exécute le script de test OpenHTF
5. RésultatsTofuPilot enregistre le pass/fail par mesure
6. GateLe CI échoue le build si une mesure est hors spécification

Cela nécessite une station de test dédiée connectée à votre runner CI. La station de test comprend la carte cible, l'adaptateur GPIO, le DAQ et les instruments. Le runner CI (Jenkins, GitHub Actions self-hosted runner, etc.) déclenche le test après chaque build.

Catégories courantes de tests HIL

CatégorieQuoi testerMesures
GPIORoutage entrée/sortie, réponse aux interruptionsÉtat des broches, temps de réponse
ADCPrécision de lecture capteur, bruitValeurs de tension, plancher de bruit
DACPrécision de la tension de sortieValeurs de tension
PWMFréquence, rapport cyclique, résolutionFréquence, rapport cyclique
CommunicationIntégrité des données UART, SPI, I2C, CANTaux d'erreur binaire, temps de réponse
TimingLatence d'interruption, ordonnancement des tâchesTemps de réponse en microsecondes
AlimentationCourant en mode veille, temps de réveilConsommation de courant, latence de réveil
WatchdogRécupération après blocageDétection de reset, temps de récupération

HIL vs FCT en production

Le HIL et le FCT testent des choses différentes à des étapes différentes :

HILFCT
QuandDéveloppement, à chaque build firmwareProduction, à chaque carte fabriquée
QuoiComportement firmware sur matériel réelAssemblage de la carte et fonction système
DUTCarte de développement ou prototypeCarte de production
Ce qui change entre les testsFirmware (modifications de code)Matériel (variation de fabrication)
Un échec signifieBug firmware (correction de code)Défaut d'assemblage (reprise ou mise au rebut)
Fréquence d'exécutionÀ chaque build CIÀ chaque unité fabriquée
Utilisation TofuPilotSuivre les régressions entre versions firmwareSuivre le FPY, Cpk, cartes de contrôle

De nombreuses équipes utilisent le même framework de test OpenHTF pour le HIL et le FCT. Les plugs changent (GPIO carte de dev vs. montage de production), mais la structure de test reste la même.

Plus de guides

Mettez ce guide en pratique