diff --git a/README.md b/README.md index 6ab6139..33446d8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ You always stay at the **repo root** for Git operations and use `--project` to p INSTRUMENT-CONTROL/ ├── .gitignore ├── README.md -├── automated-iv-curves/ +├── your-project-folder/ │ ├── pyproject.toml ← declares your dependencies (commit this) │ ├── uv.lock ← locks exact versions (commit this) │ ├── .python-version ← pins the Python version (commit this) @@ -83,10 +83,10 @@ Pick one version and stick to it across all projects unless you have a specific From the **repo root**, run: ```bash -uv init --python 3.12 automated-iv-curves +uv init --python 3.12 your-project-folder ``` -This creates the following inside `automated-iv-curves/`: +This creates the following inside `your-project-folder/`: - `pyproject.toml` — the project's metadata and dependency list - `uv.lock` — a locked snapshot of the full dependency tree (including sub-dependencies) - `.python-version` — a one-line file that just says `3.12` @@ -98,7 +98,7 @@ The `.venv/` folder doesn't exist yet — it gets created the first time you add ## Adding packages ```bash -uv --project automated-iv-curves add numpy matplotlib scipy +uv --project your-project-folder add numpy matplotlib scipy ``` This does three things at once: updates `pyproject.toml` with the new dependencies, resolves the full dependency tree into `uv.lock`, and installs everything into `.venv/`. You never need to manually `pip install` anything. @@ -108,7 +108,7 @@ This does three things at once: updates `pyproject.toml` with the new dependenci ## Running a script ```bash -uv --project automated-iv-curves run python automated-iv-curves/measure.py +uv --project your-project-folder run python your-project-folder/measure.py ``` `uv run` automatically uses the project's environment — no need to activate anything first for one-off script runs. @@ -119,18 +119,18 @@ uv --project automated-iv-curves run python automated-iv-curves/measure.py First add Jupyter to the project: ```bash -uv --project automated-iv-curves add jupyter +uv --project your-project-folder add jupyter ``` Then activate the environment and launch: ```bash -source automated-iv-curves/.venv/bin/activate +source your-project-folder/.venv/bin/activate jupyter lab ``` You need to activate here (rather than use `uv run`) because Jupyter is an interactive session, not a one-off command. After activating, your terminal prompt will change to show `(.venv)` — this means all `python`, `jupyter`, and other commands will use the project's environment until you run `deactivate`. -Create or open any `.ipynb` file inside `automated-iv-curves/` and it will have access to all the project's packages. Before committing notebooks, clear the outputs (`Kernel → Restart & Clear Output`) to keep your Git diffs clean. +Create or open any `.ipynb` file inside `your-project-folder/` and it will have access to all the project's packages. Before committing notebooks, clear the outputs (`Kernel → Restart & Clear Output`) to keep your Git diffs clean. --- @@ -138,15 +138,15 @@ Create or open any `.ipynb` file inside `automated-iv-curves/` and it will have To confirm which Python a project is using: ```bash -uv --project automated-iv-curves run python --version +uv --project your-project-folder run python --version # or just read the pin file directly: -cat automated-iv-curves/.python-version +cat your-project-folder/.python-version ``` To change it: ```bash -uv --project automated-iv-curves python pin 3.12 +uv --project your-project-folder python pin 3.12 ``` --- @@ -158,13 +158,13 @@ uv --project automated-iv-curves python pin 3.12 git pull # Add a new package -uv --project automated-iv-curves add pyvisa +uv --project your-project-folder add pyvisa # Run your script -uv --project automated-iv-curves run python automated-iv-curves/measure.py +uv --project your-project-folder run python your-project-folder/measure.py # Commit the updated dependency files -git add automated-iv-curves/pyproject.toml automated-iv-curves/uv.lock +git add your-project-folder/pyproject.toml your-project-folder/uv.lock git commit -m "add pyvisa dependency" git push ``` @@ -176,7 +176,7 @@ git push The `.venv/` folder is gitignored, so it doesn't exist on a fresh clone. `uv sync` recreates it from `uv.lock`: ```bash -uv --project automated-iv-curves sync +uv --project your-project-folder sync ``` You need this in two situations: @@ -284,7 +284,7 @@ It's not sensitive or machine-specific — committing it means any collaborator If you set up the `.gitignore` after Git already started tracking `.venv/`, the file won't be automatically un-tracked — `.gitignore` only prevents *new* files from being added. To fix this: ```bash -git rm -r --cached automated-iv-curves/.venv/ +git rm -r --cached your-project-folder/.venv/ git commit -m "untrack .venv" ``` diff --git a/automated-iv-curves/config.toml b/automated-iv-curves/config.toml new file mode 100644 index 0000000..4f21591 --- /dev/null +++ b/automated-iv-curves/config.toml @@ -0,0 +1,19 @@ +# Experiment parameters +V_start = -1.0 +V_end = 1.0 +step_size = 0.05 +time_average = 0.1 +sample_name = "test" +contact_1 = "test1" +contact_2 = "test1" +light = "MIR" +temp = 300 +amplifier = "SR570" +gain = 1e7 +number_of_loops = 0 + + +# Instrument addresses: typically do NOT need to be changed +Keithley_address = 'TCPIP0::Prologix-00-21-69-01-4e-fb.psi.ch::1234::SOCKET' +SIM900_address = 'TCPIP0::ir-moxa01.psi.ch::3001::SOCKET'#'ASRL1::INSTR' +SIM928_port = 1 \ No newline at end of file diff --git a/automated-iv-curves/iv_functions.py b/automated-iv-curves/iv_functions.py index f478281..acf72a3 100644 --- a/automated-iv-curves/iv_functions.py +++ b/automated-iv-curves/iv_functions.py @@ -15,13 +15,9 @@ 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() @@ -30,9 +26,194 @@ 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 +Keithley.read() # Throwaway # Initialise SIM900 SIM900_comm.flush_main(SIM900) SIM900_comm.reset(SIM900) -SIM900_comm.flush_port(SIM900, SIM928_port) \ No newline at end of file +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 +""" diff --git a/old/Keithley196_commands.py b/automated-iv-curves/old/Keithley196_commands.py similarity index 100% rename from old/Keithley196_commands.py rename to automated-iv-curves/old/Keithley196_commands.py diff --git a/old/SIM900-SIM928-Keithly196-IV.py b/automated-iv-curves/old/SIM900-SIM928-Keithly196-IV.py similarity index 100% rename from old/SIM900-SIM928-Keithly196-IV.py rename to automated-iv-curves/old/SIM900-SIM928-Keithly196-IV.py diff --git a/old/SIM900_commands.py b/automated-iv-curves/old/SIM900_commands.py similarity index 100% rename from old/SIM900_commands.py rename to automated-iv-curves/old/SIM900_commands.py diff --git a/old/SIM928_commands.py b/automated-iv-curves/old/SIM928_commands.py similarity index 100% rename from old/SIM928_commands.py rename to automated-iv-curves/old/SIM928_commands.py