Merge pull request 'Jamie's code updated to work with GPIB ethernet' (#1) from apjs-automated-iv-curves into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
# ======================
|
||||
# uv environments
|
||||
# ======================
|
||||
**/.venv/
|
||||
.uv-cache/
|
||||
|
||||
# ======================
|
||||
# Python
|
||||
# ======================
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
|
||||
# ======================
|
||||
# Jupyter
|
||||
# ======================
|
||||
.ipynb_checkpoints/
|
||||
**/.ipynb_checkpoints/
|
||||
|
||||
# ======================
|
||||
# OS files
|
||||
# ======================
|
||||
.DS_Store # macOS
|
||||
Thumbs.db # Windows
|
||||
|
||||
# ======================
|
||||
# Editor
|
||||
# ======================
|
||||
.vscode/settings.json
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# ======================
|
||||
# Measurement data
|
||||
# ======================
|
||||
data/
|
||||
@@ -2,4 +2,290 @@
|
||||
|
||||
|
||||
### General rules
|
||||
Please use initials and kebab case when opening a branch. For example if your name is John Doe, name your branch jd-branch-name.
|
||||
Please use initials and kebab case when opening a branch. For example if your name is John Doe, name your branch jd-branch-name.
|
||||
|
||||
# Python Environments with `uv` — Lab Repo Guide (Written by Claude AI)
|
||||
|
||||
---
|
||||
|
||||
## Installing `uv`
|
||||
|
||||
`uv` is a modern Python package and environment manager — much faster than `pip` and handles everything in one tool (environments, dependencies, Python versions). Install it with:
|
||||
|
||||
**macOS / Linux:**
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.sh | iex"
|
||||
```
|
||||
|
||||
After installing, restart your terminal and verify it worked:
|
||||
```bash
|
||||
uv --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## If you have other Python installations on your machine
|
||||
|
||||
If you installed Python previously (via `python.org`, `brew`, `conda`, `pyenv`, etc.), those installations still exist and your terminal may default to one of them. This is fine — `uv` does not conflict with them. However, to avoid confusion:
|
||||
|
||||
- **Do not use `pip install` globally anymore.** It installs packages into your system Python, which creates a mess over time. Use `uv add` inside a project instead (see below).
|
||||
- **Check what Python your terminal defaults to** at any time with `which python` or `python --version`. This tells you what runs when you type `python` without using `uv`.
|
||||
- **`uv` manages its own Python versions independently.** When you pin a project to Python 3.12, `uv` will download and use that version for the project regardless of what Python is installed system-wide. You don't need to uninstall anything.
|
||||
|
||||
If you want to see all Python versions `uv` knows about:
|
||||
```bash
|
||||
uv python list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Philosophy
|
||||
|
||||
Each project subfolder gets its own fully isolated environment — its own packages, its own Python version. Nothing bleeds between projects.
|
||||
|
||||
You always stay at the **repo root** for Git operations and use `--project` to point `uv` at the right subfolder. This way `git push`, `git pull`, and `git status` always work from one place.
|
||||
|
||||
```
|
||||
INSTRUMENT-CONTROL/
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
├── 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)
|
||||
│ └── .venv/ ← the actual environment (gitignore this)
|
||||
└── ptis-analysis/
|
||||
├── pyproject.toml
|
||||
├── uv.lock
|
||||
├── .python-version
|
||||
└── .venv/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Which Python version to use
|
||||
|
||||
Use **Python 3.12** for all new projects.
|
||||
|
||||
Python 3.9 reached end-of-life in October 2025 and no longer receives security patches. Python 3.12 is fully supported by the entire scientific stack (numpy, scipy, matplotlib, pyvisa, etc.) and has meaningful performance improvements. Avoid the absolute latest release (e.g. 3.14) as some libraries take a few months to catch up.
|
||||
|
||||
Pick one version and stick to it across all projects unless you have a specific reason not to — it makes debugging much simpler.
|
||||
|
||||
---
|
||||
|
||||
## Setting up a new project
|
||||
|
||||
From the **repo root**, run:
|
||||
|
||||
```bash
|
||||
uv init --python 3.12 your-project-folder
|
||||
```
|
||||
|
||||
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`
|
||||
|
||||
The `.venv/` folder doesn't exist yet — it gets created the first time you add a package or run something.
|
||||
|
||||
---
|
||||
|
||||
## Adding packages
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Running a script
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Running Jupyter
|
||||
|
||||
First add Jupyter to the project:
|
||||
```bash
|
||||
uv --project your-project-folder add jupyter
|
||||
```
|
||||
|
||||
Then activate the environment and launch:
|
||||
```bash
|
||||
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 `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.
|
||||
|
||||
---
|
||||
|
||||
## Checking the Python version
|
||||
|
||||
To confirm which Python a project is using:
|
||||
```bash
|
||||
uv --project your-project-folder run python --version
|
||||
|
||||
# or just read the pin file directly:
|
||||
cat your-project-folder/.python-version
|
||||
```
|
||||
|
||||
To change it:
|
||||
```bash
|
||||
uv --project your-project-folder python pin 3.12
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The full daily workflow
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
git pull
|
||||
|
||||
# Add a new package
|
||||
uv --project your-project-folder add pyvisa
|
||||
|
||||
# Run your script
|
||||
uv --project your-project-folder run python your-project-folder/measure.py
|
||||
|
||||
# Commit the updated dependency files
|
||||
git add your-project-folder/pyproject.toml your-project-folder/uv.lock
|
||||
git commit -m "add pyvisa dependency"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Syncing after a clone or pull
|
||||
|
||||
The `.venv/` folder is gitignored, so it doesn't exist on a fresh clone. `uv sync` recreates it from `uv.lock`:
|
||||
|
||||
```bash
|
||||
uv --project your-project-folder sync
|
||||
```
|
||||
|
||||
You need this in two situations:
|
||||
1. **Fresh clone on a new machine** — `.venv/` doesn't exist yet
|
||||
2. **After a `git pull`** where someone else added a dependency — your `.venv/` is now out of date
|
||||
|
||||
If you're the one adding packages with `uv add`, you never need to sync — it handles installation automatically.
|
||||
|
||||
> **Mental model:** `uv.lock` is the source of truth that lives in Git. `.venv/` is a local build of it. `uv sync` = "rebuild `.venv/` from `uv.lock`."
|
||||
|
||||
---
|
||||
|
||||
## Why `.venv/` is not committed
|
||||
|
||||
`.venv/` contains compiled binaries and symlinks built specifically for your OS and CPU. If you pushed it and pulled it on a different machine, it simply wouldn't work. It's also hundreds of MB — Git isn't designed for that.
|
||||
|
||||
`pyproject.toml` and `uv.lock` together are everything needed to recreate the exact same environment anywhere:
|
||||
|
||||
| File | Commit? | Why |
|
||||
|---|---|---|
|
||||
| `pyproject.toml` | ✅ Yes | Declares your dependencies |
|
||||
| `uv.lock` | ✅ Yes | Locks exact versions — guarantees reproducibility |
|
||||
| `.python-version` | ✅ Yes | Pins the Python version |
|
||||
| `.venv/` | ❌ No | Local build artifact — regenerated from the above |
|
||||
|
||||
---
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Task | Command |
|
||||
|---|---|
|
||||
| Create new project | `uv init --python 3.12 <project>` |
|
||||
| Add a package | `uv --project <project> add <package>` |
|
||||
| Run a script | `uv --project <project> run python <script>` |
|
||||
| Activate interactively | `source <project>/.venv/bin/activate` |
|
||||
| Deactivate | `deactivate` |
|
||||
| Sync after clone/pull | `uv --project <project> sync` |
|
||||
| Check Python version | `uv --project <project> run python --version` |
|
||||
| Pin Python version | `uv --project <project> python pin 3.12` |
|
||||
| List available Pythons | `uv python list` |
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
# `.gitignore` — Only read this if you are setting up a new repo
|
||||
|
||||
The `.gitignore` file tells Git which files to never track. It lives at the repo root and covers all subfolders. You only need to set this up once per repo.
|
||||
|
||||
## Why you need it
|
||||
|
||||
Without a `.gitignore`, Git will try to track everything — including `.venv/`, which contains thousands of compiled files you never want in version control (see above). VS Code's source control badge will show thousands of "changes" that aren't really changes, making it impossible to see what's actually going on.
|
||||
|
||||
## The `.gitignore` for this repo
|
||||
|
||||
Create a file called `.gitignore` at the repo root (next to your project folders, not inside them) with this content:
|
||||
|
||||
```gitignore
|
||||
# uv environments
|
||||
**/.venv/
|
||||
.uv-cache/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
*.pyd
|
||||
|
||||
# Jupyter
|
||||
.ipynb_checkpoints/
|
||||
**/.ipynb_checkpoints/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor
|
||||
.vscode/settings.json
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Measurement data (uncomment if needed)
|
||||
# *.csv
|
||||
# *.dat
|
||||
# *.hdf5
|
||||
# *.h5
|
||||
```
|
||||
|
||||
The `**/.venv/` pattern uses a wildcard so it catches `.venv/` inside *any* subfolder automatically — you don't need to update it when you add a new project.
|
||||
|
||||
## Committing the `.gitignore`
|
||||
|
||||
Yes, the `.gitignore` itself should be committed:
|
||||
|
||||
```bash
|
||||
git add .gitignore
|
||||
git commit -m "add .gitignore"
|
||||
```
|
||||
|
||||
It's not sensitive or machine-specific — committing it means any collaborator or future clone automatically gets the same rules.
|
||||
|
||||
## If Git is already tracking `.venv/`
|
||||
|
||||
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 your-project-folder/.venv/
|
||||
git commit -m "untrack .venv"
|
||||
```
|
||||
|
||||
The `--cached` flag removes it from Git tracking only — the actual folder stays on disk and your environment keeps working.
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
3.12
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
# -*- 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")
|
||||
Executable
+4
@@ -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`
|
||||
Executable
+43
@@ -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)
|
||||
Executable
+30
@@ -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 ('\nVoltage too low (<|3 mV|), it is set to 0 mV')
|
||||
V_value = 0
|
||||
elif abs(V_value) > 39:
|
||||
print ('\nVoltage 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)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Experiment parameters
|
||||
[experiment]
|
||||
V_start = -0.1
|
||||
step_size = 0.01
|
||||
V_end = 0.1
|
||||
time_average = 2 # time over which to collect voltage average (time per measurement)
|
||||
sample_name = "barrucada?"
|
||||
contact_pos = "B"
|
||||
contact_neg = "C"
|
||||
light = "dark"
|
||||
temp = 300
|
||||
amplifier = "SR570"
|
||||
gain = 1e4 # amplifier gain
|
||||
|
||||
|
||||
# Instrument addresses: typically do NOT need to be changed
|
||||
[instruments]
|
||||
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
|
||||
|
||||
# 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
|
||||
@@ -0,0 +1,232 @@
|
||||
# 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
|
||||
import h5py
|
||||
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
|
||||
|
||||
with open("automated-iv-curves/config.toml", "rb") as f:
|
||||
cfg = tomllib.load(f)
|
||||
|
||||
Keithley = rm.open_resource(cfg["instruments"]["keithley_address"])
|
||||
SIM900 = rm.open_resource(cfg["instruments"]["SIM900_address"])
|
||||
|
||||
# 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
|
||||
SIM928_port = cfg["instruments"]["SIM928_port"]
|
||||
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
|
||||
|
||||
|
||||
# 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)
|
||||
SIM900_comm.send_command(SIM900, SIM928_port, SIM928_comm.V_source_state("on"))
|
||||
print('\nVoltage set to ' + str(V_value) + " 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_pos = '', contact_neg = '', light = 'Dark', temp=777, amplifier='SR570', gain=10**3, number_of_loops=0):
|
||||
Keithley.clear() # clear the buffer
|
||||
gain = int(gain) # make sure the gain is an Int
|
||||
|
||||
data_id = datetime.datetime.now().strftime("%d%m%Y_%H%M%S")
|
||||
|
||||
print('\nStarting measurement.\nMeasurement ID: %s\n' %data_id)
|
||||
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("Voltage scanning list:")
|
||||
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
|
||||
file_path = Path(__file__).parent / "data"
|
||||
filename_root = "iv_%s_%s_pos%s_neg%s_%s_scan_at_%sK_%s" % \
|
||||
(data_id, sample_name, contact_pos, contact_neg, 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')
|
||||
|
||||
# save metadata before loop
|
||||
file_path = Path(__file__).parent / "data"
|
||||
file_path.mkdir(exist_ok=True)
|
||||
h5_path = file_path / f"{filename[0:-4]}.h5"
|
||||
with h5py.File(h5_path, "w") as f:
|
||||
f.attrs["V_start"] = V_start
|
||||
f.attrs["step_size"] = step_size
|
||||
f.attrs["V_end"] = V_end
|
||||
f.attrs["time_average"] = time_average
|
||||
f.attrs["sample_name"] = sample_name
|
||||
f.attrs["contact_pos"] = contact_pos
|
||||
f.attrs["contact_neg"] = contact_neg
|
||||
f.attrs["light"] = light
|
||||
f.attrs["temp"] = temp
|
||||
f.attrs["amplifier"] = amplifier
|
||||
f.attrs["gain"] = gain
|
||||
|
||||
|
||||
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_avg[V]\tVoltage_meas_err[V]\tGain[V/A]\tVoltage_meas\n")
|
||||
# raw_V_file.write("Voltage_source[V]\tVoltage_meas[V]\n")
|
||||
|
||||
V_avg_list = []
|
||||
V_err_list = []
|
||||
V_source_list = []
|
||||
V_raw_lists = []
|
||||
time_list = []
|
||||
|
||||
i=0
|
||||
for V_scan_item in V_scan_list:
|
||||
Keithley.clear()
|
||||
time_start_iteration= datetime.datetime.now()
|
||||
|
||||
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\t %s\n"\
|
||||
%(time_now, V_source, V_avg, V_err, gain, V_list)
|
||||
data_file.write(str(data_string))
|
||||
# raw_V_file.write("%s\t %s\n" %(V_source, V_list))
|
||||
|
||||
V_avg_list.append(V_avg)
|
||||
V_err_list.append(V_err)
|
||||
V_source_list.append(V_source)
|
||||
V_raw_lists.append(V_list)
|
||||
time_list.append(time_now)
|
||||
|
||||
#make plot
|
||||
plt.close()
|
||||
plt.plot(V_source_list, V_avg_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)
|
||||
|
||||
i = i+1
|
||||
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_avg_list[i]/gain for i in range(len(V_avg_list))]
|
||||
I_err_list = [V_err_list[i]/gain for i in range(len(V_avg_list))]
|
||||
|
||||
plt.close()
|
||||
plt.figure()
|
||||
plt.errorbar(V_source_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()
|
||||
|
||||
file_path = Path(__file__).parent / "data"
|
||||
file_path.mkdir(exist_ok=True)
|
||||
|
||||
with h5py.File(file_path / f"{filename[0:-4]}.h5", "a") as f:
|
||||
f.create_dataset("V_source", data=V_source_list)
|
||||
f.create_dataset("V_avg", data=V_avg_list)
|
||||
f.create_dataset("V_err", data=V_err_list)
|
||||
f.create_dataset("V_raw", data=V_raw_lists)
|
||||
f.create_dataset("timestamps", data=[str(t) for t in time_list])
|
||||
f.create_dataset("I", data=I_list)
|
||||
f.create_dataset("I_err", data=I_err_list)
|
||||
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
# Run IV curves using the SIM900/SIM928 source and the Keithley 196 voltmeter
|
||||
|
||||
import tomllib
|
||||
from iv_functions import V_scan
|
||||
import h5py
|
||||
|
||||
with open("automated-iv-curves/config.toml", "rb") as f:
|
||||
cfg = tomllib.load(f)
|
||||
|
||||
V_scan(**cfg["experiment"])
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# -*- 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")
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
# -*- 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
|
||||
"""
|
||||
Executable
+43
@@ -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)
|
||||
Executable
+30
@@ -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)
|
||||
@@ -0,0 +1,17 @@
|
||||
[project]
|
||||
name = "automated-iv-curves"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"datetime>=5.5",
|
||||
"h5py>=3.14.0",
|
||||
"ipykernel>=6.31.0",
|
||||
"jupyter>=1.1.1",
|
||||
"matplotlib>=3.9.4",
|
||||
"numpy>=2.0.2",
|
||||
"pyvisa>=1.14.1",
|
||||
"pyvisa-py>=0.7.2",
|
||||
"scipy>=1.13.1",
|
||||
]
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
Generated
+4464
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user