Source code for semi_cr.core.lab.drivers.instrument
# TODO: RENAME THIS ...
# ----------------------------------------------------
# Note
# ----------------------------------------------------
"""
A single driver to make resistance readings on an
individual and full-matrix scale
Need to debug the full matrix as it sometimes gets
stuck on the last run
Need to understand error checking better
Improve the way settings are updated ...
"""
# ----------------------------------------------------
# Imports
# ----------------------------------------------------
import math
import os
import time
from dataclasses import dataclass, field
import numpy as np
import semi_cr.setup.juno.cqcc.tests.GUI.resources.error_messages as err
from semi_cr.core.lab.drivers.cqccV2 import CQCC, SwitchPairControlStruct
from semi_cr.core.lab.drivers.keithley_2636V3 import Keithley2636bDriver
from semi_cr.setup.juno.cqcc.utils.parser import CQCC_V1
# ----------------------------------------------------
# Supporting Classes
# ----------------------------------------------------
# Channel to check before executing large measurements
[docs]
@dataclass
class CheckChannel:
positive: int = 12
gnd: int = 6
repeats: int = 5
tolerance: float = 0.01 # Measurement should be accurate pm 1%
theoretical_resistance: float = 100_000 # Ohms
err_message: str = "High deviation from the theoretical resistance detected: power system on and off and try again."
# Set-up error
[docs]
class SetupError(Exception):
pass
# Paths for full-matrix
[docs]
@dataclass
class Paths:
# TODO: Name them better ...
cqcc_v1: str = r"src\\semi_cr\\setup\\juno\\cqcc\\conf\\cqcc_conf\\CQCC_V1.yaml"
lua_script: str = r"src\\semi_cr\\setup\\juno\\cqcc\\conf\\keitheley_scripts\\keithley_script.lua"
data: str = r"src\semi_cr\\setup\\juno\\cqcc\\tests"
# Data for the full matrix-measurement
[docs]
@dataclass
class FullMatrixParams:
n: int = 10
chunk_size: int = 20
num_comb_FM: int = field(init=False)
num_comb_HM: int = field(init=False)
path: Paths = field(default_factory=Paths)
def __post_init__(self):
self.num_comb_FM: int = self.n**2
self.num_comb_HM: int = math.comb(self.n, 2) + self.n
# ----------------------------------------------------
# Measurement Class
# ----------------------------------------------------
[docs]
class Instrument:
# ----------------------------------------------------
# Init, Connect, Disconnect, Checking Connections
# ----------------------------------------------------
def __init__(self):
self.keithley = Keithley2636bDriver()
self.cqcc = CQCC()
self.check_channel = CheckChannel()
self.connected = False
[docs]
def connect(self, IP, port):
if (not self.keithley.connected) and (not self.cqcc.connected):
self.keithley.connect(IP)
self.cqcc.connect(port)
self.connected = True
if not self.keithley.connected:
self.keithley.connect(IP)
self.connected = True
if not self.cqcc.connected:
self.cqcc.connect(port)
self.connected = True
else:
raise err.ReconnectionError()
[docs]
def disconnect(self):
self.keithley.disconnect()
self.cqcc.disconnect()
self.connected = False
# TODO: Is this fnc. needed?
[docs]
def check_connection(self):
self.cqcc.check_connection()
self.keithley.check_connection()
# ----------------------------------------------------
# Manually Measure Resistance and Current
# ----------------------------------------------------
# Set appropriate channels and update settings
[docs]
def setup_measurement(self, positive, gnd, **kwargs):
self.keithley.update_settings(**kwargs)
self.cqcc.set_channel("Positive", positive, state=True)
self.cqcc.set_channel("GND", gnd, state=True)
# Confirm the settings have been updated
self.keithley.print_settings()
[docs]
def measure_resistance(self):
self.check_connection()
return self.keithley.measure_resistance()
[docs]
def measure_current(self):
self.check_connection()
return self.keithley.measure_current()
# ----------------------------------------------------
# Measure the full-matrix
# ----------------------------------------------------
[docs]
def measure_full_matrix(self, full_measurement=False, **kwargs):
# Ensure connected before starting
self.check_connection()
# Import self.params
self.params = FullMatrixParams()
# Check reasonable values are returned first
self.check_setup()
# Proceed with full matrix measurement - reset, update and display settings
self.keithley.reset()
self.keithley.update_settings(**kwargs)
self.keithley.print_settings()
# Set the value of num comb
if full_measurement:
self.num_comb = self.params.num_comb_FM
else:
self.num_comb = self.params.num_comb_HM
# Start the measurements
self.keithley.upload_lua(self.params.path.lua_script, self.num_comb)
self.keithley.status.write("script.anonymous.run()")
time.sleep(2) # add delay to ensure Keithley is ready before sending commands
# Execute measurements
self.loop()
time.sleep(2)
# Retrieve and close all
print("All measurements done, retrieving data...")
data = self.keithley.read_keithley_buffer() # read the measurement data from Keithley
# Save data
save_loc = f"keithley_measurement_data_fullscan_X002002C1D1_{full_measurement}_{time.strftime('%Y-%m-%d_%H-%M-%S')}.npy"
np.save(
os.path.join(
self.params.path.data,
save_loc,
),
data,
)
print(f"Data saved to: {save_loc}")
# ----------------------------------------------------
# Measure the full-matrix - helper funcs.
# ----------------------------------------------------
[docs]
def loop(self):
count = 1
for i in range(1, 97):
for j in range(1, 97):
path = {"input": f"dc{i}", "output": f"dc{j}"}
arduino_signal = CQCC_V1(self.params.path.cqcc_v1).path_to_signal(path)
command = SwitchPairControlStruct()
command.exp_type = "K"
command.pin_list = [pin for pin, _ in arduino_signal.items()]
command.level_list = [value for _, value in arduino_signal.items()]
command.num_pairs = len(command.pin_list)
if count % self.params.chunk_size == 1:
command.tail = False
self.cqcc.send_switchPairControlStruct_to_arduino(command, header=True) # for the first command within the chunk, send the header
# TODO: Tidy up and don't repeat code ...
elif count % self.params.chunk_size == 0:
command.tail = True
self.cqcc.send_switchPairControlStruct_to_arduino(command, header=False) # for the others, don't need to
# for every 20 commands, wait for execution
_ = self.cqcc.wait_for_ack()
yield self.calc_progess
elif count == self.num_comb:
command.tail = True
self.cqcc.send_switchPairControlStruct_to_arduino(command, header=False) # for the others, don't need to
# for every 20 commands, wait for execution
_ = self.cqcc.wait_for_ack()
return
else:
# otherwise just send the command without header and tail false
command.tail = False
self.cqcc.send_switchPairControlStruct_to_arduino(command, header=False)
count += 1
[docs]
def calc_progess(self, measurements_completed):
return measurements_completed / self.num_comb
[docs]
def check_setup(self, **kwargs):
# Check connection before starting
print("-----Checking Experimental Setup-----")
self.check_connection()
# Define upper and lower bounds
lb = (1 - self.check_channel.tolerance) * self.check_channel.tolerance
ub = (1 + self.check_channel.tolerance) * self.check_channel.tolerance
self.setup_measurement(
positive=self.check_channel.positive,
gnd=self.check_channel.gnd,
**kwargs,
)
for i in range(1, self.check_channel.repeats + 1):
resistance = self.measure_resistance()
print(f"Resistance ({i}) = {resistance}")
if resistance < lb or resistance > ub:
raise SetupError(self.check_channel.err_message)
# Acknowledge the device is working
print("No issues detected")
print("######################################")