Instrument Control

Premiers pas avec PyVISA

Apprenez à connecter et contrôler des instruments de test (multimètres, oscilloscopes, alimentations) depuis Python avec PyVISA, avec intégration.

JJulien Buteau
beginner10 min de lecture14 mars 2026

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

pyvisa_setup/install.sh
pip install pyvisa pyvisa-py

Deux backends sont disponibles :

BackendInstallationQuand l'utiliser
pyvisa-pypip install pyvisa-pyPython pur. Fonctionne sous Linux/macOS/Windows. Supporte USB-TMC, Ethernet (TCP/IP), série. Pas besoin de logiciel NI.
NI-VISAInstaller depuis ni.comNé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.

pyvisa_setup/discover.py
import pyvisa

rm = pyvisa.ResourceManager("@py")  # Utiliser le backend pyvisa-py
# rm = pyvisa.ResourceManager()     # Utiliser le backend NI-VISA

# Lister tous les instruments connectés
resources = 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 :

InterfaceChaîne de ressourceExemple
USB-TMCUSB0::VID::PID::SERIAL::INSTRUSB0::0x2A8D::0x1301::MY59001234::INSTR
Ethernet (LXI)TCPIP::IP::INSTRTCPIP::192.168.1.100::INSTR
GPIBGPIB0::ADDR::INSTRGPIB0::22::INSTR
SérieASRL/dev/ttyUSB0::INSTRASRL/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?.

pyvisa_setup/connect.py
import pyvisa

rm = 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'instrument
idn = dmm.query("*IDN?")
print(f"Connecté à : {idn.strip()}")
# Sortie : Keysight Technologies,34461A,MY59001234,A.03.01

La 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

pyvisa_setup/measure_voltage.py
import pyvisa

rm = pyvisa.ResourceManager("@py")
dmm = rm.open_resource("TCPIP::192.168.1.100::INSTR")
dmm.timeout = 5000

# Configurer pour la mesure de tension DC, auto-range
dmm.write(":CONF:VOLT:DC AUTO")

# Déclencher une mesure et lire le résultat
dmm.write(":INIT")
voltage = float(dmm.query(":FETCH?"))

print(f"Tension : {voltage:.4f} V")

dmm.close()
rm.close()

Commandes SCPI courantes

CommandeFonctionExemple
*IDN?Identifier l'instrumentRetourne fabricant, modèle, série, firmware
*RSTRéinitialiser aux paramètres d'usineBonne pratique au début du test
*CLSEffacer la file d'erreursEffacer les erreurs précédentes
*OPC?Requête de fin d'opérationRetourne « 1 » quand la dernière commande est terminée
:CONF:VOLT:DCConfigurer la tension DC:CONF:VOLT:DC 10 pour le calibre 10 V
:CONF:CURR:DCConfigurer le courant DC:CONF:CURR:DC AUTO pour l'auto-range
:CONF:RESConfigurer la résistance:CONF:RES AUTO
:MEAS:VOLT:DC?Mesurer la tension DC (configurer + déclencher + lire)Retourne un flottant
:INITDéclencher une mesureUtiliser avec :FETCH? pour séparer déclenchement/lecture
:FETCH?Lire la dernière mesureRetourne un flottant
:SYST:ERR?Lire la file d'erreursRetourne 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.

pyvisa_setup/power_supply.py
import pyvisa
import time

rm = pyvisa.ResourceManager("@py")
psu = rm.open_resource("TCPIP::192.168.1.101::INSTR")
psu.timeout = 5000

# Réinitialiser à un état connu
psu.write("*RST")
psu.write("*CLS")

# Configurer le canal 1 : 5 V, limite de courant 500 mA
psu.write(":INST:SEL CH1")
psu.write(":VOLT 5.0")
psu.write(":CURR 0.5")

# Activer la sortie
psu.write(":OUTP ON")
time.sleep(0.5)  # Attendre la stabilisation de la sortie

# Lire la tension et le courant réels
actual_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 sortie
psu.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.

pyvisa_setup/error_handling.py
import pyvisa


def 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 errors


rm = pyvisa.ResourceManager("@py")
dmm = rm.open_resource("TCPIP::192.168.1.100::INSTR")
dmm.timeout = 5000

# Envoyer des commandes
dmm.write("*RST")
dmm.write(":CONF:VOLT:DC AUTO")
dmm.write(":INIT")
voltage = float(dmm.query(":FETCH?"))

# Vérifier les erreurs
errors = 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.

pyvisa_setup/production_test.py
import pyvisa
import openhtf as htf
from openhtf.plugs import BasePlug
from openhtf.util import units
from tofupilot.openhtf import TofuPilot


class 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èmeCauseSolution
VisaIOError: VI_ERROR_RSRC_NFOUNDInstrument non trouvéVérifier le câble, l'IP/USB, exécuter rm.list_resources()
VisaIOError: VI_ERROR_TMOTimeoutAugmenter instr.timeout, vérifier si l'instrument est occupé
VisaIOError: VI_ERROR_CONN_LOSTConnexion perdueVérifier le réseau, retenter la connexion
Réponse vide de query()L'instrument n'a pas réponduAjouter time.sleep() après write, vérifier la syntaxe de la commande
Instrument USB non détectéPilote manquantInstaller le runtime NI-VISA ou vérifier le module kernel USB-TMC (Linux)
ValueError: could not convert string to floatFormat de réponse inattenduAfficher la réponse brute, vérifier la présence de messages d'erreur mélangés

Plus de guides

Mettez ce guide en pratique