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)
pip install pyvisa pyvisa-py tofupilot openhtfStep 1: Connect to the Instrument
Keysight DMMs support two primary interfaces: USB-TMC and Ethernet (LXI).
| Interface | VISA Resource String | Use Case |
|---|---|---|
| USB-TMC | USB0::0x0957::0x1A07::MY12345678::INSTR | Single bench, direct PC connection |
| Ethernet (VXI-11) | TCPIP0::192.168.1.100::inst0::INSTR | Networked test systems |
| Ethernet (HiSLIP) | TCPIP0::192.168.1.100::hislip0::INSTR | Higher throughput, recommended for 34465A |
Find your instrument's resource string:
import pyvisa
rm = pyvisa.ResourceManager()
print(rm.list_resources())Open a connection and verify identity:
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-01Step 2: Configure SCPI Measurements
Always reset to a known state before configuring.
dmm.write("*RST")
dmm.write("*CLS")
print(dmm.query("SYST:ERR?"))
# +0,"No error"DC Voltage
# 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
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)
# 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
dmm.write("CONF:CURR:DC AUTO,DEF")
reading = float(dmm.query("READ?"))
print(f"DC Current: {reading:.6f} A")SCPI Command Reference
| Measurement | CONF Command | Range Values |
|---|---|---|
| DC Voltage | CONF:VOLT:DC {range},DEF | 0.1, 1, 10, 100, 1000 V |
| AC Voltage | CONF:VOLT:AC {range},DEF | 0.1, 1, 10, 100, 750 V |
| 2-wire Resistance | CONF:RES {range},DEF | 100, 1k, 10k, 100k, 1M, 10M, 100M Ohm |
| 4-wire Resistance | CONF:FRES {range},DEF | Use for less than 100 Ohm |
| DC Current | CONF:CURR:DC {range},DEF | 0.0001, 0.001, 0.01, 0.1, 1, 3 A |
| AC Current | CONF:CURR:AC {range},DEF | 0.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.
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| NPLC | Integration Time (60 Hz) | Readings/sec | Use Case |
|---|---|---|---|
| 0.02 | 333 us | ~50 | Fast production screening |
| 0.2 | 3.3 ms | ~15 | Good speed/noise balance |
| 1 | 16.7 ms | ~5 | Standard accuracy |
| 10 | 167 ms | ~0.6 | High accuracy measurements |
| 100 | 1.67 s | ~0.1 | Calibration-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
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
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.
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
| Symptom | Likely Cause | Fix |
|---|---|---|
VI_ERROR_RSRC_NFOUND | Wrong resource string or driver not installed | Run rm.list_resources(), install Keysight IO Libraries |
+1,"Hardware error" | Overload on input terminals | Check probe connections, reduce input signal |
Reading returns +9.9E+37 | Signal exceeds selected range | Use auto-range or increase manual range |
| Readings drift over 30 seconds | DMM not warmed up | Allow 30-minute warm-up for less than 10 ppm accuracy |
| USB device not found on Linux | Missing udev rules | Add SUBSYSTEM=="usb", ATTRS{idVendor}=="0957", MODE="0666" |
| HiSLIP connection refused | HiSLIP not enabled | Enable via front panel: Utilities > I/O Config > LAN > HiSLIP |
NPLC command returns error | Wrong function prefix | NPLC is per-function: VOLT:DC:NPLC, RES:NPLC |
| Slow throughput over LAN | VXI-11 overhead | Switch to HiSLIP for 10x faster transactions |
| 4-wire reading matches 2-wire | Wrong terminals | Confirm SENSE HI/LO connected to separate terminals |