Source code for device.source_meter_ki2400

import time
import numpy as np
import pathlib

from . import base


[docs] class SourceMeter_KI2400(base.VisaHardwareBase): """Source meter (Langmuir experiment) Model name: KEITHLEY KI2400 Communication: - RS-232 (USB adapter), using VISA protocol and SCPI commands """ SAVE_DIR = pathlib.Path.home() / "saved_data" def __init__(self, visa_string, **kwargs): super().__init__(visa_string, baud_rate=57600, **kwargs) self._last = { "channel" : 0, } self._progress = 0 # ==== Inherited abstract methods ==== def _safestate(self): """Safety kill-switch. Turns off anything dangerous after interrupt.""" self.raw_scpi(":OUTPUT:STATE 0") # def _readerror(self): # """Read new error from device.""" # # @TODO def _defaultstate(self): """Init device to a well defined state. (Auto called after connect.)""" self.raw_scpi(":SOURCE:CLEAR:AUTO 1") self.clear_channels() # @TODO # This method should run a series of commands to bring the device to some well defined state # Probably: *RST, set source voltage = 0, disable output, etc. # def _clear_defaultstate(self): # """Clean-up device settings (i.e. keys lock). (Auto called before disconnect.)""" # # @TODO # ==== Commands ====
[docs] @base.base_command def read_data(self, dc): """Read data from the buffer.""" self.log.info("Fetching sweep data (~ 10s)") data = self.raw_scpi(":DATA:DATA?", "ascii", container=np.array, delay=10) self.SAVE_DIR.mkdir(exist_ok=True) filepath = "{}_sweep_{}.txt".format(type(self).__name__, time.asctime()) filepath = self.SAVE_DIR / filepath.replace(" ", "_").replace(":", ",") h = "Unspecified sweep data" np.savetxt(filepath, data, fmt="%.6e", header=h) with filepath.open("r") as file: dc.emit_datafile(file, filepath.name) self.log.info("Fetched sweep data from the device!") return str(filepath)
[docs] @base.base_command def set_voltage(self, voltage): """Set the source voltage [V]""" voltage = float(voltage) # In device classes, the method arguments always need to be converted from string if not (-210 <= voltage <= 210) : raise base.CommandError("Invalid voltage value, use -210 to 210") self.raw_scpi(f":SOURCE:VOLTAGE {voltage:.2f}")
[docs] @base.base_command def voltage(self): """Read the source voltage [V]""" return float(self.raw_scpi(":SOURCE:VOLTAGE?"))
[docs] @base.base_command def measure_current(self, voltage): """Execute a one-off measurment of current and return the result [A]""" voltage = float(voltage) self.set_voltage(voltage) current = self.raw_scpi(":MEASURE?", "ascii")[1] return current
@base.base_command def _configure_sweep (self, v_min, v_max, v_step, n_steps): self.raw_scpi(":TRACE:CLEAR") self.raw_scpi(":TRIGGER:CLEAR") self.raw_scpi(":TRACE:FEED:CONTROL NEVER") self.raw_scpi(":SOURCE:VOLTAGE:MODE SWEEP") self.raw_scpi(f":SOURCE:VOLTAGE:START {v_min:.2f}") self.raw_scpi(f":SOURCE:VOLTAGE:STOP {v_max:.2f}") self.raw_scpi(f":SOURCE:VOLTAGE:STEP {v_step}") self.raw_scpi(":SOURCE:FUNCTION:MODE VOLTAGE") self.raw_scpi(f":TRIGGER:COUNT {n_steps}") self.raw_scpi(":TRIGGER:DELAY MIN") self.raw_scpi(f":TRACE:POINTS {n_steps}") self.raw_scpi(":TRACE:FEED:CONTROL NEXT") @base.base_command def _save_data(self,dc): data = self.raw_scpi(":TRACE:DATA?","ascii", container=np.array, delay = 5) data = data.reshape((-1,5)).T[[3,0,1]] self.SAVE_DIR.mkdir(exist_ok=True) filepath = "{}_sweep_{}.txt".format(type(self).__name__, time.asctime()) filepath = self.SAVE_DIR / filepath.replace(" ", "_").replace(":", "-") h = "Time (s), Voltage (V), Current (A)" np.savetxt(filepath, data.T, fmt="%.6e", header=h) if dc is not None: with filepath.open("r") as file: dc.emit_datafile(file, filepath.name) dc.emit("graph", { "title": "IV Characteristics", "x": data[1].tolist(), "y": data[2].tolist(), "xlabel": "Voltage [V]", "ylabel": "Current [A]", "id": "iv_characteristics" }) return data
[docs] @base.base_command def sweep(self,dc,v_min, v_max, v_step): """Execute a sweep measurement and save the data to a file""" v_min = float(v_min) v_max = float(v_max) v_step = float (v_step) if not (-210 <= v_min <= 210) : raise base.CommandError("Invalid v_min value, use -210 to 210") if not (-210 <= v_max <= 210) : raise base.CommandError("Invalid v_max value, use -210 to 210") if not (-420 <= v_step <= 420) : raise base.CommandError("Invalid v_step value, use -420 to 420") n_steps = abs(int((v_max-v_min)/v_step))+1 self.log.info(f"Sweep started {v_min:.1f} {v_max:.1f} {v_step:.1f}") """Configuring the sweep""" self._configure_sweep(v_min, v_max, v_step, n_steps) time.sleep(1) self.raw_scpi(":INITIATE") time.sleep((n_steps/16)*1.2) """Saving the data""" data = self._save_data(dc) return data
[docs] @base.base_command def set_channel(self, channel): channel = int(channel) if (channel == 1): self._last["channel"] = 1 self.raw_scpi(":SOURCE2:TTL 6") elif (channel == 2): self._last["channel"] = 2 self.raw_scpi(":SOURCE2:TTL 5") elif(channel == 3): self._last["channel"] = 3 self.raw_scpi(":SOURCE2:TTL 3") else: raise base.CommandError ("The number of the channel should be 1, 2 or 3")
[docs] @base.base_command def clear_channels(self): self.raw_scpi(":SOURCE2:TTL 7")
# ==== Methods/commands related to DeviceClient ==== @base.idle_command def _idle_update_all(self): self._last["channel"] = { 7: 0, 6: 1, 5: 2, 3 :3, }[int(self.raw_scpi(":SOURCE2:TTL:ACTUAL?"))]
[docs] def update_frontend(self, device_client): # Not an idle command, it does not communicate with hardware # => frontend can be updated even when a command is running self._idle_update_all() value = self._last["channel"] device_client.emit("value", { "value": value, "formatted": str(value), "label": "Channel", "min": 0, "max": 3, "id": "Channel", })