PyVISA vous permet de contrôler des instruments de test (multimètres, oscilloscopes, alimentations, générateurs de signaux) depuis Python en utilisant le même protocole VISA que LabVIEW et TestStand. Si votre instrument dispose d'une interface USB, GPIB, Ethernet ou série, vous pouvez communiquer avec lui via PyVISA. Ce guide couvre l'installation, la connexion, les commandes SCPI et l'intégration des mesures d'instruments dans un test TofuPilot.
Prérequis
- Python 3.8+
- Un instrument de test avec interface USB-TMC, GPIB, Ethernet (LXI) ou série
- Optionnel : runtime NI-VISA (pour le GPIB et certains instruments USB)
Installation
pip install pyvisa pyvisa-pyDeux backends sont disponibles :
| Backend | Installation | Quand l'utiliser |
|---|---|---|
| pyvisa-py | pip install pyvisa-py | Python pur. Fonctionne sous Linux/macOS/Windows. Supporte USB-TMC, Ethernet (TCP/IP), série. Pas besoin de logiciel NI. |
| NI-VISA | Installer depuis ni.com | Nécessaire pour le GPIB. Supporte également USB-TMC et Ethernet. Windows et Linux uniquement. |
Commencez avec pyvisa-py. Passez à NI-VISA uniquement si vous avez besoin du GPIB ou rencontrez des problèmes de compatibilité.
Trouver vos instruments
Chaque instrument VISA possède une chaîne de ressource qui l'identifie. PyVISA peut découvrir automatiquement les instruments connectés.
import pyvisarm = pyvisa.ResourceManager("@py") # Utiliser le backend pyvisa-py# rm = pyvisa.ResourceManager() # Utiliser le backend NI-VISA# Lister tous les instruments connectésresources = rm.list_resources()print(f"{len(resources)} instrument(s) trouvé(s) :")for r in resources: print(f" {r}")Formats courants de chaînes de ressource :
| Interface | Chaîne de ressource | Exemple |
|---|---|---|
| USB-TMC | USB0::VID::PID::SERIAL::INSTR | USB0::0x2A8D::0x1301::MY59001234::INSTR |
| Ethernet (LXI) | TCPIP::IP::INSTR | TCPIP::192.168.1.100::INSTR |
| GPIB | GPIB0::ADDR::INSTR | GPIB0::22::INSTR |
| Série | ASRL/dev/ttyUSB0::INSTR | ASRL/dev/ttyUSB0::INSTR |
Se connecter à un instrument
Une fois la chaîne de ressource obtenue, ouvrez une connexion et vérifiez l'identité avec la requête standard *IDN?.
import pyvisarm = pyvisa.ResourceManager("@py")dmm = rm.open_resource("TCPIP::192.168.1.100::INSTR")# Définir le timeout (en millisecondes)dmm.timeout = 5000# Requête d'identité de l'instrumentidn = dmm.query("*IDN?")print(f"Connecté à : {idn.strip()}")# Sortie : Keysight Technologies,34461A,MY59001234,A.03.01La méthode query() envoie une commande et lit la réponse. Pour les commandes qui ne retournent pas de données, utilisez write().
Bases des commandes SCPI
SCPI (Standard Commands for Programmable Instruments) est le langage que la plupart des instruments modernes utilisent. Les commandes suivent une structure arborescente.
Lire une tension DC
import pyvisarm = pyvisa.ResourceManager("@py")dmm = rm.open_resource("TCPIP::192.168.1.100::INSTR")dmm.timeout = 5000# Configurer pour la mesure de tension DC, auto-rangedmm.write(":CONF:VOLT:DC AUTO")# Déclencher une mesure et lire le résultatdmm.write(":INIT")voltage = float(dmm.query(":FETCH?"))print(f"Tension : {voltage:.4f} V")dmm.close()rm.close()Commandes SCPI courantes
| Commande | Fonction | Exemple |
|---|---|---|
*IDN? | Identifier l'instrument | Retourne fabricant, modèle, série, firmware |
*RST | Réinitialiser aux paramètres d'usine | Bonne pratique au début du test |
*CLS | Effacer la file d'erreurs | Effacer les erreurs précédentes |
*OPC? | Requête de fin d'opération | Retourne « 1 » quand la dernière commande est terminée |
:CONF:VOLT:DC | Configurer la tension DC | :CONF:VOLT:DC 10 pour le calibre 10 V |
:CONF:CURR:DC | Configurer le courant DC | :CONF:CURR:DC AUTO pour l'auto-range |
:CONF:RES | Configurer la résistance | :CONF:RES AUTO |
:MEAS:VOLT:DC? | Mesurer la tension DC (configurer + déclencher + lire) | Retourne un flottant |
:INIT | Déclencher une mesure | Utiliser avec :FETCH? pour séparer déclenchement/lecture |
:FETCH? | Lire la dernière mesure | Retourne un flottant |
:SYST:ERR? | Lire la file d'erreurs | Retourne le code d'erreur et le message |
Le raccourci :MEAS: configure, déclenche et lit en une seule commande. Utilisez :CONF: + :INIT + :FETCH? quand vous avez besoin de plus de contrôle sur le timing.
Contrôler une alimentation
Les alimentations utilisent des commandes SCPI similaires mais ajoutent le contrôle de sortie.
import pyvisaimport timerm = pyvisa.ResourceManager("@py")psu = rm.open_resource("TCPIP::192.168.1.101::INSTR")psu.timeout = 5000# Réinitialiser à un état connupsu.write("*RST")psu.write("*CLS")# Configurer le canal 1 : 5 V, limite de courant 500 mApsu.write(":INST:SEL CH1")psu.write(":VOLT 5.0")psu.write(":CURR 0.5")# Activer la sortiepsu.write(":OUTP ON")time.sleep(0.5) # Attendre la stabilisation de la sortie# Lire la tension et le courant réelsactual_voltage = float(psu.query(":MEAS:VOLT?"))actual_current = float(psu.query(":MEAS:CURR?"))print(f"Sortie : {actual_voltage:.3f} V, {actual_current:.4f} A")# Désactiver la sortiepsu.write(":OUTP OFF")psu.close()rm.close()Gestion des erreurs
Les instruments communiquent les erreurs via la file d'erreurs SCPI. Vérifiez toujours les erreurs après une séquence de commandes.
import pyvisadef check_instrument_errors(instr): """Lit et signale toutes les erreurs de la file d'erreurs de l'instrument.""" errors = [] while True: err = instr.query(":SYST:ERR?").strip() code = int(err.split(",")[0]) if code == 0: break errors.append(err) return errorsrm = pyvisa.ResourceManager("@py")dmm = rm.open_resource("TCPIP::192.168.1.100::INSTR")dmm.timeout = 5000# Envoyer des commandesdmm.write("*RST")dmm.write(":CONF:VOLT:DC AUTO")dmm.write(":INIT")voltage = float(dmm.query(":FETCH?"))# Vérifier les erreurserrors = check_instrument_errors(dmm)if errors: print(f"Erreurs instrument : {errors}")else: print(f"Tension : {voltage:.4f} V (aucune erreur)")dmm.close()Intégration avec OpenHTF et TofuPilot
Dans un test de production, le contrôle des instruments se fait dans un plug OpenHTF. Le plug gère le cycle de vie de la connexion, et les mesures transitent automatiquement via OpenHTF vers TofuPilot.
import pyvisaimport openhtf as htffrom openhtf.plugs import BasePlugfrom openhtf.util import unitsfrom tofupilot.openhtf import TofuPilotclass MultimeterPlug(BasePlug): """Plug PyVISA pour multimètre avec gestion automatique du cycle de vie.""" RESOURCE = "TCPIP::192.168.1.100::INSTR" def setUp(self): rm = pyvisa.ResourceManager("@py") self.instr = rm.open_resource(self.RESOURCE) self.instr.timeout = 5000 self.instr.write("*RST") self.instr.write("*CLS") self.logger.info(f"Connecté : {self.instr.query('*IDN?').strip()}") def measure_dc_voltage(self, range_v: str = "AUTO") -> float: """Effectue une mesure unique de tension DC.""" self.instr.write(f":CONF:VOLT:DC {range_v}") return float(self.instr.query(":MEAS:VOLT:DC?")) def measure_dc_current(self, range_a: str = "AUTO") -> float: """Effectue une mesure unique de courant DC.""" self.instr.write(f":CONF:CURR:DC {range_a}") return float(self.instr.query(":MEAS:CURR:DC?")) def measure_resistance(self, range_ohm: str = "AUTO") -> float: """Effectue une mesure unique de résistance.""" self.instr.write(f":CONF:RES {range_ohm}") return float(self.instr.query(":MEAS:RES?")) def tearDown(self): self.instr.close() self.logger.info("Multimètre déconnecté")@htf.measures( htf.Measurement("supply_3v3") .in_range(3.2, 3.4) .with_units(units.VOLT) .doc("Tension du rail 3,3 V"), htf.Measurement("supply_5v0") .in_range(4.8, 5.2) .with_units(units.VOLT) .doc("Tension du rail 5,0 V"), htf.Measurement("idle_current") .in_range(0.01, 0.15) .with_units(units.AMPERE) .doc("Courant de repos de la carte"),)@htf.plug(dmm=MultimeterPlug)def test_power_rails(test, dmm): """Mesure tous les rails d'alimentation et le courant de repos.""" test.measurements.supply_3v3 = dmm.measure_dc_voltage() test.measurements.supply_5v0 = dmm.measure_dc_voltage() test.measurements.idle_current = dmm.measure_dc_current()@htf.measures( htf.Measurement("pullup_resistance") .in_range(4500, 5500) .with_units(units.OHM) .doc("Résistance de pull-up I2C (attendu 4,7k)"),)@htf.plug(dmm=MultimeterPlug)def test_pullup_resistors(test, dmm): """Vérifie les valeurs des résistances de pull-up I2C.""" test.measurements.pullup_resistance = dmm.measure_resistance()def main(): test = htf.Test( test_power_rails, test_pullup_resistors, procedure_id="FCT-001", part_number="PCBA-100", ) with TofuPilot(test): test.execute(test_start=lambda: input("Scannez le numéro de série : "))if __name__ == "__main__": main()Chaque mesure (tension, courant, résistance) est transmise à TofuPilot avec son nom, sa valeur, ses limites et ses unités. Vous obtenez le suivi FPY, les cartes de contrôle et l'analyse Cpk pour chaque mesure sans code supplémentaire.
Dépannage
| Problème | Cause | Solution |
|---|---|---|
VisaIOError: VI_ERROR_RSRC_NFOUND | Instrument non trouvé | Vérifier le câble, l'IP/USB, exécuter rm.list_resources() |
VisaIOError: VI_ERROR_TMO | Timeout | Augmenter instr.timeout, vérifier si l'instrument est occupé |
VisaIOError: VI_ERROR_CONN_LOST | Connexion perdue | Vérifier le réseau, retenter la connexion |
Réponse vide de query() | L'instrument n'a pas répondu | Ajouter time.sleep() après write, vérifier la syntaxe de la commande |
| Instrument USB non détecté | Pilote manquant | Installer le runtime NI-VISA ou vérifier le module kernel USB-TMC (Linux) |
ValueError: could not convert string to float | Format de réponse inattendu | Afficher la réponse brute, vérifier la présence de messages d'erreur mélangés |