Skip to Content

← SCPI Power Supply Control with Raspberry PLC in Python

Fuse test benchRaspberry PLC 19RSCPISerie USBControl

SCPI Power Supply Control with Raspberry PLC in Python — full example

Control a Sorensen XG programmable power supply over SCPI from a Raspberry PLC: serial setup, SOUR:CURR and SOUR:VOLT commands, full Python example.

Complete, runnable program for the Raspberry PLC 19R (sorensen-scpi-power-supply-control.py): wiring header, requirements and integration notes included.

Download the full project pack — freeThis example + the related ones + bill of materials

Read-only preview.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
COMPLETE EXAMPLE — Sorensen XG programmable power supply control over SCPI

Equipment: Raspberry PLC 19R (Industrial Shields)
Based on:  fuse test bench project

Wiring:
  - Sorensen XG (Ametek) supply, 0-5 V / 0-220 A, connected over USB
    to the Raspberry PLC port -> shows up as /dev/ttyUSB0
  - Serial link: 9600 baud, 8N1

Requirements:
  pip3 install pyserial

Integration with the catalog:
  - The current setpoint is fine-tuned by the closed-loop calibration
    module (closed-loop-current-calibration.py).
  - The actual current is measured with the ADS1015 over I2C
    (ads1015-differential-adc-autogain.py).
"""

import time
import serial


class SorensenXG:
    """Minimal SCPI driver for the Sorensen XG supply over a serial port."""

    def __init__(self, port="/dev/ttyUSB0", address=1, timeout=1.0):
        self.address = address
        self.ser = serial.Serial(
            port=port,
            baudrate=9600,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            timeout=timeout,
        )
        # Select the instrument on the bus (several supplies may share it)
        self.write("*ADR %d" % self.address)

    def write(self, command):
        """Send a SCPI command terminated with \\r and let the supply breathe."""
        self.ser.write((command + "\r").encode("ascii"))
        time.sleep(0.05)  # the XG needs a small margin between commands

    def query(self, command):
        """Send a query command and return the response as text."""
        self.ser.reset_input_buffer()
        self.write(command)
        return self.ser.readline().decode("ascii", errors="ignore").strip()

    def identify(self):
        return self.query("*IDN?")

    def set_current(self, amps):
        """Current setpoint (A). The bench XG goes up to 220 A."""
        self.write("SOUR:CURR %.2f" % amps)

    def set_voltage(self, volts):
        """Voltage setpoint (V). In fuse testing it is limited to 5 V."""
        self.write("SOUR:VOLT %.2f" % volts)

    def output(self, on):
        """Enable or cut the power output."""
        self.write("OUTP %s" % ("ON" if on else "OFF"))

    def read_current(self):
        """Current measured by the supply itself (SCPI telemetry)."""
        try:
            return float(self.query("MEAS:CURR?"))
        except ValueError:
            return 0.0

    def close(self):
        self.output(False)
        self.ser.close()


def current_ramp(supply, target_a, step_a=5.0, wait_s=0.5):
    """Raise the setpoint in steps to avoid stressing shunt and wiring."""
    setpoint = 0.0
    while setpoint < target_a:
        setpoint = min(setpoint + step_a, target_a)
        supply.set_current(setpoint)
        print("  setpoint %.1f A -> supply measures %.2f A"
              % (setpoint, supply.read_current()))
        time.sleep(wait_s)


def main():
    supply = SorensenXG(port="/dev/ttyUSB0", address=1)
    print("Supply detected:", supply.identify())

    try:
        # Typical configuration of a non-fusing test:
        # voltage limited to 5 V and current as the controlled quantity.
        supply.set_voltage(5.0)
        supply.set_current(0.0)
        supply.output(True)

        print("Ramping up to 50 A...")
        current_ramp(supply, target_a=50.0)

        print("Holding 50 A for 10 s (the actual test would run here)...")
        for _ in range(10):
            print("  I supply = %.2f A" % supply.read_current())
            time.sleep(1.0)

    finally:
        # No matter what happens, the output is cut on exit
        print("Cutting output and closing port.")
        supply.close()


if __name__ == "__main__":
    main()
Download the full project pack — freeThis example + the related ones + bill of materials