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 pyvisarm = pyvisa.ResourceManager()psu = rm.open_resource("USB0::0x1AB1::0x0E11::DP8C224200001::INSTR")psu.timeout = 5000psu.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 pyvisarm = pyvisa.ResourceManager()psu = rm.open_resource("USB0::0x1AB1::0x0E11::DP8C224200001::INSTR")psu.timeout = 5000def 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 mAset_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 pyvisaimport timerm = pyvisa.ResourceManager()psu = rm.open_resource("USB0::0x1AB1::0x0E11::DP8C224200001::INSTR")psu.timeout = 5000CHANNELS = { 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 Aconfigure_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 timeimport pyvisaimport openhtf as htfclass 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 timeimport openhtf as htffrom openhtf.util import unitsfrom tofupilot.openhtf import TofuPilotfrom 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 |