Séquencement et orchestration de tests avec TofuPilot
Un test hardware n'est pas une vérification unique. C'est une séquence : mise sous tension, attente du démarrage, mesure des tensions, calibration, vérification des communications, test de stress, mise hors tension. TofuPilot capture chaque étape avec ses mesures et son timing, vous donnant un enregistrement structuré du flux de test complet.
Pourquoi le séquencement des tests est important
Exécuter des tests manuellement depuis un instrument de banc fonctionne pour les prototypes. Cela ne fonctionne pas en production. Les tests de production nécessitent :
- Répétabilité : Chaque unité passe par les mêmes étapes exactement dans le même ordre
- Rapidité : Pas d'attente pour qu'un opérateur clique sur « suivant »
- Capture des données : Chaque mesure est enregistrée automatiquement avec ses limites et son statut pass/fail
- Traçabilité : Un enregistrement exact de ce qui a été testé, dans quel ordre, avec quels résultats
L'orchestration de test consiste à définir cette séquence une fois et à l'exécuter de la même manière à chaque fois.
Architecture d'une séquence de test
Une séquence de test bien structurée suit un schéma :
┌─────────────┐
│ Setup │ Mise sous tension, initialisation des instruments, identification du DUT
├─────────────┤
│ Étape 1 │ Mesure ou action avec critères pass/fail
├─────────────┤
│ Étape 2 │ Mesure ou action suivante
├─────────────┤
│ ... │ Étapes supplémentaires selon les besoins
├─────────────┤
│ Teardown │ Mise hors tension, libération des instruments, envoi des résultats
└─────────────┘
Chaque étape produit des mesures. Chaque mesure a des limites. La séquence s'arrête en cas de défaillance critique ou continue à travers toutes les étapes selon votre stratégie.
Construire une séquence de test avec OpenHTF
OpenHTF est un framework Python conçu pour le séquencement de tests hardware. TofuPilot s'intègre directement en tant que callback de sortie.
import openhtf as htf
from openhtf.plugs import BasePlug
from tofupilot.openhtf import TofuPilotClient
class PowerSupplyPlug(BasePlug):
"""Contrôle l'alimentation de banc."""
def setup(self):
self.psu = connect_power_supply()
def set_voltage(self, voltage):
self.psu.write(f"VOLT {voltage}")
def enable_output(self):
self.psu.write("OUTP ON")
def disable_output(self):
self.psu.write("OUTP OFF")
def teardown(self):
self.disable_output()
class DMMPlug(BasePlug):
"""Lit les valeurs du multimètre numérique."""
def setup(self):
self.dmm = connect_dmm()
def measure_voltage(self):
return float(self.dmm.query("MEAS:VOLT:DC?"))
def measure_current(self):
return float(self.dmm.query("MEAS:CURR:DC?"))
# Étape 1 : Vérification des rails d'alimentation
@htf.measures(
htf.Measurement("vcc_3v3").in_range(3.25, 3.35).with_units("V"),
htf.Measurement("vcc_1v8").in_range(1.75, 1.85).with_units("V"),
htf.Measurement("vcc_5v0").in_range(4.90, 5.10).with_units("V"),
)
@htf.PhaseOptions(name="Power Rail Verification")
def power_rail_check(test, psu: PowerSupplyPlug, dmm: DMMPlug):
psu.set_voltage(12.0)
psu.enable_output()
time.sleep(0.5) # Attendre la stabilisation des rails
test.measurements.vcc_3v3 = dmm.measure_voltage()
# Changer le canal du DMM et mesurer les autres rails
test.measurements.vcc_1v8 = dmm.measure_voltage()
test.measurements.vcc_5v0 = dmm.measure_voltage()
# Étape 2 : Consommation de courant
@htf.measures(
htf.Measurement("idle_current_ma").in_range(30, 60).with_units("mA"),
htf.Measurement("active_current_ma").in_range(80, 150).with_units("mA"),
)
@htf.PhaseOptions(name="Current Consumption")
def current_check(test, psu: PowerSupplyPlug, dmm: DMMPlug):
test.measurements.idle_current_ma = dmm.measure_current() * 1000
trigger_active_mode()
time.sleep(0.2)
test.measurements.active_current_ma = dmm.measure_current() * 1000
# Étape 3 : Vérification des communications
@htf.measures(
htf.Measurement("uart_loopback").equals(True),
htf.Measurement("spi_whoami").equals(0x68),
)
@htf.PhaseOptions(name="Communication Interfaces")
def comm_check(test):
test.measurements.uart_loopback = verify_uart_loopback()
test.measurements.spi_whoami = read_spi_register(0x75)
def main():
test = htf.Test(
power_rail_check,
current_check,
comm_check,
)
test.add_output_callbacks(TofuPilotClient())
test.execute(lambda: input("Scanner le numéro de série du DUT : "))Chaque phase s'exécute dans l'ordre. Chaque mesure est capturée. La séquence complète est envoyée à TofuPilot lorsque le test se termine.
Construire une séquence de test avec le client Python
Si vous n'utilisez pas OpenHTF, le client Python TofuPilot gère directement le séquencement des tests.
from tofupilot import TofuPilotClient
import time
client = TofuPilotClient()
serial = input("Scanner le numéro de série du DUT : ")
steps = []
# Étape 1 : Rails d'alimentation
psu.enable(12.0)
time.sleep(0.5)
vcc_3v3 = dmm.measure_voltage(channel=1)
vcc_1v8 = dmm.measure_voltage(channel=2)
steps.append({
"name": "Power Rail Verification",
"step_type": "measurement",
"status": 3.25 <= vcc_3v3 <= 3.35 and 1.75 <= vcc_1v8 <= 1.85,
"measurements": [
{"name": "vcc_3v3", "value": vcc_3v3, "unit": "V", "limit_low": 3.25, "limit_high": 3.35},
{"name": "vcc_1v8", "value": vcc_1v8, "unit": "V", "limit_low": 1.75, "limit_high": 1.85},
],
})
# Étape 2 : Vérification fonctionnelle
boot_ok = wait_for_boot(timeout=5)
steps.append({
"name": "Boot Sequence",
"step_type": "measurement",
"status": boot_ok,
"measurements": [
{"name": "boot_success", "value": boot_ok, "unit": "bool"},
],
})
# Envoyer la séquence complète
run_passed = all(s["status"] for s in steps)
client.create_run(
procedure_id="BOARD-FUNCTIONAL-V3",
unit_under_test={"serial_number": serial},
run_passed=run_passed,
steps=steps,
)Bonnes pratiques de conception de séquence
| Pratique | Pourquoi |
|---|---|
| Tester les éléments simples en premier | Si un rail d'alimentation est en court-circuit, inutile de perdre du temps sur les vérifications de communication |
| Regrouper les mesures liées | Garder toutes les mesures de tension dans une étape, toutes les mesures de courant dans une autre |
| Utiliser des noms d'étapes cohérents | « Power Rail Verification » dans toutes les procédures, pas « Voltage Check » dans l'une et « Rail Test » dans l'autre |
| Inclure setup/teardown | Toujours couper l'alimentation du DUT à la fin, même si le test échoue |
| Définir des limites sur chaque mesure | Une mesure sans limites ne peut pas être suivie en tendance ni analysée |
Gestion des échecs de séquence
Deux stratégies pour gérer l'échec d'une étape :
Échec rapide (fail-fast) : Arrêter la séquence immédiatement au premier échec. Utilisez cette approche pour les tests critiques pour la sécurité ou lorsqu'un échec à l'étape 1 rend les étapes suivantes inutiles (ex. : un échec du rail d'alimentation signifie qu'il est inutile de tester les communications).
Exécuter tout (run-all) : Continuer à travers toutes les étapes même si l'une échoue. Utilisez cette approche quand vous voulez des données de diagnostic complètes (ex. : savoir lesquelles parmi 20 mesures ont échoué aide à l'analyse des causes racines).
OpenHTF prend en charge les deux via les options de phase. Le client Python TofuPilot vous permet d'implémenter l'un ou l'autre dans votre logique de test.
Ce que TofuPilot enregistre pour chaque séquence
Chaque exécution de test dans TofuPilot capture :
| Donnée | Objectif |
|---|---|
| Identifiant de procédure | Quelle séquence de test a été exécutée |
| Numéro de série | Quelle unité a été testée |
| Résultat global pass/fail | La séquence a-t-elle réussi ? |
| Étapes avec mesures | Chaque étape, chaque mesure, chaque limite |
| Horodatages | Quand le test a commencé et terminé |
| Identifiant de station | Quelle station a exécuté le test |
| Durée | Combien de temps la séquence a pris |
Ces données structurées sont ce qui permet le suivi des tendances, la comparaison et l'analyse à travers tout votre historique de production.