This commit is contained in:
2026-06-03 15:24:23 +02:00
parent 878b8bc5b0
commit 835db02c5c
7 changed files with 222 additions and 22 deletions
+16 -16
View File
@@ -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"
```
+19
View File
@@ -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
+187 -6
View File
@@ -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)
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
"""