Skip to content
Instrument Control

How to Control a Keysight DMM with TofuPilot

Connect a Keysight 34461A or 34465A DMM to Python using PyVISA, measure voltage, current, and resistance, and log results to TofuPilot via an OpenHTF plug.

JJulien Buteau
beginner12 min readMarch 14, 2026

Connect a Keysight 34461A or 34465A DMM to Python using PyVISA, send SCPI commands to measure DC voltage, AC voltage, resistance, and current, then log results automatically to TofuPilot via an OpenHTF plug.

Prerequisites

  • Keysight 34461A or 34465A DMM (or compatible 344xxA series)
  • Python 3.8+
  • OpenHTF and TofuPilot installed
  • Keysight IO Libraries Suite (for USB) or network access (for Ethernet)
terminal
pip install pyvisa pyvisa-py tofupilot openhtf

Step 1: Connect to the Instrument

Keysight DMMs support two primary interfaces: USB-TMC and Ethernet (LXI).

InterfaceVISA Resource StringUse Case
USB-TMCUSB0::0x0957::0x1A07::MY12345678::INSTRSingle bench, direct PC connection
Ethernet (VXI-11)TCPIP0::192.168.1.100::inst0::INSTRNetworked test systems
Ethernet (HiSLIP)TCPIP0::192.168.1.100::hislip0::INSTRHigher throughput, recommended for 34465A

Find your instrument's resource string:

find_instruments.py
import pyvisa

rm = pyvisa.ResourceManager()
print(rm.list_resources())

Open a connection and verify identity:

connect.py
import pyvisa

rm = pyvisa.ResourceManager()
dmm = rm.open_resource("USB0::0x0957::0x1A07::MY12345678::INSTR")
dmm.timeout = 5000  # ms
print(dmm.query("*IDN?"))
# Keysight Technologies,34461A,MY12345678,A.02.14-02.40-02.14-00.49-01-01

Step 2: Configure SCPI Measurements

Always reset to a known state before configuring.

scpi_basics.py
dmm.write("*RST")
dmm.write("*CLS")
print(dmm.query("SYST:ERR?"))
# +0,"No error"

DC Voltage

measure_dc_voltage.py
# Auto-range DC voltage
dmm.write("CONF:VOLT:DC AUTO,DEF")
reading = float(dmm.query("READ?"))
print(f"DC Voltage: {reading:.6f} V")

# Manual range: 10 V range
dmm.write("CONF:VOLT:DC 10,DEF")
reading = float(dmm.query("READ?"))

AC Voltage

measure_ac_voltage.py
dmm.write("CONF:VOLT:AC AUTO,DEF")
reading = float(dmm.query("READ?"))
print(f"AC Voltage (RMS): {reading:.6f} V")

Resistance (2-wire and 4-wire)

measure_resistance.py
# 2-wire resistance, auto-range
dmm.write("CONF:RES AUTO,DEF")
reading = float(dmm.query("READ?"))
print(f"2-wire resistance: {reading:.4f} Ohm")

# 4-wire resistance (eliminates lead resistance)
dmm.write("CONF:FRES AUTO,DEF")
reading = float(dmm.query("READ?"))
print(f"4-wire resistance: {reading:.4f} Ohm")

DC Current

measure_current.py
dmm.write("CONF:CURR:DC AUTO,DEF")
reading = float(dmm.query("READ?"))
print(f"DC Current: {reading:.6f} A")

SCPI Command Reference

MeasurementCONF CommandRange Values
DC VoltageCONF:VOLT:DC {range},DEF0.1, 1, 10, 100, 1000 V
AC VoltageCONF:VOLT:AC {range},DEF0.1, 1, 10, 100, 750 V
2-wire ResistanceCONF:RES {range},DEF100, 1k, 10k, 100k, 1M, 10M, 100M Ohm
4-wire ResistanceCONF:FRES {range},DEFUse for less than 100 Ohm
DC CurrentCONF:CURR:DC {range},DEF0.0001, 0.001, 0.01, 0.1, 1, 3 A
AC CurrentCONF:CURR:AC {range},DEF0.001, 0.01, 0.1, 1, 3 A

Step 3: Tune Speed vs. Accuracy with NPLC

NPLC (Number of Power Line Cycles) controls integration time. Higher NPLC averages more noise but takes longer.

nplc_config.py
dmm.write("CONF:VOLT:DC 10,DEF")
dmm.write("VOLT:DC:NPLC 1")    # balanced (default)
dmm.write("VOLT:DC:NPLC 10")   # high accuracy, slower
dmm.write("VOLT:DC:NPLC 0.02") # fast, more noise
NPLCIntegration Time (60 Hz)Readings/secUse Case
0.02333 us~50Fast production screening
0.23.3 ms~15Good speed/noise balance
116.7 ms~5Standard accuracy
10167 ms~0.6High accuracy measurements
1001.67 s~0.1Calibration-grade

Manual range eliminates the auto-range delay (~20-50 ms) and is recommended for production test throughput.

Step 4: Build a Keysight DMM OpenHTF Plug

keysight_dmm_plug.py
import pyvisa
import openhtf as htf


class KeysightDMM(htf.plugs.BasePlug):
    """OpenHTF plug for Keysight 344xxA series DMMs."""

    RESOURCE = "USB0::0x0957::0x1A07::MY12345678::INSTR"
    NPLC = 1.0

    def setUp(self):
        self._rm = pyvisa.ResourceManager()
        self._dmm = self._rm.open_resource(self.RESOURCE)
        self._dmm.timeout = 5000
        self._dmm.write("*RST")
        self._dmm.write("*CLS")

    def tearDown(self):
        if self._dmm:
            self._dmm.close()
        self._rm.close()

    def measure_dc_voltage(self, range_v: float = None) -> float:
        range_str = str(range_v) if range_v else "AUTO"
        self._dmm.write(f"CONF:VOLT:DC {range_str},DEF")
        self._dmm.write(f"VOLT:DC:NPLC {self.NPLC}")
        return float(self._dmm.query("READ?"))

    def measure_ac_voltage(self, range_v: float = None) -> float:
        range_str = str(range_v) if range_v else "AUTO"
        self._dmm.write(f"CONF:VOLT:AC {range_str},DEF")
        return float(self._dmm.query("READ?"))

    def measure_resistance(self, four_wire: bool = False, range_ohm: float = None) -> float:
        cmd = "FRES" if four_wire else "RES"
        range_str = str(range_ohm) if range_ohm else "AUTO"
        self._dmm.write(f"CONF:{cmd} {range_str},DEF")
        self._dmm.write(f"{cmd}:NPLC {self.NPLC}")
        return float(self._dmm.query("READ?"))

    def measure_dc_current(self, range_a: float = None) -> float:
        range_str = str(range_a) if range_a else "AUTO"
        self._dmm.write(f"CONF:CURR:DC {range_str},DEF")
        self._dmm.write(f"CURR:DC:NPLC {self.NPLC}")
        return float(self._dmm.query("READ?"))

Step 5: Write Production Tests with TofuPilot

test_power_supply_board.py
import openhtf as htf
from openhtf.util import units
from tofupilot.openhtf import TofuPilot
from keysight_dmm_plug import KeysightDMM


@htf.plug(dmm=KeysightDMM)
@htf.measures(
    htf.Measurement("output_voltage_3v3")
    .in_range(minimum=3.267, maximum=3.333)
    .with_units(units.VOLT),
    htf.Measurement("output_voltage_5v0")
    .in_range(minimum=4.900, maximum=5.100)
    .with_units(units.VOLT),
)
def test_output_voltages(test, dmm):
    """Verify regulated output voltages are within tolerance."""
    test.measurements.output_voltage_3v3 = dmm.measure_dc_voltage(range_v=10)
    test.measurements.output_voltage_5v0 = dmm.measure_dc_voltage(range_v=10)


@htf.plug(dmm=KeysightDMM)
@htf.measures(
    htf.Measurement("sense_resistor_value")
    .in_range(minimum=0.099, maximum=0.101)
    .with_units(units.OHM),
)
def test_sense_resistor(test, dmm):
    """Verify current sense resistor with 4-wire measurement."""
    r = dmm.measure_resistance(four_wire=True, range_ohm=100)
    test.measurements.sense_resistor_value = r


@htf.plug(dmm=KeysightDMM)
@htf.measures(
    htf.Measurement("quiescent_current")
    .in_range(maximum=0.005)
    .with_units(units.AMPERE),
)
def test_quiescent_current(test, dmm):
    """Verify quiescent current does not exceed 5 mA."""
    test.measurements.quiescent_current = dmm.measure_dc_current(range_a=0.01)


@htf.plug(dmm=KeysightDMM)
@htf.measures(
    htf.Measurement("ac_ripple")
    .in_range(maximum=0.050)
    .with_units(units.VOLT),
)
def test_output_ripple(test, dmm):
    """Verify AC ripple on the 5 V output."""
    test.measurements.ac_ripple = dmm.measure_ac_voltage(range_v=1)


def main():
    test = htf.Test(
        test_output_voltages,
        test_sense_resistor,
        test_quiescent_current,
        test_output_ripple,
        test_name="Power Supply Board Functional Test",
    )

    with TofuPilot(test):
        test.execute(test_start=lambda: input("Enter DUT serial number: ").strip())


if __name__ == "__main__":
    main()

Step 6: Fast Multi-Measurement Mode

For high-throughput lines, use SAMP:COUN for burst sampling.

fast_sampling.py
import pyvisa
import numpy as np

rm = pyvisa.ResourceManager()
dmm = rm.open_resource("USB0::0x0957::0x1A07::MY12345678::INSTR")
dmm.timeout = 10000

dmm.write("*RST")
dmm.write("CONF:VOLT:DC 10,DEF")
dmm.write("VOLT:DC:NPLC 0.2")
dmm.write("SAMP:COUN 10")
dmm.write("TRIG:SOUR IMM")
dmm.write("TRIG:DEL 0")

dmm.write("INIT")
raw = dmm.query("FETC?")

readings = [float(x) for x in raw.split(",")]
print(f"Mean: {np.mean(readings):.6f} V")
print(f"Std:  {np.std(readings):.6f} V")

Troubleshooting

SymptomLikely CauseFix
VI_ERROR_RSRC_NFOUNDWrong resource string or driver not installedRun rm.list_resources(), install Keysight IO Libraries
+1,"Hardware error"Overload on input terminalsCheck probe connections, reduce input signal
Reading returns +9.9E+37Signal exceeds selected rangeUse auto-range or increase manual range
Readings drift over 30 secondsDMM not warmed upAllow 30-minute warm-up for less than 10 ppm accuracy
USB device not found on LinuxMissing udev rulesAdd SUBSYSTEM=="usb", ATTRS{idVendor}=="0957", MODE="0666"
HiSLIP connection refusedHiSLIP not enabledEnable via front panel: Utilities > I/O Config > LAN > HiSLIP
NPLC command returns errorWrong function prefixNPLC is per-function: VOLT:DC:NPLC, RES:NPLC
Slow throughput over LANVXI-11 overheadSwitch to HiSLIP for 10x faster transactions
4-wire reading matches 2-wireWrong terminalsConfirm SENSE HI/LO connected to separate terminals

More Guides

Put this guide into practice