Capturing Transients with the Zurich Instruments Toolkit

April 7, 2021 by Meng Li

In many applications, Zurich Instruments' lock-in amplifiers and impedance analyzers are integrated into a larger measurement system. Relying on an application programming interface (API) to obtain necessary information from the instruments is of great importance in such cases. Thanks to the 5 included LabOne® APIs for Python, C, MATLAB®, LabVIEW™ and .NET, one can conveniently achieve this goal with minimal effort. If you are not yet familiar with them, take a look at our recent tutorial video, which shows how to get your data with an API in less than 5 minutes.

We have also introduced a new high-level Python programming interface for Zurich Instruments' hardware: the Zurich Instruments Toolkit. With the zhinst-toolkit, not only does the node tree structure become more readable (hierarchical), but the workflow of high-level modules such as Dweeper and Data Acquisition (DAQ) is also greatly simplified. This blog post presents a practical example (with code that can be downloaded here), i.e., capturing transient capacitance commonly needed in deep level transient spectroscopy (DLTS). This example requires the MFIA Impedance Analyzer or the MFLI Lock-in Amplifier with the MF-IA option, but the toolkit can be used with other instruments too (the HF2LI being an exception).

Here are the advantages of the LabOne Python API (ziPython) and of zhinst-toolkit:

Advantages of ziPython

  • Clear command log, ready to be pasted into your own scripts
  • Ability to load nested dictionary to cover all settings parameters
  • Comprehensive and flexible settings to optimize signal acquisition

Advantages of zhinst-toolkit

  • Hierarchial node structure in Python style
  • Focus on changing key parameters while leaving others at default settings
  • Simplified workflow for high-level modules

Setup and Initialization

If you would like to repeat the measurement taken in this blog post, you will only need a small diode as the device under test (DUT), which can be a solar cell or a light-emitting diode (LED). To generate a square voltage pulse, the Threshold Unit can be used. Naturally, the setup steps can be done in the LabOne graphical user interface (GUI) without API programming. With the command log, you can track which lines correspond to the changes in the GUI, and simply paste them into your own Python scripts. We also provided official examples in the LabOne Python API library, which shows how to synchronize all setting parameters in one single nested dictionary.

On the other hand, if you are used to the dot (.) notation in Python, then the zhinst-toolkit may be more convenient. Particularly when you are only interested in adjusting a few key parameters, accessing the node tree in this way is more intuitive.

Screen-Shot-2018-08-10-at-17.01.04.png

Figure 1: The MFIA with the MFITF fixture. The mounted device shown is a solar cell but can also be other diodes (e.g. small LEDs) for the measurements described in this blog post. A short BNC cable connects Aux output 1 to Aux input 1 for generating and receiving square pulses, respectively.

DAQ using ziPython

To find out how zhinst-toolkit helps to simplify high-level modules, we can have a look at the comparison below. Figure 2 shows the code in ziPython. In addition to the code pasted from the GUI, it is necessary to manually add lines, for example, with a progress indicator to run and finish the DAQ module. Setting a sufficiently long timeout can be important when studying a slow process, for instance.

It should also be mentioned that the time in the measured data is saved in the format of 'timestamp'. To calculate the real time, you will need to divide it to the clockbase which varies on different instrument platforms.

# subscription and set trigger for measurement
daq_module = daq.dataAcquisitionModule()
#change the device to your SN
daq_module.set("device", 'dev4562') 
#use capacitance as SW trigger
daq_module.set('triggernode', '/dev4562/imps/0/sample.param1') 
daq_module.set("type", 1)
daq_module.set("edge", 1)
daq_module.set('findlevel', 1) #automatically find the trigger level
#this sets the capture duration, which is 500/13k~38ms
daq_module.set('grid/cols', 500) 
daq_module.subscribe('/dev4562/imps/0/sample.param1')

daq_module.execute()
timeout = 100
start = time.time()
# Wait until the sweep is complete, with timeout.
while not daq_module.finished():  
    time.sleep(0.2)
    progress = daq_module.progress()
    print("Individual DAQ progress: {:.2%}.".format(progress[0]), end="\r")
    if (time.time() - start) > timeout:
        print("\nDAQ still not finished, forcing finish...")
        daq_module.finish()
print("")

data = daq_module.read(True)
daq_module.unsubscribe('/dev4562/imps/0/sample.param1') 

# save data for plotting
samples = data['/dev4562/imps/0/sample.param1']
clockbase = float(daq.getInt('/dev4562/clockbase'))
#this is needed to convert timestamp into real time
plt.plot(np.linspace(0, (samples[0]['timestamp'][0][-1] - 
                     samples[0]['timestamp'][0][0])/clockbase, 
                     len(samples[0]['timestamp'][0]) ), 
                     samples[0]['value'][0]) 
plt.xlabel('Time (s)')
plt.ylabel('Capacitance (F)')
plt.title("Transient capacitance")

DAQ using zhinst-toolkit

Now we turn to zhinst-toolkit, where the progress indicator and the timestamp are taken care of by default. This means that all you need to do is just plot the value (more precisely, its 0th row, as the value is a multi-dimensional array) against time. Therefore, zhinst-toolkit greatly helps to simplify your workflow in these high-level LabOne modules.

#subscription and set up trigger for measurement
mfli.daq.signals_clear()
#add the 2nd parameter of impedance, 
#default Cp, as the only signal of interest
imp_param1 = mfli.daq.signals_add('imp0','param1') 
mfli.daq.type('edge') #set edge trigger mode, default, rise 
#change to your device number
mfli.daq.triggernode('/DEV4562/imps/0/sample.param1') 
mfli.daq.findlevel(1) #automatic search for level
#500 is arbitraty chosen. 
#The duration by default equals to 500Sa/13kSa/s=38ms
mfli.daq.grid_cols(500) 
mfli.daq.measure() #start the daq module

#save data for plotting
result = mfli.daq.results[imp_param1]
plt.plot(result.time, result.value[0])
plt.xlabel('Time (s)')
plt.ylabel('Capacitance (F)')
plt.title("Transient capacitance")

To conclude, we now compare the result from zhinst-toolkit to the one from the GUI. We can see they match almost perfectly. The only noticeable difference: in the GUI, the default setting has a trigger delay of -1 ms, so there are also data (baseline) available before the first trigger (in negative time). By contrast in zhinst-toolkit, by recalculation, we choose to start from 0.

DLTS-comparison.png

Figure 4: Comparison of the results from LabOne GUI (upper plot) and zhinst-toolkit (lower plot).

If you are interested in knowing more about what zhinst-toolkit can do, please take a look at the technical documentation. And do not hesitate to contact us if you have any questions.