6 Commits

10 changed files with 5 additions and 491 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
# FTIR-IV
Script for IV measurements of samples in FTIR cryostat at PSI
Script for IV measurements using Keithley 196 DMM for voltage measurements, and the SIM900 rack with the SIM928 voltage source. Use `iv_run.py` to run the IV measurement.
To use, run `SIM900-SIM928-Keithly196-IV.py`
Note that the script is made specifically for these instruments. The command messages need to be changed if you would like to use it with other instruments. Command messages are found in the manuals of the corresponding instruments.
+3 -3
View File
@@ -13,9 +13,9 @@ from pathlib import Path
import tomllib
rm = pyvisa.ResourceManager()
import SIM900_commands as SIM900_comm
import SIM928_commands as SIM928_comm
import Keithley196_commands as Keithley_comm
from command_functions import SIM900_commands as SIM900_comm
from command_functions import SIM928_commands as SIM928_comm
from command_functions import Keithley196_commands as Keithley_comm
with open("automated-iv-curves/config.toml", "rb") as f:
cfg = tomllib.load(f)
@@ -1,53 +0,0 @@
# -*- 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):
Keithley.write("X\n")
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")
@@ -1,230 +0,0 @@
# -*- 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
from pathlib import Path
rm = pyvisa.ResourceManager()
import SIM900_commands as SIM900_comm
import SIM928_commands as SIM928_comm
import Keithley196_commands as Keithley_comm
Keithley_address = 'TCPIP0::Prologix-00-21-69-01-4e-fb.psi.ch::1234::SOCKET'
# Keithley_address = 'GPIB0::6::INSTR'
SIM900_address = 'TCPIP0::ir-moxa01.psi.ch::3001::SOCKET'#'ASRL1::INSTR'
Keithley = rm.open_resource(Keithley_address)
SIM900 = rm.open_resource(SIM900_address)
SIM928_port = 1
# Initialise Keithley
Keithley.clear()
Keithley.timeout = 5000
Keithley.read_termination = '\n'
Keithley.write("T5X\n") # Ensures the Keithley reads when it is triggered by "X" write
Keithley_comm.Set_Mode(Keithley, "DC V")
Keithley_comm.Set_Range(Keithley, "Auto")
Keithley.read() # Throwaway
# 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):
Keithley.clear()
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 = Path(__file__).parent / "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, "test", "test1", "test1", "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
"""
@@ -1,43 +0,0 @@
# -*- 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)
@@ -1,30 +0,0 @@
# -*- 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)
-130
View File
@@ -1,130 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "7e9d23ea",
"metadata": {},
"outputs": [],
"source": [
"# -*- coding: utf-8 -*-\n",
"\"\"\"\n",
"Created on Mon Oct 10 16:05:44 2022\n",
"\n",
"@author: gac-x01dc\n",
"\"\"\"\n",
"\n",
"# Program to do I-V measurements\n",
"# Using SIM928 voltage source within SIM900 mainframe\n",
"# Using Keithley196 for voltage measurement\n",
"\n",
"import time\n",
"import datetime\n",
"import os.path\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import pyvisa\n",
"from pathlib import Path\n",
"rm = pyvisa.ResourceManager()\n",
"\n",
"import SIM900_commands as SIM900_comm\n",
"import SIM928_commands as SIM928_comm\n",
"import Keithley196_commands as Keithley_comm\n",
"\n",
"Keithley_address = 'TCPIP0::Prologix-00-21-69-01-4e-fb.psi.ch::1234::SOCKET'\n",
"# Keithley_address = 'GPIB0::6::INSTR'\n",
"SIM900_address = 'TCPIP0::ir-moxa01.psi.ch::3001::SOCKET'#'ASRL1::INSTR'\n",
"\n",
"Keithley = rm.open_resource(Keithley_address)\n",
"SIM900 = rm.open_resource(SIM900_address)\n",
"SIM928_port = 1\n",
"\n",
"# Initialise Keithley\n",
"Keithley.clear()\n",
"Keithley.timeout = 5000\n",
"Keithley.read_termination = '\\n'\n",
"Keithley.write(\"T5X\\n\") # Ensures the Keithley reads when it is triggered by \"X\" write\n",
"Keithley_comm.Set_Mode(Keithley, \"DC V\")\n",
"Keithley_comm.Set_Range(Keithley, \"Auto\")\n",
"Keithley.read() # Throwaway\n",
"\n",
"# Initialise SIM900\n",
"SIM900_comm.flush_main(SIM900)\n",
"SIM900_comm.reset(SIM900)\n",
"SIM900_comm.flush_port(SIM900, SIM928_port)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "91371dcc",
"metadata": {},
"outputs": [],
"source": [
"V_value = -0.2\n",
"SIM900_comm.send_command(SIM900, SIM928_port, SIM928_comm.set_voltage(V_value)) # get voltage \n",
"\n",
"SIM900_comm.send_command(SIM900, SIM928_port, SIM928_comm.V_source_state(\"on\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5236610b",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 5,
"id": "9298c70d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"-0.2001732"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# time.sleep(0.1)\n",
"Keithley_comm.Voltage(Keithley)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19bb6089",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "automated-iv-curves",
"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.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}