Skip to content
Instrument Control

Automate a Rigol Power Supply

Control a Rigol DP800 series power supply from Python using PyVISA, with multi-channel control, OVP/OCP protection, and TofuPilot integration via OpenHTF.

JJulien Buteau
beginner10 min readMarch 14, 2026

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
terminal
pip install pyvisa pyvisa-py tofupilot openhtf

Step 1: Connect to the Instrument

Rigol DP800 instruments expose a VISA interface over USB-TMC and TCP/IP.

ConnectionVISA address example
USB-TMCUSB0::0x1AB1::0x0E11::DP8C224200001::INSTR
Ethernet (VXI-11)TCPIP0::192.168.1.50::inst0::INSTR
Ethernet (raw socket)TCPIP0::192.168.1.50::5555::SOCKET
connect_rigol.py
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.14

Step 2: Understand Rigol SCPI Syntax

Rigol DP800 SCPI differs from Keysight in a few important ways.

OperationRigol DP800Keysight 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

rigol_basic_control.py
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.

rigol_multichannel.py
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.

rigol_protection.py
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

plugs/rigol_dp800.py
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

test_power_supply.py
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

SymptomLikely CauseFix
Instrument not found on USBMissing udev rules (Linux)Add SUBSYSTEM=="usb", ATTR{idVendor}=="1ab1", MODE="0666" to udev
VI_ERROR_TMO on queryread_termination not setSet `psu.read_termination = "
"`
Output trips OCP immediatelyCapacitive inrush exceeds limitRaise OCP limit 20% above steady-state or ramp voltage
Measurements read 0 VChannel in CC mode, load too heavyIncrease current limit or reduce load
Ethernet connection dropsIdle VXI-11 timeout (~60s)Re-open resource or send periodic *IDN? keep-alive
Command returns no dataUsed query() for set commandUse write() for commands that don't return data

More Guides

Put this guide into practice