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

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

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

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

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

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