OT: I/V curve tracing made easy with Python and PyVISA

magnustoelle
 

Good Day to the group,

this is completely off-topic, but as several of you seemed always interested in curve-tracing applications, let me share my excitement with you...

I know that I am very late into this, but allow me to say that I have found the use of the Python scripting language in combination with libraries & packages such as PyVISA very powerful.
To cut a long story short: I was curious to see, if I can replace my NI LabView projects with something simpler, less costly and more portable between Operating Systems such as Windows, Linux etc. And after playing with Python for a few days, it seems that Python offers such an alternative.
I still like how easy it is to generate appealing GUIs with LabView, but even when using hierarchical designs, LabView "code" can be hard to read and comprehend - just try to decipher your own VIs after a few months!

I have used PyVISA https://pyvisa.readthedocs.io/en/master/ for easy communication with T&M equipment and libraries such as mathlibplot https://matplotlib.org/ for plotting and numpy https://www.numpy.org/ for a few math operations. The documentation and tools come for free, and there are tons and tons of on-line training courses on Python as well as code examples etc. which are available at 0 costs.

Curve-tracing application:
I have used a GRUNDIG/digimess PN300 programmable power supply & a common NI USB-GPIB adapter; then created some simple Python-scripts which allow the I/V-curve tracing of two-terminal devices such as diodes and of three-terminal devices such as transistors. For the latter, output A from the power supply provided the Collector-Emitter sweep voltage, and output B and a 22kOhm resistor provided the base current sweep.

I have never really had any formal education in programming/coding, but I have found it fairly easy to create the attached PNG diagram, to save the current and voltage data to a local file for post-processing with MS Excel etc.

While this is nothing to "write home about" - my heartful recommendation is: If you have the interest and leisure, give Python and a stab into PyVISA a try!

I am including my simple Python code below . I am sure there are many things which could be improved - however,  please let's do not discuss here...

Cheers,

Magnus

P.S. I am aware that the GRUNDIG PN300 power supply is not a precision instrument, but I have chosen it for simplicity - upgrading to precision DMMs and more precise current/voltage sources and the like would be easy.

# Setup Digimess/GRUNDIG PN300 power supply
# Testing three-terminal electronic devices such as transistors
# Set-up for independent operation, Voltage and Currents for output A and readback Current measurements
# Set-up Voltage and Currents for output B and readback Current measurements and turn off outputs
# Hardware setup: Connect emitter to common GND, collector to pos. terminal output A, connect base to output B using a 22k series resistor for base currents.
# Magnus Tölle, last edit: 13th May 2019
# Setup: Easyinstall from https://pypi.org/project/setuptools/0.6c11/#files first, and "easyinstall pip" second
# Install Python, pip, matplotlib, numpy, pandas, seaborn and NI VISA from ni.com
# References: https://github.com/demisjohn/Keithley-I-V-Sweep/blob/master/Keithley%20I-V%20Sweep%20v2.py


# Import libraries for plotting
import matplotlib.pyplot as plt
import numpy as np
# import pandas as pd
# import seaborn as sns

# Import libraries for VISA control
import visa

# Optional logging of PyVISA
# visa.log_to_screen()

# Import other libraries
import time # as to allow pause between measurements
import csv # as to allow saving data file

# For UTF Coding to avoid encoding issues:
# -*- coding: utf-8 -*-

# Program runs on Windows only:
import sys
import os # Filesystem manipulation

# Disable print
def blockPrint():
    sys.stdout = open(os.devnull, 'w')

# Restore print
def enablePrint():
    sys.stdout = sys.__stdout__

def windows_interaction():
    assert ('win' in sys.platform), "This program only runs on Windows systems."
    try: windows_interaction()
    except AssertionError as error:
        print(error)
        print('This script was not executed')

# open resource Digimess/GRUNDIG PN300 power supply
rm = visa.ResourceManager()
# rm.list_resources()
res = rm.list_resources()
print ("Please connect and power-up GPIB adapter and PN300 power supply")
print ("Please configure PN300 for GPIB and note address - default is address 7")
PN300_GPIB_address = input("Please enter GPIB address:")
print ("Please connect DUT as follows:")
print ("Emitter to common GND, collector to pos. terminal output A, connect base to output B using a 22k series resistor")

print("Found the following equipment / resources:")
print(res)
    # print("Opening " + res[-1])

PN300 = rm.get_instrument ('GPIB::' + str(PN300_GPIB_address))

 # Query to confirm that Digimess/GRUNDIG PN300 is present
print (PN300.query('*IDN?'))

 # Initialize and setup Digimess/GRUNDIG PN300 power supply for constant voltage, independent operation
PN300.write('*RST')
PN300.write('OPER_IND')
PN300.write('CONT_CV')

# Ask user for voltage setup, current limits for output A
VstartA = input("Enter min. voltage sweep setting for output A in Volt: ")
VstopA = input("Enter max. voltage sweep setting for output A in Volt: ")
VstepA = input("Enter number of sweep steps for output A: ")

VoltsA = np.linspace(int(VstartA),int(VstopA),(int(VstepA)+1))

Current_setA = input("Enter current limit for output A in Ampere: ")
Current_setA = 'ISET ' + Current_setA

# Ask user for voltage setup, current limits for output B
VstartB = input("Enter min. current sweep setting for output B in µA: ")
VstopB = input("Enter max. current sweep setting for output B in µA (max. 1300µA): ")
VstepB = input("Enter number of sweep steps for output B: ")

Current_setB = input("Enter current limit for output B in Ampere: ")
Current_setB = 'ISET ' + Current_setB

# Ask user for base current resistor value
Resistor = input("Enter measured series resistor value in kOhm: ")
Resistor = float(Resistor)*1E3

VoltsB = np.linspace(float(float(VstartB)*1E-6*Resistor),float(float(VstopB)*1E-6*Resistor),int(VstepB)+1)


# Ask user for DUT name
DUTname = input("Enter name of the device under test or DUT: ")

# Set timeout to 10 seconds
PN300.timeout = 10000
# Enable outputs, and set output A and B to 0V, inform user that measurements start now
PN300.write('SEL_A')
PN300.write('VSET 0.00')
PN300.write(Current_setA)

PN300.write('SEL_B')
PN300.write('VSET 0.00')
PN300.write(Current_setB)
PN300.write('OUT_ON')
print("Starting with measurements now")

VoltageDataA = []
CurrentDataA = []
CurrentDataB = []

for Voltage2 in VoltsB:
    PN300.write('SEL_B')
    PN300.write('VSET '+(str(Voltage2)))
    Voltage2A = PN300.query('VOUT?')
    Voltage_formatted2A = float(Voltage2A[2:7])
    CurrentDataB.append(Voltage_formatted2A/Resistor)

    for Voltage1 in VoltsA:
        PN300.write('SEL_A')
        PN300.write('VSET '+(str(Voltage1)))
        Voltage1A = PN300.query('VOUT?')
        Voltage_formatted1A = float(Voltage1A[2:7])
        VoltageDataA.append(Voltage_formatted1A)
        # time.sleep(0.1) optional delay time of 0.1 seconds
        Current1A = PN300.query('IOUT?')
        Current_formatted1A = float(Current1A[2:7])
        CurrentDataA.append(Current_formatted1A)
        print ("Emitter voltage and current: ", Voltage1A, Current1A)
        print ("Base current in µA: ", (Voltage_formatted2A/Resistor*1E6))

DataArrayA = np.asarray((VoltageDataA, CurrentDataA, CurrentDataB))


 # Set output A to 0V and turn outputs off
PN300.write('OUT_OFF')
Voltage_setA = 'VSET 0.00'
PN300.write(Voltage_setA)

# Notify user
print("Finished measurement and outputs turned off")

plt.style.use('seaborn-whitegrid')

for IndexA in range(0, int(VstepA)+1):
    IndexB = IndexA+1
    IndexC = IndexA+2
plt.plot(VoltageDataA[(IndexA*int(VstepA)+IndexA):(IndexC*int(VstepA)+IndexC-1)], CurrentDataA[(IndexA*int(VstepA)+IndexA):(IndexC*int(VstepA)+IndexC-1)], linestyle='--', marker='x', markersize=8, color='g')
plt.xlabel('Voltage in Volt')
plt.ylabel('Current in Ampere')
plt.legend([DUTname + ' I/V curve'], loc='best', shadow=True)
plt.show()

# Save the Data to a csv-file with the DUT name
# with appended rows for VoltageA, CurrentA, CurrentB data
NewFile = open(DUTname+'.csv', 'w')

with open(DUTname+'.csv', 'w', newline='') as fp:
    wr = csv.writer(fp, dialect='excel')
    wr.writerow(VoltageDataA)

with open(DUTname+'.csv', 'a', newline='') as fp:
    wr = csv.writer(fp, dialect='excel')
    wr.writerow(CurrentDataA)

with open(DUTname+'.csv', 'a', newline='') as fp:
    wr = csv.writer(fp, dialect='excel')
    wr.writerow(CurrentDataB)
# Close the file - end of python script
NewFile.close()

Join TekScopes@groups.io to automatically receive all group messages.