From a7366ba88fdec255d1e9ef83aaf8858189efeb33 Mon Sep 17 00:00:00 2001 From: adrian sidhu Date: Wed, 3 Jun 2026 11:51:14 +0200 Subject: [PATCH] python files from Jamie --- .DS_Store | Bin 0 -> 6148 bytes automated-iv-curves/Keithley196_commands.py | 52 +++++ automated-iv-curves/README.md | 4 + .../SIM900-SIM928-Keithly196-IV.py | 221 ++++++++++++++++++ automated-iv-curves/SIM900_commands.py | 43 ++++ automated-iv-curves/SIM928_commands.py | 30 +++ 6 files changed, 350 insertions(+) create mode 100644 .DS_Store create mode 100755 automated-iv-curves/Keithley196_commands.py create mode 100755 automated-iv-curves/README.md create mode 100755 automated-iv-curves/SIM900-SIM928-Keithly196-IV.py create mode 100755 automated-iv-curves/SIM900_commands.py create mode 100755 automated-iv-curves/SIM928_commands.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..42647e808c16131cf0920b9af917042f178d857c GIT binary patch literal 6148 zcmeHKPfrs;6n_InwxF^=t3Wi_*oz5>l@g6HhFT!n7z3dMLBP7Z9m>jfr`g@M0wL*H zPksPDfLBi@UOam7>dp8O^y*39{Au`8O}Jpx>`P{TZ)Vp&f| z*v1hbH3~uAWwzxq3qAODk;5|nLkMcej$$=}s*3W~s=7`PrqiEDM`x_-RJYcx^=j*d zP1Y!cW>^byroYNti;THeOE;}0hc=3OVw!oO>3Gx^4o-=p$=XB5E3rn7)x1(5d<$|w zi)-y7vHo<4K7PwyWX>`(M3lgV@EhlWQ+E)?}a%e1%3K3~T) zbD5uWYjm~jJEcO^v>mU!9MWfD!P%ijec*`c0xdn>?^@ihqD^AILxj8QQ|gI$a~m50 z4VMq-D@DC8uzlvb^DJ;ehj}Y&0ltNNNGo}7gW-&Id;@b;J`^lbKR|5rSza-H-}K5f zx4rI~7Io*m91BBM;g&p_&AfS^@<_`L>U=G!sc(|wRQ7&LxhxeOQc}UQDACdslGOW3 zQ|OBx^JqMt%Fe4H4MyP(Jb)$Gf*p7Xui!PjgAec-zQT9XO@_%OGD;@NZ8Amfkvw@s zY_drl@>KeFv_650DxLxopmNMlpkDAjmw9FM;L+l+0JHc^Gx$`qFpeo#I+9p#(CT1f zVe@1}El%q_neb86h=GWKf0zNjKNwgzW)(Iiimd}HaRoq(VYd*}`Og9OF%)JMHYMT+ z3X!ge(v_$y29fUAZz#L0!lp#&4n)k1an#I2-B5^_9s3Po4kRlvsv`y>1`adODVsV! z|7U-H|3B;$wIT*02L3AsNNlDs!>{w|Y;9W{KWhyvPq1*|c1?-G1uJzN3xyxWLs*31 an86jqtiq;5>_Lfs1OyFH6*2Hf8Tbj#G4}=l literal 0 HcmV?d00001 diff --git a/automated-iv-curves/Keithley196_commands.py b/automated-iv-curves/Keithley196_commands.py new file mode 100755 index 0000000..ed3d526 --- /dev/null +++ b/automated-iv-curves/Keithley196_commands.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Oct 05 14:27:45 2022 + +@author: Jamie +""" +# Function for controlling Keithly-196 for electrical measurements + +# Function to output voltage in Volts +# Keithley is the VISA resource for the Keithley-196 +def Voltage(Keithley): + Voltage_string = Keithley.read() + Voltage = float(Voltage_string[4:]) + return Voltage + +# Function to set measurement mode +# Keithley is the VISA resource for the Keithley-196 +# mode is the measurement mode +def Set_Mode(Keithley, mode): + if mode == "DC V": + mode_number = 0 + elif mode == "AC V": + mode_number = 1 + elif mode == "Ohm": + mode_number = 2 + elif mode == "DC I": + mode_number = 3 + elif mode == "DC I": + mode_number = 4 + elif mode == "AC I": + mode_number = 5 + elif mode == "ACV dB": + mode_number = 6 + elif mode == "Offset comp Ohm": + mode_number = 7 + Keithley.write("F"+str(mode_number)) + +# Function to set voltage range +# Keithley is the VISA resource for the Keithley-196 +# range_name is the number of the range setting (numbers in V) +def Set_Range(Keithley, range_name): + if range_name == "Auto": + range_number = 0 + elif range_name == "0.3": + range_number = 1 + elif range_name == "3": + range_number = 2 + elif range_name == "30": + range_number = 3 + elif range_name == "300": + range_number = 4 + Keithley.write("R"+str(range_number)+"X") \ No newline at end of file diff --git a/automated-iv-curves/README.md b/automated-iv-curves/README.md new file mode 100755 index 0000000..367dcc4 --- /dev/null +++ b/automated-iv-curves/README.md @@ -0,0 +1,4 @@ +# FTIR-IV +Script for IV measurements of samples in FTIR cryostat at PSI + +To use, run `SIM900-SIM928-Keithly196-IV.py` diff --git a/automated-iv-curves/SIM900-SIM928-Keithly196-IV.py b/automated-iv-curves/SIM900-SIM928-Keithly196-IV.py new file mode 100755 index 0000000..aef57df --- /dev/null +++ b/automated-iv-curves/SIM900-SIM928-Keithly196-IV.py @@ -0,0 +1,221 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Oct 10 16:05:44 2022 + +@author: gac-x01dc +""" + +# Program to do I-V measurements +# Using SIM928 voltage source within SIM900 mainframe +# Using Keithley196 for voltage measurement + +import time +import datetime +import os.path +import numpy as np +import matplotlib.pyplot as plt +import pyvisa +rm = pyvisa.ResourceManager() + +import SIM900_commands as SIM900_comm +import SIM928_commands as SIM928_comm +import Keithley196_commands as Keithley_comm + +Keithley_address = 'GPIB0::6::INSTR' +SIM900_address = 'TCPIP0::ir-moxa01.psi.ch::3002::SOCKET'#'ASRL1::INSTR' + +Keithley = rm.open_resource(Keithley_address) +SIM900 = rm.open_resource(SIM900_address) +SIM928_port = 1 + +# Initialise Keithley +Keithley_comm.Set_Mode(Keithley, "DC V") +Keithley_comm.Set_Range(Keithley, "Auto") + +# Initialise SIM900 +SIM900_comm.flush_main(SIM900) +SIM900_comm.reset(SIM900) +SIM900_comm.flush_port(SIM900, SIM928_port) + +# Function to read the voltage set by a voltage source +def read_source_V(): + count = 0 + voltage = SIM900_comm.send_query(SIM900, SIM928_port, SIM928_comm.read_voltage(), 128) # get voltage + print("returned data: "+voltage) + while (count<10) and (type(voltage)!= float): + try: + voltage = float(voltage) + except: + print('no float') + voltage = SIM900_comm.send_query(SIM900, SIM928_port, SIM928_comm.read_voltage(), 128) # get voltage + print("returned data: "+voltage) + count = count + 1 + return voltage + +#print(read_source_V()) + +# Function to set voltage of SIM928 +def set_voltage(V_value=1e-3): + if V_value > 20: + print ('Voltage setting too high (>10V), it is set to 10V') + V_value = 20 + elif V_value < -20: + print ('Voltage setting too high (<-10V), it is set to -10V') + V_value = -20 + SIM900_comm.send_command(SIM900, SIM928_port, SIM928_comm.set_voltage(V_value)) # get voltage + time.sleep(1) +# count = 0 +# while (count<10) and (read_source_V()!= V_value): +# print('Setting voltage again') +# time.sleep(1) #wait 1 second +# SIM900_comm.send_command(SIM900, SIM928_port, SIM928_comm.set_voltage(V_value)) # get voltage +# count = count + 1 + SIM900_comm.send_command(SIM900, SIM928_port, SIM928_comm.V_source_state("on")) + print('Voltage set to ' + str(V_value)) +# return read_source_V() + +# Function to measure the current from the Keithley voltmeter +# time_average is the averaging time, time_interval is the time between points +# Could also use "Filter (P)" command to set the Keithley's internal averaging time +def measure_V(time_average=10, time_interval=0.2): + N_points = time_average/time_interval + 1 + V_list = [] + i = 1 + Keithley_comm.Set_Range(Keithley, "Auto") #set auto range + time.sleep(time_interval) + while i < N_points: + V_list.append(Keithley_comm.Voltage(Keithley)) #read the voltage + time.sleep(time_interval) + i = i+1 + V_avg_ = np.mean(V_list, dtype=np.float32) + V_std_ = np.std(V_list, dtype=np.float32) + V_err_ = V_std_/(N_points)**(1/2) + return [V_list, V_avg_, V_err_] + + +# Function to do I-V measurements +# Currently doesn't read the voltage that was set, due to issues with querying through mainframe +# Errors caluclated incorrectly +def V_scan(V_start=1e-3, step_size=1e-3, V_end=2e-3, time_average=10, sample_name='no_name', contact_1 = '', contact_2 = '', light = 'Dark', temp=777, amplifier='SR570', gain=10**3, number_of_loops=0): + print('Measurement started: %s'\ + %(datetime.datetime.now().strftime("%Hh%Mm%Ss"))) + V_scan_list = np.linspace(V_start, V_end, int(abs(V_start-V_end)/step_size)+1) + for n_l in range(number_of_loops): + V_scan_list = np.append(V_scan_list,np.linspace(V_end, V_start, (abs(V_start-V_end)/step_size)+1)) + print(V_scan_list) + SIM900_comm.send_command(SIM900, SIM928_port, SIM928_comm.V_source_state("off")) #set source off + # open a file to write data to + date_today = datetime.date.today() #date for making a directory + file_path = "C:/Jamie/IV-Data/" + str(date_today) + "/" + if not os.path.exists(file_path):#make the directory if it is not there + os.mkdir(file_path) + time_start = datetime.datetime.now().strftime("%Hh%Mm%Ss") + filename_root = "%s_%s_%s_%s_%s_scan_at_%sK_%s" % \ + (time_start, sample_name, contact_1, contact_2, light, temp, amplifier) + filename = filename_root + "_averaging%ss.txt" % (time_average) + plotname = filename_root + "_averaging%ss.png" % (time_average) + filename2 = filename_root + "_raw.txt" + full_name = os.path.join(file_path, filename) + full_plotname = os.path.join(file_path, plotname) + full_name2 = os.path.join(file_path, filename2) + #data_file=open(str(full_name),'w') + with open(str(full_name),'w') as data_file, open(str(full_name2),'w') as raw_V_file: + data_file.write("Time\tVoltage_source[V]\tVoltage_meas[V]\tVoltage_meas_err[V]\tGain[V/A]\n") + raw_V_file.write("Voltage_source[V]\tVoltage_meas[V]\n") + # just for ploting here + V_plot_list = [] + V_err_plot_list = [] + V_scan_plot_list = [] + # A loop over all currents + + i=0 + set_voltage(V_scan_list[0]) + time.sleep(5) #wait 3 sec to avoid large noise on first measurement + + for V_scan_item in V_scan_list: + time_start_iteration= datetime.datetime.now() + i = i+1 + set_voltage(V_scan_item) + time.sleep(2) #wait 5 sec for signal to settle + # measure+read+write the data + V_source = V_scan_item +# V_source = read_source_V() + [V_list, V_avg, V_err] = measure_V(time_average=time_average, time_interval=0.2) + time_now = datetime.datetime.now() + data_string = "%s\t %s\t %.5e\t %.5e\t %s\n"\ + %(time_now, V_source, V_avg, V_err, gain) + data_file.write(str(data_string)) + raw_V_file.write("%s\t %s\n" %(V_source, V_list)) + V_plot_list.append(V_avg) + V_err_plot_list.append(V_err) + V_scan_plot_list.append(V_source) + + #make plot + plt.close() + plt.plot(V_scan_plot_list, V_plot_list,'+') + plt.title(str(sample_name)) + plt.xlabel('Source Voltage / V') + plt.ylabel('Measured voltage / V') + plt.draw() + plt.pause(0.1) + plt.show(block=False) + iteration_time = datetime.datetime.now()-time_start_iteration + remaining_time = iteration_time.total_seconds()*(len(V_scan_list)-i) + print(time.strftime('remaining time: %H:%M:%S',\ + time.gmtime(remaining_time))) + + I_list = [V_plot_list[i]/gain for i in range(len(V_plot_list))] + I_err_list = [V_err_plot_list[i]/gain for i in range(len(V_plot_list))] + plt.close() + plt.figure() + plt.errorbar(V_scan_plot_list, I_list, yerr=I_err_list, fmt='+') + plt.title(str(sample_name)) + plt.xlabel('Source Voltage / V') + plt.ylabel('I / A') + plt.tight_layout() + plt.grid() + + plt.savefig(full_plotname) + plt.draw() + plt.pause(0.001) + plt.show(block=False) + plt.close() + + data_file.close() + raw_V_file.close() + set_voltage(0) + SIM900_comm.send_command(SIM900, SIM928_port, SIM928_comm.V_source_state("off")) #set source off + print('Source turned off') + print('Measurement finished: %s'\ + %(datetime.datetime.now().strftime("%Hh%Mm%Ss"))) + return + +# V_start, step size, V_end, time average, sample name, contact 1, contact 2, light, temperature, amplifier, amplifier gain +#V_scan(-1.0, 0.05, 1.0, 1.0, "test", "contact1", "contact2", "MIR", 300, "SR570", 10**(7)) #amplifier sensitivity = 1/gain +#V_scan(-1.0, 0.1, 1.0, 1.0, "SHADWELL", "posContactC", "negContactF", "MIR", 18, "SR570", 10**(8)) #amplifier sensitivity = 1/gain +#V_scan(0.0, 0.1, 1.0, 1.0, "Sun", "posContactB", "negContactH", "MIR", 18, "SR570", 10**(9)) #amplifier sensitivity = 1/gain +#V_scan(0.0, 0.2, -2.0, 1.0, "Sun", "posContactB", "negContactH", "MIR", 18, "SR570", 10**(9)) #amplifier sensitivity = 1/gain +#V_scan(0.0, 0.2, 2.0, 1.0, "Sun", "posContactB", "negContactH", "MIR", 18, "SR570", 10**(9)) #amplifier sensitivity = 1/gain +#V_scan(0.0, 0.3, -3.0, 1.0, "Sun", "posContactB", "negContactH", "MIR", 18, "SR570", 10**(9)) #amplifier sensitivity = 1/gain +#V_scan(0.0, 0.3, 3.0, 1.0, "Sun", "posContactB", "negContactH", "MIR", 18, "SR570", 10**(9)) #amplifier sensitivity = 1/gain +#V_scan(0.0, 0.5, -5.0, 1.0, "Sun", "posContactB", "negContactH", "MIR", 18, "SR570", 10**(9)) #amplifier sensitivity = 1/gain +#V_scan(0.0, 0.5, 5.0, 1.0, "Sun", "posContactB", "negContactH", "MIR", 18, "SR570", 10**(9)) #amplifier sensitivity = 1/gain + +""" +# Set up the plot +plt.ion() # Enable interactive mode +figure, ax = plt.subplots(figsize=(10, 6)) # Create a figure and an axes +ax.set_title("Live Number Update") + +# Loop to update the number and plot every second +for _ in range(9999): # Limiting to 10 iterations for demonstration + number = Keithley_comm.Voltage(Keithley) + # Create a new plot for the current number + figure, ax = plt.subplots(figsize=(10, 6)) + ax.text(0.5, 0.5, str(number), fontsize=100, ha='center') # Display the number in the center + ax.set_title(f"Number: {number}") + ax.axis('off') # Turn off the axis + + plt.show() # Show the plot + time.sleep(2) # Pause for a second +""" diff --git a/automated-iv-curves/SIM900_commands.py b/automated-iv-curves/SIM900_commands.py new file mode 100755 index 0000000..34e6c94 --- /dev/null +++ b/automated-iv-curves/SIM900_commands.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Oct 10 12:41:44 2022 + +@author: gac-x01dc +""" + +# Sends a command to the instrument +# SIM900 is the VISA resource for the SIM900 +# port_number is the port number of the instrument to be controlled +# instrument_command is the command to be sent +def send_command(SIM900, port_number, instrument_command): + SIM900_command = "SNDT "+str(port_number)+", \""+instrument_command+"\"" + SIM900.write(SIM900_command) + +# Send a query to the instrument and get a response - CURRENTLY BROKEN +# SIM900 is the VISA resource for the SIM900 +# port_number is the port number of the instrument to be controlled +# instrument_query is the query to be sent +# response_bytes is the maximum number of bytes in the response +def send_query(SIM900, port_number, instrument_query, response_bytes=7): + send_command(SIM900, port_number, instrument_query) + SIM900_command = "GETN? "+str(port_number)+", " + str(response_bytes) + full_response = SIM900.query(SIM900_command) + print("full_response="+full_response) + response = full_response[5:] + print("response="+response) + return(response) + +# Flushes input & output buffers of specified port +def flush_port(SIM900, port_number): + SIM900_command = "FLSH "+str(port_number) + SIM900.write(SIM900_command) + +# Flushes output queue of mainframe +def flush_main(SIM900): + SIM900_command = "FLOQ" + SIM900.write(SIM900_command) + +# Resets all ports +def reset(SIM900): + SIM900_command = "SRST" + SIM900.write(SIM900_command) \ No newline at end of file diff --git a/automated-iv-curves/SIM928_commands.py b/automated-iv-curves/SIM928_commands.py new file mode 100755 index 0000000..5bb1883 --- /dev/null +++ b/automated-iv-curves/SIM928_commands.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Oct 10 15:39:12 2022 + +@author: gac-x01dc +""" + +# Program containing functions with commands for SIM928 voltage source + +# Turns source on/off +def V_source_state(on_off = 'off'): + command = 'EXON ' + on_off + return(command) + +# Set voltage of SIM928 +def set_voltage(V_value=1e-3): + if abs(V_value) < 1e-3: + print ('Voltage too low (<|3 mV|), it is set to 0 mV') + V_value = 0 + elif abs(V_value) > 39: + print ('Voltage setting too high (>39V), it is set to 39V') + V_value = 40e-3 + V_value = round(V_value,3) # rounding to mV, otherwise the source does not set the voltage because it lacks the precision + command = 'VOLT ' + str(V_value) # set voltage + return(command) + +# Read voltage set by voltage source +def read_voltage(): + command = 'VOLT?' + return(command) \ No newline at end of file