Source code for semi_cr.core.lab.drivers.keithley_2636

# ----------------------------------------------------
# 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