Get our free email newsletter

Laboratory Automation with PyVISA

Applying Python and PvVISA to Automated Testing

Python has become a widely used programming language in the area of electronic test automation, especially when used with the PyVISA library. While the fundamental principles of lab automation have been around for a long time (i.e., the SCPI protocol), Python and PyVISA have made it easy to get started quickly with test automation. Once data has been collected, Python also has a plethora of data analysis tools (pandas, scipy, scikit, etc.) that are useful in analyzing data.

In this article, I will introduce how to interface with instruments using Python/PyVISA and give a practical example of measuring power supply efficiency. Finally, I will introduce how to plot gathered efficiency data directly in Python.

SCPI Protocol

The Standard Commands for Programmable Instruments (SCPI) is a definition layer on top of the IEEE 488.2-1987 standard for instrument communication. While SCPI was originally meant for IEEE 488.1 (GPIB connections), this has expanded to include RS-232, Ethernet, USB, and several others. SCPI commands are sent in ASCII format and received as a string of ASCII text. Here is an example of a simple SCPI transaction:

- Partner Content -

Shielding Effectiveness Test Guide

Just as interference testing requires RF enclosures, isolation systems in turn need their own testing. This document reviews some of the issues and considerations in testing RF enclosures.

Host query: *IDN?

Device reply: Siglent Technologies,SDL1020X‑E,SDLxxxxxxxxxxx,1.1.1.21R2\n

SCPI defines a number of generic commands like MEASure and CONFigure, which can be used to read data from or configure parameters on test equipment.

VISA Specification

Unfortunately for the SCPI standard, different operating systems, interfaces, and devices meant that the early days of SCPI required different libraries for each device and bus system. In order to alleviate this pain, the Virtual Instrument Software Architecture (VISA) specification was created to seamlessly work with all devices and bus systems.

Python and PyVISA

Even with the VISA specification in place, it has traditionally been challenging to interface a host computer to measurement devices without expensive/cumbersome software and hardware. With these drawbacks in mind, the PyVISA library was created to simplify instrument communication and make lab automation more efficient.

- From Our Sponsors -

Python itself is a free, interpreted programming language that can be used with any modern operating system. Since this is an interpreted (and not compiled) language, Python can generally be “installed” on any system, even where the user does not have admin/root access. While the syntax of Python can take some getting used to (spaces are used as delimiters instead of ; or other characters), it is a very widely used language with many libraries, examples, and code snippets available.

PyVISA works as a front end to the VISA library and simplifies the process of communicating with instruments. PyVISA is officially tested against National Instruments’ VISA and Keysight IO Library Suite and can be used with hardware adapters from National Instruments, Keysight, and many others.

To get started, here is a simple program that queries what instruments are visible to PyVISA on my computer. In the below code snippet, I am using National Instruments VISA on a 64-bit Windows computer running Python 3.11.5 and PyVISA 1.13.0

In [2]:

import pyvisa
instruments = pyvisa.ResourceManager().list_resources()
instruments

Out[2]:

(‘USB0::0xF4EC::0x1621::SDL13GCQ6R0772::INSTR’,
‘USB0::0x2A8D::0x3402::MY61003767::INSTR’,
‘GPIB0::12::INSTR’,
‘GPIB0::22::INSTR’)

This output shows there are four instruments connected to my computer, two connected by USB and two by GPIB. Now we can create an object for each instrument and query what it is. Note that all instruments will reply to the special *IDN? Command:

In [5]:

for i in instruments:
    inst = pyvisa.ResourceManager().open_resource(i)
    print(i,inst.query(‘*IDN?’))

USB0::0xF4EC::0x1621::SDL13GCQ6R0772::INSTR Siglent Technologies,SDL1020X-E,SDL13GCQ6R0772,1.1.1.21R2

USB0::0x2A8D::0x3402::MY61003767::INSTR Keysight Technologies,E36234A,MY61003767,1.0.4-1.0.3-1.00

GPIB0::12::INSTR HEWLETT-PACKARD,34401A,0,7-5-2

GPIB0::22::INSTR HEWLETT-PACKARD,34401A,0,11-5-2

From the query, you can see I have a Keysight power supply (E35234A) and a Siglent power supply (SDL1020X-E). For the following example, I am using the Siglent power supply only to read the input and output voltages of the device under test (DUT).

Measuring Efficiency

For a power supply, efficiency is the measure of how much power you get out per unit of power put in. Since P = VI, this can be written as:

As this equation gives a fraction less than 1, it is customary to multiply by 100 and express efficiency as a percentage.

Power Supply Setup

For the following test, I am using a 720 W adjustable DC-DC power supply from DROK (shown in Figure 1). For the purpose of this example efficiency test, I am using a constant input voltage of 25 V with a fixed output of 12 V. Note there is a large fan near the North side of the board which turns on when the power supply is under heavy load. We will see the effects of this fan in the full efficiency characteristic.

Figure 1
Figure 1: Drok 720 W Adjustable DC Power Supply
Figure 2
Figure 2: Practical instrumentation of power supply for efficiency measurements

Practical Efficiency Measurements¶

In order to measure the efficiency of a DC-DC power supply, we must apply a source voltage vs and a load current iLOAD. The voltage source is a DC voltage and the load current is an electronic load running in the constant current mode. We step up the load current and measure the efficiency at various load points to create a full plot showing the efficiency characteristic of the power supply.

Since each measurement requires four values (input voltage, input current, output voltage, and output current), we need sufficient equipment to read all these parameters. In practice, it is beneficial to measure the input and output voltage on separate meters as close to the DUT as possible.

For current measurements, reading the current directly from the voltage source (for input current) and the electronic load (for output current) are generally close enough when using modern, calibrated equipment.

Instrument Objects for Efficient Data Collection

We can use the tools available in Python to create an object for each piece of equipment and create a standard list of methods that our instruments will use. As an example, we can make a read_v() method for all of our instrument objects to read the voltage value. At the top level, we just see the method instrument.read_v(), but this actually maps to the specific SCPI commands for our instrument and returns data that Python can read.

For this test, we will need four instrument objects, though the voltage measurements will be instances of the same object with different addresses (same meter, different GPIB address).

In [6]:

#Basic object for Keysight E36200 series power supply
#Note that channel must be specified
class keysight_E36200(object):

    def __init__(self, visa_address, **kwargs):
        self.pyvisa = pyvisa.ResourceManager().open_resource(visa_address)
        #
        #Setup some things
        #
        self.__channel = int(kwargs[‘channel’])

        #check if this is the correct device

    def set_v(self, voltage):
        self.pyvisa.write(‘VOLT {0:G}, (@{1})’.format(voltage,self.__channel))

    def set_i(self, current):
        self.pyvisa.write(‘CURR {0:G}, (@{1})’.format(current,self.__channel))

    def read_v(self):
        return float( self.pyvisa.query(‘MEAS:VOLT? (@{0})’.format(self.__channel)) )

    def read_i(self):
        return float( self.pyvisa.query(‘MEAS:CURR? (@{0})’.format(self.__channel)) )

    def output_enable(self):
        self.pyvisa.write(‘OUTP 1, (@{0})’.format(self.__channel))

    def output_disable(self):
        self.pyvisa.write(‘OUTP 0, (@{0})’.format(self.__channel))

In [7]:

class SDL1000X(object):

    def __init__(self, visa_address):
        self.pyvisa = pyvisa.ResourceManager().open_resource(visa_address)
        #
        #Setup some things
        #    

    def read_v(self):
        return float(self.pyvisa.query(‘MEASure:VOLTage:DC?’))

    def read_i(self):
        return float(self.pyvisa.query(‘MEASure:CURRent:DC?’))

    def set_i(self, current):
        self.pyvisa.write(‘:SOURce:CURRent:LEVel:IMMediate {0:f}’.format(current))

    def output_enable(self):
        self.pyvisa.write(‘:SOURce:INPut:STATe ON’)

    def output_disable(self):
        self.pyvisa.write(‘:SOURce:INPut:STATe OFF’)

In [8]:

class hp34401(object):

    def __init__(self, visa_address):
        self.pyvisa = pyvisa.ResourceManager().open_resource(visa_address)

    def read_v(self, average=1):
        #start from v=0, add values and average as needed
        v = 0.0
        self.pyvisa.write(“CONFigure:VOLTage:DC”)

        for x in range(average):
            v += float(self.pyvisa.query(“READ?”))

        voltage = v / average

        return voltage

We also need to import a few libraries which will be helpful for this test:

In [9]:

import time
import numpy as np
import pandas as pd

Then, create an object for each instrument with a descriptive name:

In [10]:

inst_load = SDL1000X(‘USB0::0xF4EC::0x1621::SDL13GCQ6R0772::INSTR’)
inst_supply = keysight_E36200(‘USB0::0x2A8D::0x3402::MY61003767::INSTR’,channel=1)
inst_vin_sense = hp34401(‘GPIB0::12::INSTR’)
inst_vo_sense = hp34401(‘GPIB0::22::INSTR’)

We now have objects for the four instruments we are using to measure efficiency, and each instrument has high-level methods with descriptive names. Note again that the actual SCPI commands sent to each object are very different, but the intended data (like measuring current) returns the appropriate data for Python.

Calibrate Input Voltage

The wire connecting from the power supply to the DUT has a finite impedance, and when the input current increases (due to increasing load current), the input voltage seen at the input of the DUT will decrease. In order to compensate for this effect, we can directly measure the voltage right at the DUT and increase/decrease the supply voltage to stay within a certain bound (in this case, 10mV).

In [11]:

def calibrate_vin(supply, sense, v_target):

    v_meas = sense.read_v()
    v_diff = v_meas – v_target

    #Keep input to within 10mV
    while abs(v_diff) > 0.01:
        supply.set_v(supply.read_v() – v_diff/1.5)
        time.sleep(1)        

        v_meas = sense.read_v()

        v_diff = v_meas – v_target

Cooldown¶

When using the calibration function above and at very high load currents, it is possible that the input voltage will be so high that it could electrically overstress (EOS) the device we are testing. To avoid this, we can create a simple “cooldown” loop that decreases the load and lowers the supply voltage slowly down to a safe voltage:

In [12]:

def cooldown(v_final,steps):
    #Read current and voltage right now
    v_now = inst_supply.read_v()
    i_now = inst_load.read_i()

    #Determine step size
    v_step = (v_now – v_final)/steps
    i_step = i_now/steps

    #Reduce by step sizes
    i_now -= i_step
    v_now -= v_step

    for s in range(steps):
        inst_supply.set_v(v_now)
        inst_load.set_i(i_now)
        i_now -= i_step
        v_now -= v_step
        time.sleep(1)
    #Disable when current is 0 and voltage is at target
    inst_load.output_disable()
    inst_supply.output_disable()

Initial Setup¶

Next, we need to set up the loads point we will use for our test and set the remaining parameters. All test data will be stored in a Pandas DataFrame object which will be useful for plotting and exporting to .csv later on.

In [13]:

load_currents = np.linspace(0,5,51) #100mA steps
#load_currents = np.logspace(-2,0.6989700043360189,100)

supply_voltage = 25

inst_supply.set_v(supply_voltage)
inst_supply.output_enable()
time.sleep(1)

inst_load.set_i(0)
inst_load.output_enable()

input(“Press ENTER when ready\n”)

row_counter = 0

column_labels = [‘Vin_set’,’Iload_set’,’Vin’,’Iin’,’Vout’,’Iout’,’Efficiency’]
all_data = pd.DataFrame(columns=column_labels)

Loop Through Currents¶

The main loop works as follows:

  1. Set the next load current value on the electronic load;
  2. Wait for the current to stabilize;
  3. Calibrate the input voltage (right at the DUT) to keep this close to the supply voltage value;
  4. Read all meters and calculate efficiency;
  5. Store all read data as a new row in the all_ data DataFrame; and
  6. Increment the row counter and continue to the next load value.

In [14]:

for lc in load_currents:
    inst_load.set_i(lc)
    time.sleep(1)
    calibrate_vin(inst_supply, inst_vin_sense, supply_voltage)

    data = [supply_voltage,
    lc,
    inst_vin_sense.read_v(),
    inst_supply.read_i(),
    inst_vo_sense.read_v(),
    inst_load.read_i()]

    efficiency = (100* data[4] * data[5]) / (data[2] * data[3])
    data.append(efficiency)

    #print(*data, sep=”,”)
    all_data.loc[row_counter] = data

    row_counter += 1

Cooldown and Save Data¶

Once the loop is complete, cooldown in 10 steps and save the  all_data DataFrame to a .csv file (with a timestamp that guarantees all data files are unique):

In [15]:

cooldown(supply_voltage,10)

timestamp = time.strftime(“%Y.%m.%d.%H.%M.%S”)

all_data.to_csv(‘.\\data\\Efficiency_’+timestamp+’.csv’)

Plot data inline¶

Since the entire all_data DataFrame still exists in memory, we can easily plot this using matplotlib:

In [22]:

%matplotlib inline

import matplotlib.pyplot as plt

In [31]:

all_data.plot(x=’Iout’,y=’Efficiency’)
plt.title(‘Efficiency vs. Load Current’)
plt.xlabel(‘Load Current (A)’)
plt.ylabel(‘Efficiency (%)’)
plt.show()

Figure 3
Figure 3: Python plot of efficiency vs. load current

Note that there is a large dip in efficiency when the load current is near 2 A. This is the point where the fan on the DC-DC converter turns on and causes a noticeable kink in the efficiency characteristic.

Plot With Log X Axis¶

Similarly, we can change the X axis to logarithmic scale, which tends to show a smooth curve when plotting efficiency:

In [37]:

fig, axs = plt.subplots(1)
all_data.plot(ax=axs,x=’Iout’,y=’Efficiency’)
plt.title(‘Efficiency vs. Load Current’)
plt.xlabel(‘Load Current (A)’)
axs.set_xscale(‘log’)
plt.ylabel(‘Efficiency (%)’)
plt.show()

Figure 4
Figure 4: Plot of efficiency vs. load current with a logarithmic X axis

Summary

Using Python and the PyVISA library, we have created instrument objects and a simple program to tabulate the efficiency of a DC power supply. We have also used the plotting tools in Python to create graphs of efficiency for this test.

With the basic program in place, it is possible to modify this program to add features, including:

  • Loop through different input voltages;
  • Change the tested currents;
  • Save plot data as image or pdf files; and
  • Save data as a Word document or PowerPoint slides.

Related Articles

Digital Sponsors

Become a Sponsor

Discover new products, review technical whitepapers, read the latest compliance news, and check out trending engineering news.

Get our email updates

What's New

- From Our Sponsors -

Sign up for the In Compliance Email Newsletter

Discover new products, review technical whitepapers, read the latest compliance news, and trending engineering news.