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("######################################")