Control a Rigol DP800 series power supply over USB or Ethernet using Python, wrap it in an OpenHTF plug, and log results to TofuPilot. This guide covers connection, multi-channel control, protection settings, and a full production test example.
Prerequisites
- Rigol DP800 series PSU (DP821, DP831, DP832, or DP832A)
- Python 3.8+
- USB-B cable or network connection to the PSU
pip install pyvisa pyvisa-py tofupilot openhtfStep 1: Connect to the Instrument
Rigol DP800 instruments expose a VISA interface over USB-TMC and TCP/IP.
| Connection | VISA address example |
|---|---|
| USB-TMC | USB0::0x1AB1::0x0E11::DP8C224200001::INSTR |
| Ethernet (VXI-11) | TCPIP0::192.168.1.50::inst0::INSTR |
| Ethernet (raw socket) | TCPIP0::192.168.1.50::5555::SOCKET |
import pyvisa
rm = pyvisa.ResourceManager()
psu = rm.open_resource("USB0::0x1AB1::0x0E11::DP8C224200001::INSTR")
psu.timeout = 5000
psu.write_termination = "
"
psu.read_termination = "
"
print(psu.query("*IDN?"))
# Rigol Technologies,DP832,DP8C224200001,00.01.14Step 2: Understand Rigol SCPI Syntax
Rigol DP800 SCPI differs from Keysight in a few important ways.
| Operation | Rigol DP800 | Keysight E36xx |
|---|---|---|
| Select channel | :INST CH1 | :INST:SEL OUT1 |
| Set voltage | :VOLT 5.0 | :VOLT 5.0 |
| Set current limit | :CURR 1.0 | :CURR 1.0 |
| Enable output | :OUTP CH1,ON | :OUTP ON |
| Read voltage | :MEAS:VOLT? CH1 | :MEAS:VOLT? |
| Read current | :MEAS:CURR? CH1 | :MEAS:CURR? |
| OVP enable | :OUTP:OVP CH1,ON | :VOLT:PROT:STAT ON |
| OVP level | :OUTP:OVP:VAL CH1,5.5 | :VOLT:PROT:LEV 5.5 |
Step 3: Control Voltage, Current, and Output
import pyvisa
rm = pyvisa.ResourceManager()
psu = rm.open_resource("USB0::0x1AB1::0x0E11::DP8C224200001::INSTR")
psu.timeout = 5000
def set_channel(psu, channel: int, voltage: float, current: float) -> None:
psu.write(f":INST CH{channel}")
psu.write(f":VOLT {voltage:.3f}")
psu.write(f":CURR {current:.3f}")
def enable_output(psu, channel: int) -> None:
psu.write(f":OUTP CH{channel},ON")
def disable_output(psu, channel: int) -> None:
psu.write(f":OUTP CH{channel},OFF")
def measure_voltage(psu, channel: int) -> float:
return float(psu.query(f":MEAS:VOLT? CH{channel}"))
def measure_current(psu, channel: int) -> float:
return float(psu.query(f":MEAS:CURR? CH{channel}"))
# Example: power a 3.3 V rail at up to 500 mA
set_channel(psu, 1, voltage=3.3, current=0.5)
enable_output(psu, 1)
vout = measure_voltage(psu, 1)
iout = measure_current(psu, 1)
print(f"CH1: {vout:.3f} V, {iout:.4f} A")Step 4: Multi-Channel Control (DP832)
The DP832 has three independent channels. Configure all channels before enabling any output.
import pyvisa
import time
rm = pyvisa.ResourceManager()
psu = rm.open_resource("USB0::0x1AB1::0x0E11::DP8C224200001::INSTR")
psu.timeout = 5000
CHANNELS = {
1: {"voltage": 5.0, "current": 2.0}, # Main 5 V rail
2: {"voltage": 3.3, "current": 1.0}, # Logic rail
3: {"voltage": 12.0, "current": 0.5}, # Analog rail
}
for ch, cfg in CHANNELS.items():
psu.write(f":INST CH{ch}")
psu.write(f":VOLT {cfg['voltage']:.3f}")
psu.write(f":CURR {cfg['current']:.3f}")
for ch in CHANNELS:
psu.write(f":OUTP CH{ch},ON")
time.sleep(0.1)
for ch in CHANNELS:
v = float(psu.query(f":MEAS:VOLT? CH{ch}"))
i = float(psu.query(f":MEAS:CURR? CH{ch}"))
print(f"CH{ch}: {v:.3f} V {i:.4f} A")Step 5: Configure OVP and OCP Protection
Always set protection before enabling output in a production environment.
def configure_protection(psu, channel: int, ovp_volts: float, ocp_amps: float) -> None:
psu.write(f":OUTP:OVP:VAL CH{channel},{ovp_volts:.3f}")
psu.write(f":OUTP:OVP CH{channel},ON")
psu.write(f":OUTP:OCP:VAL CH{channel},{ocp_amps:.3f}")
psu.write(f":OUTP:OCP CH{channel},ON")
# 5 V rail: trip at 5.5 V or 2.5 A
configure_protection(psu, 1, ovp_volts=5.5, ocp_amps=2.5)OVP and OCP trip levels should be set 10-15% above nominal to avoid nuisance trips while still catching faults quickly.
Step 6: Build an OpenHTF Plug
import time
import pyvisa
import openhtf as htf
class RigolDP800(htf.plugs.BasePlug):
"""OpenHTF plug for Rigol DP800 series power supplies."""
VISA_ADDRESS = "USB0::0x1AB1::0x0E11::DP8C224200001::INSTR"
def setUp(self):
self._rm = pyvisa.ResourceManager()
self._psu = self._rm.open_resource(self.VISA_ADDRESS)
self._psu.timeout = 5000
self._psu.write_termination = "
"
self._psu.read_termination = "
"
def tearDown(self):
for ch in (1, 2, 3):
try:
self._psu.write(f":OUTP CH{ch},OFF")
except Exception:
pass
if self._psu:
self._psu.close()
self._rm.close()
def configure(self, channel: int, voltage: float, current: float):
self._psu.write(f":INST CH{channel}")
self._psu.write(f":VOLT {voltage:.3f}")
self._psu.write(f":CURR {current:.3f}")
def set_protection(self, channel: int, ovp: float, ocp: float):
self._psu.write(f":OUTP:OVP:VAL CH{channel},{ovp:.3f}")
self._psu.write(f":OUTP:OVP CH{channel},ON")
self._psu.write(f":OUTP:OCP:VAL CH{channel},{ocp:.3f}")
self._psu.write(f":OUTP:OCP CH{channel},ON")
def enable(self, channel: int):
self._psu.write(f":OUTP CH{channel},ON")
def disable(self, channel: int):
self._psu.write(f":OUTP CH{channel},OFF")
def measure_voltage(self, channel: int) -> float:
return float(self._psu.query(f":MEAS:VOLT? CH{channel}"))
def measure_current(self, channel: int) -> float:
return float(self._psu.query(f":MEAS:CURR? CH{channel}"))Step 7: Write the Production Test with TofuPilot
import time
import openhtf as htf
from openhtf.util import units
from tofupilot.openhtf import TofuPilot
from plugs.rigol_dp800 import RigolDP800
@htf.plug(psu=RigolDP800)
def power_on_dut(test, psu):
"""Configure rails, enable protection, and power the DUT."""
psu.configure(channel=1, voltage=5.0, current=2.0)
psu.set_protection(channel=1, ovp=5.5, ocp=2.5)
psu.configure(channel=2, voltage=3.3, current=1.0)
psu.set_protection(channel=2, ovp=3.7, ocp=1.2)
psu.enable(channel=1)
psu.enable(channel=2)
time.sleep(0.5)
@htf.plug(psu=RigolDP800)
@htf.measures(
htf.Measurement("ch1_voltage")
.in_range(minimum=4.85, maximum=5.15)
.with_units(units.VOLT),
htf.Measurement("ch1_current")
.in_range(maximum=2.0)
.with_units(units.AMPERE),
htf.Measurement("ch2_voltage")
.in_range(minimum=3.20, maximum=3.40)
.with_units(units.VOLT),
htf.Measurement("ch2_current")
.in_range(maximum=1.0)
.with_units(units.AMPERE),
)
def measure_power_rails(test, psu):
"""Read and validate all supply rails under load."""
test.measurements.ch1_voltage = psu.measure_voltage(channel=1)
test.measurements.ch1_current = psu.measure_current(channel=1)
test.measurements.ch2_voltage = psu.measure_voltage(channel=2)
test.measurements.ch2_current = psu.measure_current(channel=2)
@htf.plug(psu=RigolDP800)
def power_off_dut(test, psu):
"""Disable all outputs after test."""
psu.disable(channel=1)
psu.disable(channel=2)
def main():
test = htf.Test(
power_on_dut,
measure_power_rails,
power_off_dut,
test_name="Power Supply Validation",
)
with TofuPilot(test):
test.execute(test_start=lambda: input("Enter serial number: ").strip())
if __name__ == "__main__":
main()Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Instrument not found on USB | Missing udev rules (Linux) | Add SUBSYSTEM=="usb", ATTR{idVendor}=="1ab1", MODE="0666" to udev |
VI_ERROR_TMO on query | read_termination not set | Set `psu.read_termination = " |
| "` | ||
| Output trips OCP immediately | Capacitive inrush exceeds limit | Raise OCP limit 20% above steady-state or ramp voltage |
| Measurements read 0 V | Channel in CC mode, load too heavy | Increase current limit or reduce load |
| Ethernet connection drops | Idle VXI-11 timeout (~60s) | Re-open resource or send periodic *IDN? keep-alive |
| Command returns no data | Used query() for set command | Use write() for commands that don't return data |