Le test fonctionnel de PCBA (FCT) vérifie qu'une carte assemblée fonctionne comme prévu. C'est le dernier test avant l'expédition. Contrairement à l'ICT (test in-circuit) qui vérifie les composants individuels, le FCT teste la carte comme un système : rails d'alimentation, interfaces de communication, firmware et courant consommé. Ce guide vous montre comment construire un FCT de production avec Python, OpenHTF et TofuPilot.
Méthodes de test PCBA comparées
| Méthode | Ce qu'elle teste | Quand l'utiliser | Équipement |
|---|---|---|---|
| ICT (Test In-Circuit) | Composants individuels : résistances, condensateurs, CI | Grands volumes, après refusion SMT | Lit à clous, testeur ICT dédié |
| Sonde mobile | Même chose que l'ICT, sans fixation | Faibles volumes, prototypes | Machine à sondes mobiles |
| FCT (Test Fonctionnel) | Comportement au niveau carte : alimentation, communication, firmware | Chaque carte avant expédition | Fixation + instruments + script de test |
| Boundary Scan (JTAG) | Connexions CI, logique numérique | Cartes BGA complexes | Adaptateur JTAG |
| AOI (Inspection Optique Automatisée) | Joints de soudure, placement des composants | Après refusion, avant ICT/FCT | Machine AOI |
La plupart des lignes de production exécutent l'AOI après refusion, puis le FCT avant le conditionnement. L'ICT est optionnel et rentable uniquement à grands volumes (10K+ cartes/an).
Couverture du FCT
Un FCT typique vérifie ces catégories :
| Catégorie | Quoi tester | Mesures typiques |
|---|---|---|
| Alimentation | Tous les rails de tension, courant consommé | Rails 3.3V, 5V, 1.8V ; courant au repos et en activité |
| Communication | UART, SPI, I2C, USB, Ethernet | Requête de version firmware, réponse d'auto-test |
| E/S numériques | GPIO, LED, boutons | État LED, réponse bouton, niveaux logiques |
| Analogique | Lectures ADC, sortie DAC | Tension calibrée, lectures capteurs |
| Composants passifs | Résistances de pull-up/pull-down | Mesure de résistance (hors tension) |
| Firmware | Version, auto-test, intégrité flash | Chaîne de version, vérification CRC |
Étape 1 : Définir votre fixation de test
Une fixation de test connecte le DUT (dispositif sous test) à vos instruments. Pour le FCT, vous avez typiquement besoin de :
| Instrument | Objectif | Connexion |
|---|---|---|
| Alimentation de banc | Alimenter le DUT | Fiches banane vers la fixation |
| Multimètre (DMM) | Mesurer tension, courant, résistance | Sondes de test vers la fixation |
| Adaptateur UART | Communiquer avec le firmware du DUT | USB-série vers la fixation |
| Optionnel : oscilloscope | Vérifier l'intégrité des signaux | Sondes vers les points de test |
Étape 2 : Créer les Plugs d'instruments
Chaque instrument obtient un Plug OpenHTF. Le plug gère la configuration et la déconnexion. Les Plugs sont injectés dans les phases de test via le décorateur @htf.plug().
import openhtf as htf
from openhtf.plugs import BasePlug
class PowerSupplyPlug(BasePlug):
\"\"\"Contrôle de l'alimentation de banc.\"\"\"
def setUp(self):
self.output_on = False
# Remplacer par une vraie connexion à l'instrument (ex. PyVISA)
def set_voltage(self, channel: int, voltage: float):
\"\"\"Définir la tension de sortie sur un canal.\"\"\"
pass # Remplacer : psu.write(f":INST:SEL CH{channel}"); psu.write(f":VOLT {voltage}")
def set_current_limit(self, channel: int, current: float):
\"\"\"Définir la limite de courant sur un canal.\"\"\"
pass # Remplacer : psu.write(f":CURR {current}")
def enable_output(self):
self.output_on = True
# Remplacer : psu.write(":OUTP ON")
def disable_output(self):
self.output_on = False
# Remplacer : psu.write(":OUTP OFF")
def tearDown(self):
self.disable_output()
class MultimeterPlug(BasePlug):
\"\"\"DMM pour mesures de tension, courant et résistance.\"\"\"
def setUp(self):
pass # Remplacer par une connexion PyVISA
def measure_voltage(self) -> float:
return 3.31 # Remplacer : float(dmm.query(":MEAS:VOLT:DC?"))
def measure_current(self) -> float:
return 0.12 # Remplacer : float(dmm.query(":MEAS:CURR:DC?"))
def measure_resistance(self) -> float:
return 4720.0 # Remplacer : float(dmm.query(":MEAS:RES?"))
def tearDown(self):
pass
class UartPlug(BasePlug):
\"\"\"Interface UART pour la communication avec le DUT.\"\"\"
def setUp(self):
pass # Remplacer : serial.Serial("/dev/ttyUSB0", 115200)
def send_command(self, cmd: str) -> str:
if cmd == "AT+VERSION?":
return "2.1.0" # Remplacer par une vraie lecture série
if cmd == "AT+STATUS?":
return "OK"
return ""
def tearDown(self):
passÉtape 3 : Écrire le test des rails d'alimentation
Cette phase alimente le DUT et mesure tous les rails de tension.
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement("rail_3v3")
.in_range(3.2, 3.4)
.with_units(units.VOLT)
.doc("Rail d'alimentation 3.3V"),
htf.Measurement("rail_5v0")
.in_range(4.8, 5.2)
.with_units(units.VOLT)
.doc("Rail d'alimentation 5.0V"),
htf.Measurement("rail_1v8")
.in_range(1.7, 1.9)
.with_units(units.VOLT)
.doc("Rail cœur 1.8V"),
)
@htf.plug(psu=PowerSupplyPlug, dmm=MultimeterPlug)
def test_power_rails(test, psu, dmm):
\"\"\"Alimenter le DUT et vérifier tous les rails de tension.\"\"\"
psu.set_voltage(1, 12.0)
psu.set_current_limit(1, 1.0)
psu.enable_output()
test.measurements.rail_3v3 = dmm.measure_voltage()
test.measurements.rail_5v0 = dmm.measure_voltage()
test.measurements.rail_1v8 = dmm.measure_voltage()Étape 4 : Écrire le test de communication
Vérifier que le firmware du DUT répond correctement via UART.
import openhtf as htf
@htf.measures(
htf.Measurement("firmware_version")
.equals("2.1.0")
.doc("Chaîne de version du firmware"),
htf.Measurement("self_test_status")
.equals("OK")
.doc("Résultat de l'auto-test de la carte"),
)
@htf.plug(uart=UartPlug)
def test_communication(test, uart):
\"\"\"Interroger la version du firmware et le statut de l'auto-test.\"\"\"
test.measurements.firmware_version = uart.send_command("AT+VERSION?")
test.measurements.self_test_status = uart.send_command("AT+STATUS?")Étape 5 : Écrire le test de courant consommé
Un courant excessif indique généralement un court-circuit ou un composant endommagé.
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement("idle_current")
.in_range(0.05, 0.20)
.with_units(units.AMPERE)
.doc("Courant consommé au repos par la carte"),
)
@htf.plug(dmm=MultimeterPlug)
def test_current_draw(test, dmm):
\"\"\"Mesurer le courant consommé au repos.\"\"\"
test.measurements.idle_current = dmm.measure_current()Étape 6 : Écrire le test des composants passifs
Vérifier les résistances de pull-up avec le DUT hors tension. Des valeurs de résistance incorrectes provoquent des erreurs de communication intermittentes difficiles à détecter autrement.
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement("i2c_pullup")
.in_range(4500, 5100)
.with_units(units.OHM)
.doc("Résistance de pull-up I2C SDA"),
)
@htf.plug(dmm=MultimeterPlug)
def test_pullup_resistors(test, dmm):
\"\"\"Vérifier les valeurs des résistances de pull-up I2C (hors tension).\"\"\"
test.measurements.i2c_pullup = dmm.measure_resistance()Étape 7 : Assembler et exécuter
Connectez toutes les phases dans un test avec TofuPilot pour la traçabilité de production.
import openhtf as htf
from tofupilot.openhtf import TofuPilot
def main():
test = htf.Test(
test_power_rails,
test_communication,
test_current_draw,
test_pullup_resistors,
procedure_id="PCBA-FCT-001",
part_number="PCBA-200",
)
with TofuPilot(test):
test.execute(test_start=lambda: input("Scanner le numéro de série : "))
if __name__ == "__main__":
main()Chaque mesure est envoyée à TofuPilot avec son nom, sa valeur, ses limites, ses unités et son statut de réussite/échec. Vous obtenez FPY, Cpk et graphiques de contrôle par mesure sans code supplémentaire.
Checklist de conception pour la testabilité (DFT)
Une bonne couverture FCT commence dès la conception du PCB. Suivez ces recommandations :
| Règle DFT | Pourquoi | Impact sur le FCT |
|---|---|---|
| Ajouter des points de test sur tous les rails de tension | Accès DMM sans sonder les CI | Mesure directe de tension |
| Sortir UART/SPI/I2C sur un connecteur | Test de communication sans lit à clous | Vérification du firmware |
| Ajouter une LED de mise sous tension | Vérification visuelle rapide | Mesure booléenne |
| Inclure un auto-test dans le firmware | La carte peut vérifier ses propres périphériques | Une seule commande valide plusieurs sous-systèmes |
| Étiqueter les points de test sur le sérigraphie | Les opérateurs peuvent sonder manuellement si nécessaire | Réduit la complexité de la fixation |
| Router les points de test en bord de carte | Accès par pointes pogo dans la fixation | Construction de fixation plus rapide |
Modes de défaillance FCT courants
| Défaillance | Cause typique | Comment la détecter |
|---|---|---|
| Rail de tension hors spécification | Mauvaise valeur de résistance dans le diviseur du régulateur, joint de soudure froid | Mesurer la tension du rail avec des limites |
| Courant consommé excessif | Pont de soudure, CI endommagé | Mesurer le courant au repos et en activité |
| Timeout de communication | Pull-up manquant, mauvais baud rate, CI non programmé | Interroger le firmware, vérifier le temps de réponse |
| Mauvaise version firmware | Erreur de programmation, mauvais binaire | Interroger la chaîne de version, comparer avec l'attendu |
| Défaillance intermittente | Joint de soudure marginal, connecteur desserré | Exécuter le test plusieurs fois, vérifier la variance des mesures |