# ----------------------------------------------------
# Imports
# ----------------------------------------------------
import time
import pyvisa
from semi_cr.setup.juno.cqcc.utils.parser import CQCC_V1, Sample_simulator
# ----------------------------------------------------
# Constants
# ----------------------------------------------------
CQCC_V1_PATH = r"src\semi_cr\setup\juno\cqcc\conf\cqcc_conf\CQCC_V1.yaml"
SAMPLE_SIMULATOR_PATH = r"src\semi_cr\setup\juno\cqcc\conf\cqcc_conf\sample_simulator_settings.yaml"
DATA_PATH = r"C:\Users\Luke\NQCP-spin-git-SemiCR\src\semi_cr\setup\juno\cqcc\tests"
# ----------------------------------------------------
# Helper Classes
# ----------------------------------------------------
[docs]
class ConnectionError(Exception):
pass
# Dataclass for switch pair control struct
[docs]
class SwitchPairControlStruct:
exp_type: str = "K"
pin_list: list[int]
level_list: list[int]
num_pairs: int = 18 # this is always 18 for pair control (9 for positive, 9 for GND
tail: bool = False
# TODO: DATACLASSES to organise parameters for full-matrix
# ----------------------------------------------------
# Driver Class
# ----------------------------------------------------
[docs]
class Keithley_2636b_driver:
# ----------------------------------------------------
# Initialise, Connect, Disconnect
# ----------------------------------------------------
def __init__(self, address="TCPIP::169.254.25.152::INSTR"):
self.address = address
self.rm = pyvisa.ResourceManager()
self.device = None
self.source = "a" # defualt to be channel a
self.CQCC = CQCC_V1(CQCC_V1_PATH)
self.sample_simulator = Sample_simulator(SAMPLE_SIMULATOR_PATH)
self.data_path = DATA_PATH
self.connected = False
[docs]
def connect(self):
try:
self.device = self.rm.open_resource(self.address)
self.device.read_termination = "\n"
self.device.timeout = 10000 # Set timeout to 5 seconds
except pyvisa.VisaIOError as e:
raise ConnectionError(f"Connection error: {e}")
[docs]
def disconnect(self):
if self.device is not None:
self.device.close()
self.device = None
# TODO: Explore this func. more ...
# TODO: Consider the settling time - look at manual
[docs]
def set_measure(self, channel="a", **kwargs):
if self.device is None:
raise ConnectionError("Not connected to the device. Please call connect() first.")
try:
if channel not in {"a", "b"}:
raise ValueError("Channel must be 'a' or 'b'.")
if "nplc" in kwargs:
nplc = kwargs["nplc"]
self.device.write(f"smu{channel}.measure.nplc = {nplc}")
if "filter" in kwargs:
filter_type = kwargs["filter"]
self.device.write(f"smu{channel}.measure.filter.type = smu{channel}.{filter_type}")
self.device.write(f"smu{channel}.measure.filter.enable = smu{channel}.FILTER_ON")
else:
self.device.write(f"smu{channel}.measure.filter.enable = smu{channel}.FILTER_OFF")
if "filter_count" in kwargs:
filter_count = kwargs["filter_count"]
self.device.write(f"smu{channel}.measure.filter.count = {filter_count}")
except pyvisa.VisaIOError as e:
raise ConnectionError(f"Connection error: {e}")
except ValueError as e:
raise ValueError(f"Value error: {e}")
[docs]
def set_nplc(self, nplc=5):
if self.device is None:
raise ConnectionError("Not connected to the device. Please call connect() first.")
try:
self.device.write(f"smu{self.source}.measure.nplc = {nplc}")
except pyvisa.VisaIOError as e:
raise ConnectionError(f"Connection error: {e}")
[docs]
def set_source(self, channel="a", source="v", level=0, delay=0):
if self.device is None:
raise ConnectionError("Not connected to the device. Please call connect() first.")
try:
if channel not in {"a", "b"}:
raise ValueError("Channel must be 'a' or 'b'.")
if source not in {"v", "i"}:
raise ValueError("Source must be 'v' for voltage or 'i' for current.")
if source == "v":
self.device.write(f"smu{channel}.source.func = smu{channel}.OUTPUT_DCVOLTS")
else:
self.device.write(f"smu{channel}.source.func = smu{channel}.OUTPUT_DCCURRENT")
self.device.write(f"smu{channel}.source.level{source} = {level}")
self.device.write(f"smu{channel}.source.delay = {delay}")
except pyvisa.VisaIOError as e:
raise ConnectionError(f"Connection error: {e}")
except ValueError as e:
raise ValueError(f"Value error: {e}")
# Measure resistance
[docs]
def measure_resistance(self, channel="a", **kwargs):
# Check connection to Keithley
if self.device is None:
raise ConnectionError("Not connected to the device. Please call connect() first.")
try:
# Ensure channel is selected
if channel not in {"a", "b"}:
raise ValueError("Channel must be 'a' or 'b'.")
# Measure resistance
self.device.write(f"smu{channel}.source.output = smu{channel}.OUTPUT_ON")
resistance = self.device.query(f"print(smu{channel}.measure.r())")
self.device.write(f"smu{channel}.source.output = smu{channel}.OUTPUT_OFF")
return float(resistance)
# Checking for measurement error
except pyvisa.VisaIOError as e:
raise ConnectionError(f"Measurement error: {e}")
# Measure current
[docs]
def measure_current(self, channel="a", **kwargs):
# Check connection to Keithley
if self.device is None:
raise ConnectionError("Not connected to the device. Please call connect() first.")
try:
# Ensure channel is selected
if channel not in {"a", "b"}:
raise ValueError("Channel must be 'a' or 'b'.")
# Measure resistance
self.device.write(f"smu{channel}.source.output = smu{channel}.OUTPUT_ON")
current = self.device.query(f"print(smu{channel}.measure.i())")
self.device.write(f"smu{channel}.source.output = smu{channel}.OUTPUT_OFF")
return float(current)
# Checking for measurement error
except pyvisa.VisaIOError as e:
raise ConnectionError(f"Measurement error: {e}")
[docs]
def upload_keithley_script(self, inst, path_to_script, total_measurements: int, **kwargs):
"""
descrption:
used when multiple measurements are needed
----------------------------------------------
parameters:
path_to_script: the path to the lua script to be uploaded to keithley
total_measurements: the total number of measurements to be taken, used for replacing the placeholder in the lua script
kwargs: other parameters to be replaced in the lua script, such as nplc, levelv, delay, filter type and filter count
nplc: number of power line cycles for each measurement, default to be 10
levelv: the voltage level to be applied, default to be 0.01v
delay: the delay between each measurement, default to be 0.1s
filter: the type of filter to be applied, default to be turn off
filter_count: the count of the filter, default to be 3
"""
try:
print("Uploading Lua script...")
time.sleep(1)
with open(path_to_script, encoding="utf-8") as f:
for line in f:
line_split = line.rstrip()
print(line_split)
if line_split:
if total_measurements is not None:
line_new = line_split.replace("$num_of_trigger", str(total_measurements))
if "nplc" in kwargs:
nplc = kwargs["nplc"]
line_new = line_split.replace("$nplc", str(nplc))
else:
line_new = line_split.replace("$nplc", "10")
if "levelv" in kwargs:
levelv = kwargs["levelv"]
line_new = line_split.replace("$levelv", str(levelv))
else:
line_new = line_split.replace("$levelv", "0.01")
if "delay" in kwargs:
delay = kwargs["delay"]
line_new = line_split.replace("$delay", str(delay))
else:
line_new = line_split.replace("$delay", "0.1")
if "filter" in kwargs:
filter_type = kwargs["filter"]
line_new = line_split.replace("$filter", filter_type)
line_new = line_split.replace("$filter_enable", "FILTER_ON")
else:
line_new = line_split.replace("$filter", "FILTER_REPEAT_AVG")
line_new = line_split.replace("$filter_enable", "FILTER_OFF")
if "filter_count" in kwargs:
filter_count = kwargs["filter_count"]
line_new = line_split.replace("$filter_count", str(filter_count))
self.device.write(line_new)
time.sleep(0.01)
print("Upload complete")
except ValueError as e:
raise ValueError(f"Value error: {e}")
except pyvisa.VisaIOError as e:
raise ConnectionError(f"Connection error: {e}")
except Exception as e:
raise ConnectionError(f"Unexpected error: {e}")
# TODO: Sometimes the first resistance reading is off in an experiment I wonder if the buffer has something to do with this?
[docs]
def read_keithley_buffer(self, inst):
n = int(float(inst.query("print(smua.nvbuffer1.n)")))
print(f"Number of readings in buffer: {n}")
data = []
for i in range(int(n)):
reading = inst.query(f"print(smua.nvbuffer1.readings[{i + 1}])")
data.append(float(reading))
time.sleep(0.01) # add a small delay to avoid overwhelming the instrument
return data