IV Curve Measurement System

Order Code: T111


(excluding Taxes)



The IV Curve Measurement System provides accurate, reliable and affordable IV curve measurement and analysis of solar cells. The Xtralien X100 source meter performs current measurements from 10 nA to 100 mA, and has a voltage range of -10 V to 10 V. These metrics are well-suited to IV curve measurements of photovoltaic devices.

Perform quick and easy material characterisation using either the 6-Pixel or 8-Pixel substrate system and the Ossila Push Fit Test Board! The new swivel-lid on our push-fit test board allows for fast loading and unloading of devices.


    • Source voltage from -10 V to 10 V
    • Measure up to 100 mA and down to 10 nA
    • IV curve measurements
    • Automatic device characterisation (PCE, Jsc, Voc, FF)
    • 8 devices per substrate
    • Data presented in easy-to-read format
    • Data saved to file
    • Software easily edited using our Python tutorials


IV Curve System Overview

The IV Curve Measurement System comprises an Xtralien X100 Source Meter, a manual test board, and custom software. 

The test board provides reliable contact with substrate electrodes via a spring-loaded push fit system. No electrical connection legs are required due to the alignment of the contact pins with the ITO patterning of the substrate.

Components schematic of the IV Curve Measurement System.
Side view of the complete IV Curve Measurement System.


Measuring an IV curve with the IV Curve Measurement System.


Component List

Xtralien X100

The Xtralien X100 is a source meter that provides fast and accurate measurements required for the IV Curve Measurement System.

Source meter measure specifications are displayed in the table below. There are five ranges, each with a different maximum current and accuracy. The range can be changed to suit the device under measurement. The resolution of each range is 0.1% of its maximum current. 

Range Max Current Resolution Burden Noise
1 ± 100 mA 100 µA < 10 mV 6.0 E-6
2 ± 10 mA 10 µA < 10 mV 6.0 E-7
3 ± 1 mA 1 µA < 10 mV 6.0 E-8
4 ± 100 µA 100 nA < 10 mV 6.0 E-9
5 ± 10 µA 10 nA < 10 mV 6.0 E-10

For example, a measurement of up to 10 mA at range 2 would provide an IV curve with 10 µA accuracy. Please see the Xtralien X100 Source Meter page and the Xtralien X100 Manual for more details.

Xtralien Python Software

The system is controlled using custom script written in Python. This code is freely available, can be modified for custom requirements, and can be run by downloading our Xtralien Scientific Python distribution. This software is designed to overcome the entry barrier for scientists who would like to edit the code and adapt it for their own experiments. We provide tutorials and example code to get started.



The software takes IV curve measurements of selected device pixels and interprets the data automatically. The following metrics are calculated:

  • Power Conversion Efficiency (PCE)
  • Short Circuit Current Density (Jsc)
  • Open Circuit Voltage (Voc)
  • Fill Factor (FF)
  • Maximum Power Point (MPP)

As the software is written in Python, the user can edit and adjust it to fit in with their own experiment. 

Both the IV curve data and device metrics are saved to a .csv file for easy editing in any spreadsheet program.



from scipy import stats
from scipy import stats
import xtralien as xt
from xtralien.imports import array_to_csv
import numpy as np
from matplotlib import pyplot as plt
import sys
import os
import time
import warnings as wn
import csv

# Sweep Settings
smu_num = 2  # Chooose SMU number (1 or 2)
pixels = [1, 2]  # Choose pixels to test
vstart = -0.2  # Starting Voltage (V)
vend = 1   # End Voltage (V)
vstep = 0.05  # Step Size (V)
pixel_size = 0.04  # cm^2
irrad = 100  # mW / cm^2
time_between_pixels = 5  # Seconds between each pixel measurement

# X100 Settings
precision = 4
smu_range = 2
smu_osr = 5

#  File Settings
save = True      # Save data? (True of False)
folder_name = 'C:\\Users\\Admin\\Desktop'  # Choose a folder name


vdata = []  # Create pixel data lists (pixel,voltage,current)
idata = []

# Create Variables
smu = 'smu' + str(smu_num)
vnum = ((vend-vstart)/vstep) + 1  # Calculate number of steps in sweep
volts = np.linspace(vstart, vend, vnum)  # Create voltage list
base_i = 0

# Create Functions

def metrics(vdata_, idata_):
    # Calculate Jsc
    v_min = abs(vdata_[0])
    for n, i in enumerate(idata_):
        if abs(vdata_[n]) < v_min:
            v_min = abs(vdata_[n])
            index = n
    vdatai = vdata_[index-2:index+2]
    idatai = idata_[index-2:index+2]
    slope, ishort, r_value, p_value, std_err = stats.linregress(vdatai, idatai)
    jshort = (1000 * ishort) / float(pixel_size)
    # Calculate Voc
    i_min = abs(idata_[0])
    index = 2
    for n, v in enumerate(vdata_):
        if abs(idata_[n]) < i_min:
            i_min = abs(idata_[n])
            index = n
    if idata_[0] < 0:
        vdatav = vdata_[index-2:index+2]
        idatav = idata_[index-2:index+2]
        slope, vopen, r_val, p_val, std_err = stats.linregress(idatav, vdatav)
        vopen = 0
        print('Could not measure Voc - no I data below zero.')
    # Calculate FF
    v = np.array(idata_)
    i = np.array(vdata_)
    p = v * i
    mpp = np.amin(p)
    ffact = (mpp / (ishort*vopen))
    # Calulate PCE
    powout = jshort*vopen*(ffact)  # Calculate the maximum power out
    powin = irrad  # Power in is irradiance
    pceff = abs((powout / powin))*100  # Calculate the PCE and change to %
    return [vopen, jshort, ffact, pceff]

def limcheck(current, get_range):
    limit = pow(0.1, get_range)
    if abs(current) > limit:
        print('Warning: Current Over Range Limit. Set a lower range')
        print('Current: {} Range: {}'.format(current, get_range))
        return (True, current, get_range)
        return False

def acccheck(current, get_range):
    acc = pow(0.1, get_range)/1000
    if abs(current) < acc:
        print('Warning: Current Below Range Accuracy. Set a higher range.')
        print('Current: {} Range: {} \n'.format(current, get_range))
        return (True, current, get_range)
        return False

# Find X100 Addresss
ports = xt.serial_ports()  # Lists all COM ports
x100s = []  # Creates empty list for X100s
for COM in ports:  # For every COM port...
    # Attempt connection with a COM port
    with xt.X100(COM, serial_timeout=1) as Dev:
        hello = Dev.cloi.hello()
        # If COM port returns 'Hello World...
        if (Dev.cloi.hello(sleep_timeout=10)) == 'Hello World':
            print(COM, 'is an X100')
            x100s.append(COM)  # Append the list of X100s connected
            print(COM, 'is not an X100')
if len(x100s) == 0:  # Checks if any X100s were detected
    # If there are no X100s found, the program ends
    sys.exit('No X100s detected.')

# Create Plot ###
# fig = Figure()
wn.simplefilter("ignore")  # Ignores Matplotlib warning
f, ax1 = plt.subplots()
ax1.set_title('IV Curve')
plt.xlim((vstart-vstep), (vend+vstep))

# Perform Sweep
with xt.X100.USB(x100s[0]) as Dev1:
    print('Configuring X100')
    Dev1[smu].set.range(smu_range, response=0)
    get_range = Dev1[smu].get.range()
    print(str(smu) + ' Range set to ' + str(get_range))
    Dev1[smu].set.osr(smu_osr, response=0)
    get_osr = Dev1[smu].get.osr()
    print(str(smu) + ' OSR set to ' + str(get_osr))
    base_i = np.mean(Dev1[smu].measurei(10))
    for index, pix in enumerate(pixels):
        vdata = []
        idata = []
        # input('Set pixel {} and press Enter'.format(pix))  # Wait for input
        for v in volts:  # For every voltage in the list....
            measv, measi = Dev1[smu].oneshot(v)[0]  # Perform oneshot
            vdata.append(measv)  # Append the voltage data
            idata.append(measi-base_i)  # Append the current data
            limcheck(measi, get_range)
            plt.cla()  # Clear the graph (this saves on memory)
            ax1.plot(vdata, idata)
            ax1.set_title('Pixel ' + str(pix))
            plt.xlim((vstart-vstep), (vend+vstep))
            plt.pause(0.001)  # Pause the graph (allows live plotting)
        acccheck(min(idata), get_range)
        vopen, jshort, ffact, pceff = metrics(vdata, idata)
        print('Jsc is: ', jshort, ' mA/cm^2')
        print('Voc is: ', vopen, ' V')  # Print Voc
        print('FF is: ', ffact*100, ' %')  # Print FF
        print('PCE is: ', pceff, ' %')  # Print FF
        Dev1[smu].set.voltage(0, response=0)  # Set the SMU voltage to 0

        # Save to file
        if save is True:
            if not os.path.exists(folder_name):

        data = []
        for i, d in enumerate(vdata):
            data.append([idata[i], vdata[i]])
        headers = ['Voltage (V)', 'Current (A)']
        data.insert(0, headers)  # Insert field names above the data
        date = time.strftime('%y%m%d_%H%M%S')
        # Create file names
        filename = 'Pixel_' + str(pix) + '_' + date + '.csv'
        ofile = open(filename, "w")
        writer = csv.writer(ofile, delimiter=',', lineterminator='\n')
        # data = [frame_one, angle_one, frame_two, angle_two]
        if pixels[index] != pixels[-1]:
            print('Change to pixel ' + str(pixels[index+1]))