From 90cb9d20d2fec2b231a26b0f2c12f32e96e9f601 Mon Sep 17 00:00:00 2001 From: unknown <asoalexandros@gmail.com> Date: Thu, 27 Mar 2025 14:30:02 +0100 Subject: [PATCH] Beta Version of Custom SMU for Users --- hp4155/Custom_SMU/interface_custom.ipynb | 52 ++ hp4155/Custom_SMU/lib/help.py | 223 ++++++++ hp4155/Custom_SMU/lib/interface.py | 212 ++++++++ hp4155/Custom_SMU/main.py | 653 +++++++++++++++++++++++ hp4155/hp4155a.py | 40 +- 5 files changed, 1178 insertions(+), 2 deletions(-) create mode 100644 hp4155/Custom_SMU/interface_custom.ipynb create mode 100644 hp4155/Custom_SMU/lib/help.py create mode 100644 hp4155/Custom_SMU/lib/interface.py create mode 100644 hp4155/Custom_SMU/main.py diff --git a/hp4155/Custom_SMU/interface_custom.ipynb b/hp4155/Custom_SMU/interface_custom.ipynb new file mode 100644 index 0000000..138fcb8 --- /dev/null +++ b/hp4155/Custom_SMU/interface_custom.ipynb @@ -0,0 +1,52 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "3f5d53eb-4723-4e65-8d58-22888bcc84b1", + "metadata": {}, + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "'(' was never closed (main.py, line 27)", + "output_type": "error", + "traceback": [ + "\u001b[1;36m File \u001b[1;32m~\\labcode\\hp4155\\Custom_SMU\\main.py:27\u001b[1;36m\u001b[0m\n\u001b[1;33m ini = widgets.Button(description = 'Import from ini. (Coming Soon)',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m '(' was never closed\n" + ] + } + ], + "source": [ + "%run main.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4134f36-9202-45f5-ad20-b71dfc315edd", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/hp4155/Custom_SMU/lib/help.py b/hp4155/Custom_SMU/lib/help.py new file mode 100644 index 0000000..876b79b --- /dev/null +++ b/hp4155/Custom_SMU/lib/help.py @@ -0,0 +1,223 @@ +import tkinter as tk +import tkinter.messagebox +import pandas as pd +import matplotlib.pyplot as plt +from IPython.display import clear_output +from datetime import datetime +from tkinter import filedialog + +# Check the functionalities of smus VAR1,VAR2 and VARD +def check_sweep_func(smus:list,func:str): + for i, smu in enumerate(smus): + if smu["func"] == func and smu["disabled"]== False: + return True + return False + +# Check if there is a pulsed smu for sweep smu +def check_sweep_pulse(smus:list): + for smu in smus: + if "PULSE" in smu["mode"] and smu["disabled"]== False: + return True + return False + +# Check if there any constant smus and return their numbers +def check_sweep_cons(smus:list): + cons_smu_numbers = [] + for i,smu in enumerate(smus): + if (smu['func']=='CONS' and smu['mode']!='COMM') and smu['disabled']== False: # Non Grounded SMUs + cons_smu_numbers.append(i+1) # for the actual number of smus + + return cons_smu_numbers + +# Setup the axes in tool +def setup_axes(axes_info:list,device): + axes_plot = ['X','Y1','Y2'] + for ax_plot,ax_info in zip(axes_plot,axes_info): + if ax_plot == 'Y2' and ax_info['name'] == "": # Y2 axis can be empty + pass + else: + device.display_variable(ax_plot,ax_info['name']) + device.axis_scale(ax_plot,ax_info['scale']) + device.display_variable_min_max(ax_plot,'MIN',ax_info['min']) + device.display_variable_min_max(ax_plot,'MAX',ax_info['max']) + + +# Setup the variables to be saved +def save_variables(axes,variables,device): + # condition number one: the plotted variables need to be saved in the file + # Sticter policy with exceptions + + # Retrieve the names of the variables + variable_names =[] + for variable in variables: + variable_names.append(variable['name']) + + # Retrieve the axes names (variable) + axes_names=[] + for i,ax in enumerate(axes): + if i != 2 and ax['name']!="": # Y2 axis can be left empty + axes_names.append(ax['name']) + + # Now Check if axes names are in the variable names + for ax_name in axes_names: + if ax_name not in variable_names: + raise Exception("Plotted Variables should be saved!") + + # Send the command to tool + device.variables_to_save(variable_names) + + # Now this should work + +def error_box(information): + #open dialog and hide the main window + root = tk.Tk() + root.withdraw() + root.lift() #show window above all other applications + + root.attributes("-topmost", True)#window stays above all other applications + + #display meaagebox + tkinter.messagebox.showerror(message=information) + root.destroy() + +def information_box(information): + #open dialog and hide the main window + root = tk.Tk() + root.withdraw() + root.lift() #show window above all other applications + + root.attributes("-topmost", True)#window stays above all other applications + + #display meaagebox + tkinter.messagebox.showinfo(message=information) + root.destroy() + +# We plot results: but we will play with the axes! +def plot_results(values,device): + + # convert scale from tool to matplotlib + def get_scale(scale): + if scale == 'LIN': + scale = 'linear' + else: + scale = 'log' + return scale + + # Get the names of the plotted variables + x_var = device.get_axis_variable('X') + y1_var = device.get_axis_variable('Y1') + y2_var = device.get_axis_variable('Y2') + + # Get the scales of the plotted variables + x_scale = device.get_axis_scale('X') + x_scale = get_scale(x_scale) + y1_scale = device.get_axis_scale('Y1') + y1_scale = get_scale(y1_scale) + y2_scale = device.get_axis_scale('Y2') + y2_scale = get_scale(y2_scale) + + # Find the values that will be plotted + for key, results in values.items(): + if x_var in key: # check if the substring is contained + x_values = results.copy() # This is a list + x_label = key + elif y1_var in key: + y1_values = results.copy() + y1_label = key + elif y2_var in key and y2_var !="": + y2_values = results.copy() + y2_label = key + + # Now lets do the plot + fig,ax1 = plt.subplots() + + ax1.set_xlabel(x_label) + ax1.set_ylabel(y1_label,color = 'tab:red') # Yellow Color + ax1.set_xscale(x_scale) + ax1.set_yscale(y1_scale) + + ax1.plot(x_values,y1_values,color='tab:red') + ax1.tick_params(axis ='y', labelcolor ='tab:red',which='both') + + if y2_var!= "": + # Adding Twin Axes # Blue color + ax2 = ax1.twinx() + + ax2.set_ylabel(y2_label,color = 'tab:green') + ax2.set_yscale(y2_scale) + + ax2.plot(x_values,y2_values,color='tab:green') + ax2.tick_params(axis ='y', labelcolor ='tab:green',which='both') + + display(fig) + # The checks will be corrected later + + +def create_file(filename): + root = tk.Tk() + root.withdraw() + root.lift() #show window above all other applications + + root.attributes("-topmost", True)#window stays above all other applications + + file = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files","*.txt")],title = "save results path",initialfile =filename) + + #check if the file path is correct(.txt) + while file.endswith(".txt") == False: + #open again filedialog with error message box + tk.messagebox.showerror(message='invalid filename!') + file = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files","*.txt")],title = "save results path",initialfile =filename) + root.destroy() + return file + +# sampling help functions + +def check_cons_smu_samp(smus:list): + cons_smu_numbers = [] + for i,smu in enumerate(smus): + if smu['mode']!='COMM' and smu["disabled"]==False: # Non Grounded active SMUs + cons_smu_numbers.append(i+1) # for the actual number of smus + + return cons_smu_numbers + +def load_ini(): + root = tk.Tk() + root.withdraw() + root.lift() #show window above all other applications + + root.attributes("-topmost", True)#window stays above all other applications + + + file = filedialog.askopenfilename(filetypes=[("Ini files","*.ini")],title ='Select ini file') + while file.endswith(".ini") == False: + #open again filedialog with error message box + answer=tk.messagebox.askyesno(message='Do you want to cancel the ini file load?') + if answer == True: + raise Exception("Ini File Operation aborted!") + else: + file = filedialog.askopenfilename(filetypes=[("Ini files","*.ini")],title = "Select ini file") + root.destroy() + return file + + +# Get measurement mode from ini file +def get_mode(sections): + for section in sections: + if "MEASUREMENT MODE:" in section: + mode = section.split(":")[1] + return mode + + raise Exception("Meaurement mode not found ini file!") + +def get_integration(sections): + for section in sections: + if "INTEGRATION TIME:" in section: + integration = section.split(":")[1] + return integration + + raise Exception("Integration Time not found ini file!") + + + + + \ No newline at end of file diff --git a/hp4155/Custom_SMU/lib/interface.py b/hp4155/Custom_SMU/lib/interface.py new file mode 100644 index 0000000..c15969a --- /dev/null +++ b/hp4155/Custom_SMU/lib/interface.py @@ -0,0 +1,212 @@ +import ipywidgets as widgets +from IPython.display import clear_output +from ipywidgets import GridspecLayout,Layout + + +def create_dict(**widgets): + return widgets + +# The smu configuration page +def page_1(): + first_page = GridspecLayout(5,6) + + #first column + first_page[0,0]= widgets.Label("UNIT",layout=Layout(height='auto', width='auto')) + first_page[1,0] = widgets.Label('SMU1',layout=Layout(height='auto', width='auto')) + first_page[2,0] = widgets.Label('SMU2',layout=Layout(height='auto', width='auto')) + first_page[3,0] = widgets.Label('SMU3',layout=Layout(height='auto', width='auto')) + first_page[4,0] = widgets.Label('SMU4',layout=Layout(height='auto', width='auto')) + + #second column voltage name + first_page[0,1]= widgets.Label('VNAME',layout=Layout(height='auto', width='auto')) + for i in range(1,5): + first_page[i,1]= widgets.Text(layout=Layout(height='auto', width='auto'), value =f'V{i}' ) + + #Third column Iname + first_page[0,2]= widgets.Label('INAME',layout=Layout(height='auto', width='auto')) + for i in range(1,5): + first_page[i,2]= widgets.Text(layout=Layout(height='auto', width='auto'),value = f'I{i}') + + + #Fourth column mode + options_mode = ['V','I','COMM','VPULSE','IPULSE'] + first_page[0,3]= widgets.Label('MODE',layout=Layout(height='auto', width='auto')) + first_page[1,3]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_mode,value = 'COMM') #smu1 + first_page[2,3]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_mode,value = 'I') #smu2 + first_page[3,3]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_mode,value = 'V') #smu3 + first_page[4,3]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_mode,value = 'V') #smu4 + + + #Fifth column function + options_function= ['VAR1','VAR2','VARD','CONS'] + first_page[0,4]= widgets.Label('FUNC',layout=Layout(height='auto', width='auto')) + first_page[1,4]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_function,value ='CONS') #smu1 + first_page[2,4]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_function,value ='VAR2') #smu2 + first_page[3,4]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_function,value ='VAR1') #smu3 + first_page[4,4]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_function,value ='CONS') #smu4 + + # Sixth column ask if smus are disabled + first_page[0,5] = widgets.Label("DISABLE") + for i in range(1,5): + first_page[i,5]= widgets.Checkbox(layout=Layout(height='auto', width='auto'),value = False,indent = False) + + return first_page + + +#page 2 the user function page +def page_2(): + second_page = GridspecLayout(7,3) + + # first line (descriptions) + second_page[0,0]= widgets.Label('NAME',layout=Layout(height='auto', width='auto')) + second_page[0,1]= widgets.Label('UNIT',layout=Layout(height='auto', width='auto')) + second_page[0,2]= widgets.Label('DEFINITION',layout=Layout(height='auto', width='auto')) + + # Iterate through the lines and columns for text boxes + for i in range(1,7): + for j in range(3): + second_page[i,j] = widgets.Text(layout=Layout(height='auto', width='auto')) + + return second_page + +#parameters setting +def page_3(): + third_page = GridspecLayout(24,5) + + # First Line + third_page[0,0]=widgets.Dropdown(options = ['SWEEP','SAMPLING','STRESS'], value = 'SWEEP',description ="MEASUREMENT MODE",layout=Layout(height='auto', width='auto'),style = {'description_width': 'initial'}) + third_page[0,1] = widgets.Dropdown(options = ['SHORt','MEDium','LONG'], value = 'MEDium',description ="INTEGRATION TIME",layout=Layout(height='auto', width='auto'),style = {'description_width': 'initial'}) + + # Second Line + third_page[2,:] = widgets.Label("SWEEP SETUP",layout=Layout(height='auto', width='auto')) + + # Third Line + third_page[3,0] = widgets.Label("VAR1",layout=Layout(height='auto', width='auto')) + third_page[3,1] = widgets.Label("VAR2",layout=Layout(height='auto', width='auto')) + third_page[3,2] = widgets.Label("VAR1'",layout=Layout(height='auto', width='auto')) + third_page[3,3] = widgets.Label("SMU PULSE",layout=Layout(height='auto', width='auto')) + + # Fourth Line + third_page[4,0] = widgets.FloatText(0, description = 'START',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[4,1] = widgets.FloatText(0, description = 'START',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[4,2] = widgets.FloatText(description = 'OFFSET',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[4,3] = widgets.FloatText(description = 'PERIOD',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + + # Fifth line + third_page[5,0] = widgets.FloatText(5,description = 'STOP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[5,1] = widgets.FloatText(500e-6, description = 'STOP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[5,2] = widgets.FloatText(description = 'RATIO',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[5,3] = widgets.FloatText(description = 'WIDTH',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + + # Sixth Line + third_page[6,0] = widgets.FloatText(0.1,description = 'STEP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[6,1] = widgets.FloatText(6,description = 'NO OF STEPS',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + #skip 5,2 + third_page[6,3] = widgets.FloatText(5,description = 'BASE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + + #Seventh Line + third_page[7,0] = widgets.FloatText(100e-3,description = 'COMPLIANCE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[7,1] = widgets.FloatText(5,description = 'COMPLIANCE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[7,2] = widgets.FloatText(description = 'COMPLIANCE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + #no more pulse + + # Eighth Line + third_page[8,0] = widgets.FloatText(0,description = 'POWER COMP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[8,1] = widgets.FloatText(0,description = 'POWER COMP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[8,2] = widgets.FloatText(description = 'POWER COMP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + + third_page[9,0] = widgets.Checkbox(False,description = 'HYSTERISIS',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + + ## Sampling Parameters + third_page[11,:] = widgets.Label("SAMPLING OR STRESS PARAMETERS") + third_page[12,0] = widgets.Dropdown(options=["LINear","L10","L25","L50","THINnedout"], value = 'LINear',description = 'MODE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[13,0] = widgets.FloatText(2e-3,description = "INITIAL INTERVAL", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[14,0] = widgets.IntText(1001,description = "NO. OF SAMPLES", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[15,0] = widgets.FloatText(0,description = "DURATION", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[16,0] = widgets.FloatText(0,description = "HOLD TIME", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + third_page[17,0] = widgets.Checkbox(True,description = "FILTER", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + + + # Set constant SMU for all measurement in case they are not disabled + + third_page[19,:] = widgets.Label("CONSTANT",layout=Layout(height='auto', width='auto')) + third_page[20,0] = widgets.Label("UNIT",layout=Layout(height='auto', width='auto')) + third_page[20,1] = widgets.Label("SMU1",layout=Layout(height='auto', width='auto')) + third_page[20,2] = widgets.Label("SMU2",layout=Layout(height='auto', width='auto')) + third_page[20,3] = widgets.Label("SMU3",layout=Layout(height='auto', width='auto')) + third_page[20,4] = widgets.Label("SMU4",layout=Layout(height='auto', width='auto')) + + third_page[21,0] = widgets.Label("SOURCE",layout=Layout(height='auto', width='auto')) + third_page[22,0] = widgets.Label("COMPLIANCE",layout=Layout(height='auto', width='auto')) + + for i in range(21,23): + for j in range(1,5): + third_page[i,j] = widgets.FloatText(layout=Layout(height='auto', width='auto')) + + return third_page + + +#display variables +def page_4(): + fourth_page = GridspecLayout(5,4) + + #first line headers + fourth_page[0,1]= widgets.Label('X',layout=Layout(height='auto', width='auto')) + fourth_page[0,2]= widgets.Label('Y1',layout=Layout(height='auto', width='auto')) + fourth_page[0,3]= widgets.Label('Y2',layout=Layout(height='auto', width='auto')) + + #first column + fourth_page[1,0] = widgets.Label('NAME',layout=Layout(height='auto', width='auto')) + fourth_page[2,0] = widgets.Label('SCALE',layout=Layout(height='auto', width='auto')) + fourth_page[3,0] = widgets.Label('MIN',layout=Layout(height='auto', width='auto')) + fourth_page[4,0] = widgets.Label('MAX',layout=Layout(height='auto', width='auto')) + + #iterate through the second line (NAME) + fourth_page[1,1]=widgets.Text(layout=Layout(height='auto', width='auto'),value = 'V3') + fourth_page[1,2]=widgets.Text(layout=Layout(height='auto', width='auto'),value = 'I3') + fourth_page[1,3]=widgets.Text(layout=Layout(height='auto', width='auto')) + + #Iterate through the third line (scale) + options_scale = ['LIN','LOG'] + for j in range(1,4): + fourth_page[2,j] = widgets.Dropdown(value = 'LIN',options = options_scale, layout=Layout(height='auto', width='auto'),) + + #iterate throuh the last 2 lines(min-max) + for j in range(1,4): + fourth_page[3,j] = widgets.FloatText(value = 0) #min + + fourth_page[4,1]=widgets.FloatText(value = 1) #max X-axis + fourth_page[4,2]=widgets.FloatText(value = 0.1) #max Y1-axis + fourth_page[4,3]=widgets.FloatText(value = 0) #max Y2-axis + + return fourth_page + + +def page_5(): + fifth_page = GridspecLayout(14,2) + + fifth_page[0,0] = widgets.Text(description = 'MEASUREMENT NAME',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + fifth_page[1,0] = widgets.Text(description ='PROCESSING NR.',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + fifth_page[2,0] = widgets.Text(description ='SAMPLE SERIES',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + fifth_page[3,0] = widgets.Text(description ='FIELD',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + fifth_page[4,0] = widgets.Text(description ='DUT',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) + + fifth_page[5,0] = widgets.Label("VARIABLES TO SAVE",layout=Layout(height='auto', width='auto')) + fifth_page[5,1] = widgets.Label("UNIT",layout=Layout(height='auto', width='auto')) + for i in range(8): + for j in range(2): + fifth_page[6+i,j] = widgets.Text(layout=Layout(height='auto', width='auto')) + + return fifth_page + +#change state off the grid +def change_state(*grids): + for grid in grids: + for i in range(grid.n_rows): + for j in range(grid.n_columns): + try: + grid[i,j].disabled = not grid[i,j].disabled + except: + pass + + diff --git a/hp4155/Custom_SMU/main.py b/hp4155/Custom_SMU/main.py new file mode 100644 index 0000000..98f8c9c --- /dev/null +++ b/hp4155/Custom_SMU/main.py @@ -0,0 +1,653 @@ +import sys +sys.path.insert(0, './lib') +sys.path.insert(0, '..') #append parent directory +import os +import configparser +import warnings + +from interface import * +from help import * +import hp4155a + +first_page = page_1() +second_page = page_2() +third_page = page_3() +fourth_page = page_4() +fifth_page = page_5() + +titles = ["SMUs","User Functions","Parameters","Plotting","Save to file"] +children = [first_page,second_page,third_page,fourth_page,fifth_page] +tab = widgets.Tab() +tab.children = children +tab.titles = titles + +display(tab) + +start = widgets.Button(description='Start Measurement') +ini = widgets.Button(description = 'Import from ini. (Coming Soon)',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) +output = widgets.Output() + +display(widgets.HBox([start,ini]),output) + +device = hp4155a.HP4155a('GPIB0::17::INSTR') + +def on_start_clicked(b): + with output: + clear_output() + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = True + ini.disabled = True + + # Reset the device + device.reset() + + # Step 1 create the dictionaries appropriately for every measurement + + # read general information + measurement_mode = third_page[0,0].value + measurement_name = fifth_page[0,0].value + processing_nr = fifth_page[1,0].value + sample_series = fifth_page[2,0].value + field = fifth_page[3,0].value + dut = fifth_page[4,0].value + integration = third_page[0,1].value + + # we need constant smus for all measurements + # Constant Smus + cons_smus = [] + for j in range(1,5): + cons_smus.append(create_dict(value = third_page[21,j].value,comp=third_page[22,j].value)) + + # 2nd page + user_functions = [] + #iterate over the rows + for i in range(1,7): + if second_page[i,0].value!="": # do not save user functions without names + user_functions.append( + create_dict( + name = second_page[i,0].value, + unit=second_page[i,1].value, + expression = second_page[i,2].value + ) + ) + + # Page 4 + axes = [] # 0 is x-axis , 1 is y1-axis and 2 is y2-axis + for j in range(1,4): #iterate over the column + axes.append( + create_dict( + name = fourth_page[1,j].value, + scale = fourth_page[2,j].value, + min =fourth_page[3,j].value, + max = fourth_page[4,j].value + ) + ) + + # Page 5 + variables = [] + for i in range(8): + if fifth_page[6+i,0].value!="": # do not save empty rows + variables.append( + create_dict( + name = fifth_page[6+i,0].value, + unit =fifth_page[6+i,1].value + ) + ) + + + # first sweep + if measurement_mode == 'SWEEP': + #page 1 + smus = [] + #iterate over the rows + for i in range(1,5): + smus.append( + create_dict( + vname = first_page[i,1].value, + iname = first_page[i,2].value, + mode = first_page[i,3].value, + func = first_page[i,4].value, + disabled = first_page[i,5].value + ) + ) + + + + # Page 3 + #match hysteris checkbox with the command forwarded to the tool + if third_page[9,0].value == True: + hyst = 'DOUB' + else: + hyst = 'SING' + + var1 = create_dict( + start = third_page[4,0].value, + stop = third_page[5,0].value, + step = third_page[6,0].value, + comp = third_page[7,0].value, + pcomp =third_page[8,0].value, + mode = hyst + ) + var2 = create_dict( + start = third_page[4,1].value, + step = third_page[5,1].value, + points = third_page[6,1].value, + comp = third_page[7,1].value, + pcomp=third_page[8,1].value + ) + vard = create_dict( + offset=third_page[4,2].value, + ratio = third_page[5,2].value, + comp = third_page[7,2].value, + pcomp=third_page[8,2].value + ) + + pulse = create_dict( + period=third_page[4,3].value, + width = third_page[5,3].value, + base = third_page[6,3].value + ) + + # Now execute measurement + + #setup sweep measurement mode + device.measurement_mode('SWE') + + #disable all irrelevant units + device.disable_not_smu() + + # First Setup Smus + for i,smu in enumerate(smus): + if smu['disabled'] == False: + device.setup_smu(i+1,smu) + else: #disabled + device.smu_disable(i+1) + + + # Setup User Functions + for user_function in user_functions: + device.user_function(user_function['name'],user_function['unit'],user_function['expression']) + + # Set the integration time + + device.integration_time(integration) + # Setup VAR1 (always in sweep measurements) + device.setup_var1(var1) # device will check for errors + + # Now set the 3 additional columns + if check_sweep_func(smus,'VAR2') == True: + device.setup_var2(var2) + if check_sweep_func(smus,'VARD') == True: + device.setup_vard(vard) + if check_sweep_pulse(smus)== True: + device.setup_pulse(pulse) + + # Check for constant SMUs but not grounded + cons_smu_numbers = check_sweep_cons(smus) + for i, cons_smu in enumerate(cons_smus): + if i+1 in cons_smu_numbers: # check if the constant smu was found in the first page func + device.setup_cons_smu(i+1,cons_smu) + + # Now set the axes + setup_axes(axes,device) + + # Set the variables to be saved (This function has an exemption) + try: + save_variables(axes,variables,device) + except Exception as e: + error_box(e) + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = False + ini.disabled = False + return + + + # Start the measurement + device.single_measurement() + while device.operation_completed()==False: + pass + + device.autoscaling() + + # List all errors occured + counter,message = device.list_all_errors() + if counter>0: + error_box(message) + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = False + ini.disabled = False + return + + + # Get the data from the device + # These are correcly defined and include the plotted ones + values = {} + for variable in variables: + key = variable['name']+"("+variable['unit'] +")" + values[key] = device.return_values(variable['name']) + + plot_results(values,device) + + # Save results + df = pd.DataFrame(values) + + filename = f"{sample_series}_{field}_{dut}_{measurement_name}_sweep.txt" + txt_file = create_file(filename) + ini_file = os.path.splitext(txt_file)[0]+'.ini' + + with open(txt_file,'w') as f: + date = str(datetime.today().replace(microsecond=0)) + f.write(f"{measurement_name} (sweep) at {date}:"+"\n") + f.write("Sample Information\n") + f.write(f"Processing Number:{processing_nr}"+"\n") + f.write(f"Sample Series:{sample_series}"+"\n") + f.write(f"Field:{field}"+"\n") + f.write(f"DUT:{dut}"+"\n") + f.write("\n") + f.write(f"Measurement parameters can be found at: {ini_file}"+"\n") + f.write("\nResults\n") + + df.to_csv(txt_file,sep=" ",mode='a') + + # export interface to ini file + # First the Smus + config = configparser.ConfigParser() + with open(ini_file,'w') as configfile: + config.add_section("THESE ARE THE PARAMETERS OF THE CORRESPONDING MEASUREMENT") + config.add_section("THE WHOLE INTERFACE IS SAVED SO FIND WHAT YOU NEED") + config.add_section("DO NOT MODIFY THIS FILE") + config.add_section(f"MEASUREMENT MODE: {measurement_mode}") + config.add_section(f"INTEGRATION TIME: {integration}") + + # First the smus + config.add_section("SMUS") + for i, smu in enumerate(smus): + config.add_section(f"SMU{i+1}") + for key,value in smu.items(): + config.set(f"SMU{i+1}",key,str(value)) + + # Secondly the user functions + config.add_section("USER FUNCTIONS") + for i, user_function in enumerate(user_functions): + config.add_section(f"USER FUNCTION {i+1}") + for key,value in user_function.items(): + config.set(f"USER FUNCTION {i+1}",key,str(value)) + + # Then the 3rd page + config.add_section("VAR1") + for key,value in var1.items(): + config.set("VAR1",key,str(value)) + + config.add_section("VAR2") + for key,value in var2.items(): + config.set("VAR2",key,str(value)) + + config.add_section("VARD") + for key, value in vard.items(): + config.set("VARD",key,str(value)) + + config.add_section("PULSE") + for key,value in pulse.items(): + config.set("PULSE",key,str(value)) + + # Now The constant smus + config.add_section('CONSTANT SMUS') + for i, cons_smu in enumerate(cons_smus): + config.add_section(f"CONSTANT SMU{i+1}") + for key, value in cons_smu.items(): + config.set(f"CONSTANT SMU{i+1}",key,str(value)) + + + # Page 4 The axes + config.add_section('AXES') + for i,axis in enumerate(axes): + config.add_section(f"AXIS {i+1}") + for key,value in axis.items(): + config.set(f"AXIS {i+1}",key,str(value)) + + # Page 5 The varibles + config.add_section("SAVED VARIABLES") + for i, variable in enumerate(variables): + config.add_section(f"VARIABLE {i+1}") + for key,value in variable.items(): + config.set(f"VARIABLE {i+1}",key,str(value)) + + config.write(configfile) + # Sampling Measurement Mode + elif measurement_mode=='SAMPLING': + # sampling parameters + parameters= create_dict( + mode=third_page[12,0].value, + interval=third_page[13,0].value, + hold=third_page[16,0].value, + points=third_page[14,0].value, + filter=int(third_page[17,0].value) + ) + duration = third_page[15,0].value + + # Set the smus all constant + #page 1 + smus = [] + #iterate over the rows and set the function to constant + for i in range(1,5): + smus.append( + create_dict( + vname = first_page[i,1].value, + iname = first_page[i,2].value, + mode = first_page[i,3].value, + func = 'CONS', + disabled = first_page[i,5].value + ) + ) + + # Now start the measurement + device.measurement_mode('SAMP') + + #disable all irrelevant units + device.disable_not_smu() + + # First Setup Smus + for i,smu in enumerate(smus): + if smu['disabled'] == False: + device.setup_smu(i+1,smu) + else: #disabled + device.smu_disable(i+1) + + + # Setup User Functions + for user_function in user_functions: + device.user_function(user_function['name'],user_function['unit'],user_function['expression']) + + # Set the integration time + device.integration_time(integration) + + # Set the sampling parameters + device.setup_sampling(parameters) + + # Set the total sampling time + if duration<= 0: + warnings.warn("Non positive measurement duration. Auto Sampling time will be set") + device.auto_sampling_time(1) + else: + device.total_sampling_time(duration) + + # Setup the constant SMUs + cons_smu_numbers = check_cons_smu_samp(smus) + for i, cons_smu in enumerate(cons_smus): + if i+1 in cons_smu_numbers: # check if the constant smu was found in the first page func + device.setup_smu_sampling(i+1,cons_smu) + + # Now set the axes + setup_axes(axes,device) + + # Set the variables to be saved (This function has an exemption) + try: + save_variables(axes,variables,device) + except Exception as e: + error_box(e) + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = False + ini.disabled = False + return + + + # Start the measurement + device.single_measurement() + while device.operation_completed()==False: + pass + + device.autoscaling() + + # List all errors occured + counter,message = device.list_all_errors() + if counter>0: + error_box(message) + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = False + ini.disabled = False + return + + + # Get the data from the device + # These are correcly defined and include the plotted ones + values = {} + for variable in variables: + key = variable['name']+"("+variable['unit'] +")" + values[key] = device.return_values(variable['name']) + + plot_results(values,device) + + # Save results + df = pd.DataFrame(values) + + filename = f"{sample_series}_{field}_{dut}_{measurement_name}_sampling.txt" + txt_file = create_file(filename) + ini_file = os.path.splitext(txt_file)[0]+'.ini' + + with open(txt_file,'w') as f: + date = str(datetime.today().replace(microsecond=0)) + f.write(f"{measurement_name} (sampling) at {date}:"+"\n") + f.write("Sample Information\n") + f.write(f"Processing Number:{processing_nr}"+"\n") + f.write(f"Sample Series:{sample_series}"+"\n") + f.write(f"Field:{field}"+"\n") + f.write(f"DUT:{dut}"+"\n") + f.write("\n") + f.write(f"Measurement parameters can be found at: {ini_file}"+"\n") + f.write("\nResults\n") + + df.to_csv(txt_file,sep=" ",mode='a') + + # export interface to ini file + # First the Smus + config = configparser.ConfigParser() + with open(ini_file,'w') as configfile: + config.add_section("THESE ARE THE PARAMETERS OF THE CORRESPONDING MEASUREMENT") + config.add_section("THE WHOLE INTERFACE IS SAVED SO FIND WHAT YOU NEED") + config.add_section("DO NOT MODIFY THIS FILE") + config.add_section(f"MEASUREMENT MODE: {measurement_mode}") + config.add_section(f"INTEGRATION TIME: {integration}") + + # First the smus + config.add_section("SMUS") + for i, smu in enumerate(smus): + config.add_section(f"SMU{i+1}") + for key,value in smu.items(): + config.set(f"SMU{i+1}",key,str(value)) + + # Secondly the user functions + config.add_section("USER FUNCTIONS") + for i, user_function in enumerate(user_functions): + config.add_section(f"USER FUNCTION {i+1}") + for key,value in user_function.items(): + config.set(f"USER FUNCTION {i+1}",key,str(value)) + + # Then the 3rd page + config.add_section('SAMPLING PARAMETERS') + for key,value in parameters.items(): + config.set('SAMPLING PARAMETERS',key,str(value)) + + # Now the constant smus + config.add_section('CONSTANT SMUS') + for i, cons_smu in enumerate(cons_smus): + config.add_section(f"CONSTANT SMU{i+1}") + for key, value in cons_smu.items(): + config.set(f"CONSTANT SMU{i+1}",key,str(value)) + + + # Page 4 The axes + config.add_section('AXES') + for i,axis in enumerate(axes): + config.add_section(f"AXIS {i+1}") + for key,value in axis.items(): + config.set(f"AXIS {i+1}",key,str(value)) + + # Page 5 The varibles + config.add_section("SAVED VARIABLES") + for i, variable in enumerate(variables): + config.add_section(f"VARIABLE {i+1}") + for key,value in variable.items(): + config.set(f"VARIABLE {i+1}",key,str(value)) + + config.write(configfile) + else: # Stress + #page 1 + smus = [] + #iterate over the rows and set the function to constant + for i in range(1,5): + # Set SYNC non SYNC mode + mode= first_page[i,3].value + if mode != 'COMM': + func = 'SYNC' + else: + func = 'NSYNC' + smus.append( + create_dict( + name = first_page[i,1].value, + mode = mode, + func = func, + disabled = first_page[i,5].value + ) + ) + + # Now define the parameters + duration = third_page[15,0].value + if duration <= 0: + error_box("Stress Time should be positive!") + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = False + ini.disabled = False + return + hold_time = third_page[16,0].value + filter = int(third_page[17,0].value) + + # Now start the measurement + device.stress_page() + device.stress_disable_not_smu() + + # First Setup Smus + for i,smu in enumerate(smus): + if smu['disabled'] == False: + device.smu_stress(i+1,smu) + else: #disabled + device.smu_stress_disable(i+1) + + # Now set the Parameters + device.stress_filter(filter) + device.stress_hold_time(hold_time) + device.stress_time(duration) + + cons_smu_numbers = check_cons_smu_samp(smus) # works also for sampling + for i, cons_smu in enumerate(cons_smus): + if i+1 in cons_smu_numbers: # check if the constant smu was found in the first page func + device.setup_smu_stress(i+1,cons_smu) + + # Now start the measurement + device.start_stress() + while device.operation_completed() == False: + pass + + # List all errors occured + counter,message = device.list_all_errors() + if counter>0: + error_box(message) + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = False + ini.disabled = False + return + + filename = f"{sample_series}_{field}_{dut}_{measurement_name}_stress.txt" + txt_file = create_file(filename) + ini_file = os.path.splitext(txt_file)[0]+'.ini' + + with open(txt_file,'w') as f: + date = str(datetime.today().replace(microsecond=0)) + f.write(f"{measurement_name} (Stress) at {date}:"+"\n") + f.write("Sample Information\n") + f.write(f"Processing Number:{processing_nr}"+"\n") + f.write(f"Sample Series:{sample_series}"+"\n") + f.write(f"Field:{field}"+"\n") + f.write(f"DUT:{dut}"+"\n") + f.write("\n") + f.write(f"Measurement parameters can be found at: {ini_file}"+"\n") + f.write("\nNo Results Available\n") + + #export the interface to ini file + config = configparser.ConfigParser() + with open(ini_file,'w') as configfile: + config.add_section("THESE ARE THE PARAMETERS OF THE CORRESPONDING MEASUREMENT") + config.add_section("THE WHOLE INTERFACE IS SAVED SO FIND WHAT YOU NEED") + config.add_section("DO NOT MODIFY THIS FILE") + config.add_section(f"MEASUREMENT MODE: {measurement_mode}") + + # First the smus + config.add_section("SMUS") + for i, smu in enumerate(smus): + config.add_section(f"SMU{i+1}") + for key,value in smu.items(): + config.set(f"SMU{i+1}",key,str(value)) + + config.add_section('PARAMETERS') + config.set('PARAMETERS', "STRESS TIME",str(duration)) + config.set('PARAMETERS', "HOLD TIME",str(hold_time)) + config.set('PARAMETERS', "FILTER",str(filter)) + + + # Now the constant smus + config.add_section('CONSTANT SMUS') + for i, cons_smu in enumerate(cons_smus): + config.add_section(f"CONSTANT SMU{i+1}") + for key, value in cons_smu.items(): + config.set(f"CONSTANT SMU{i+1}",key,str(value)) + config.write(configfile) + + # End of fuction + information_box("Measurement finished!") + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = False + ini.disabled = False + return # just to be sure + +# This should be tested at the end. +# After the ini files have been created +""" +def on_ini_clicked(b): + with output: + clear_output() + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = True + ini.disabled = True + + #load values to the interface + config = configparser.ConfigParser() + try: + file = load_ini() + except Exception as e: + error_box(e) + change_state(first_page,second_page,third_page,fourth_page,fifth_page) + start.disabled = False + ini.disabled = False + return + try: + # Now we do exactly the opposite thing dictionaries to widgets + #read the values from each section + config.read(file) + + # Get the sections + sections = config.sections() + # Get the measurement Mode + measurement_mode = get_mode(sections) + + # Get the constant smus + for j in range(1,5): + third_page[21,j].value = config.get(f"CONSTANT SMU{j}",'value') + third_page[22,j].value = config.get(f"CONSTANT SMU{j}",'comp') + + + if measurement_mode == 'SWEEP': + third_page[0,0].value = measurement_mode + third_page[0,1].value = get_integration(sections) + +""" + +start.on_click(on_start_clicked) \ No newline at end of file diff --git a/hp4155/hp4155a.py b/hp4155/hp4155a.py index 854256c..a47efe5 100644 --- a/hp4155/hp4155a.py +++ b/hp4155/hp4155a.py @@ -22,16 +22,37 @@ class HP4155a(object): command = f":PAGE:STR:SMU{number}:MODE {smu['mode']};FUNC {smu['func']};NAME {repr(smu['name'])}" self.inst.write(command) + def setup_smu_stress(self,number:int,smu:dict): command =f":PAGE:STR:SET:CONS:SMU{number} {smu['value']}" self.inst.write(command) command = f":PAGE:STR:SET:CONS:SMU{number}:COMP {smu['comp']}" self.inst.write(command) - + + def smu_stress_disable(self,number:int): + command = f":PAGE:STR:SMU{number}:DIS" + self.inst.write(command) + + def stress_filter(self,status:int): + command = f":PAGE:STR:SET:FILT {status}" + self.inst.write(command) + + def stress_hold_time(self,time:float): + command = f":PAGE:STR:SET:HTIM {time}" + self.inst.write(command) + #set the stess time in seconds def stress_time(self,time:float): command = f":PAGE:STR:SET:DUR {time}" self.inst.write(command) + + def stress_disable_not_smu(self): + self.inst.write(":PAGE:STR:VSU1:DIS") + self.inst.write(":PAGE:STR:VSU2:DIS") + + def stress_smu_disable(self,number:int): + command = f":PAGE:STR:SMU{number}:DIS" + self.inst.write(command) #start stress operation def start_stress(self): @@ -58,7 +79,17 @@ class HP4155a(object): def clear_error_stack(self): code,_ = self.error() while code != 0: - code,_ = self.error() + code,_ = self.error() + + def list_all_errors(self): + counter = 0 + error_code, error_message = self.error() + message = f"{error_code},{error_message}"+"\n" + while error_code!=0: + counter = counter + 1 + error_code, error_message = self.error() + message = message + f"{error_code},{error_message}"+"\n" + return counter, message def operation_completed(self): text = self.inst.query('*OPC?') @@ -188,6 +219,11 @@ class HP4155a(object): command = f":PAGE:DISP:GRAP:{axis}:NAME?" var_name = self.inst.query(command) return var_name.rstrip() #remove \n + + def get_axis_scale(self,axis): + command = f":PAGE:DISP:GRAP:{axis}:SCAL?" + scale = self.inst.query(command) + return scale.rstrip() def autoscaling(self): self.inst.write(":PAGE:GLIS:SCAL:AUTO ONCE") -- GitLab