Source code for instruments.keithley.keithley6514

#!/usr/bin/env python
"""
Provides support for the Keithley 6514 electrometer
"""

# IMPORTS #####################################################################

from enum import Enum

from instruments.abstract_instruments import Electrometer
from instruments.generic_scpi import SCPIInstrument
from instruments.units import ureg as u
from instruments.util_fns import bool_property, enum_property

# CLASSES #####################################################################


[docs] class Keithley6514(SCPIInstrument, Electrometer): """ The `Keithley 6514`_ is an electrometer capable of doing sensitive current, charge, voltage and resistance measurements. Example usage: >>> import instruments as ik >>> import instruments.units as u >>> dmm = ik.keithley.Keithley6514.open_gpibusb('/dev/ttyUSB0', 12) """ # ENUMS #
[docs] class Mode(Enum): """ Enum containing valid measurement modes for the Keithley 6514 """ voltage = "VOLT:DC" current = "CURR:DC" resistance = "RES" charge = "CHAR"
[docs] class TriggerMode(Enum): """ Enum containing valid trigger modes for the Keithley 6514 """ immediate = "IMM" tlink = "TLINK"
[docs] class ArmSource(Enum): """ Enum containing valid trigger arming sources for the Keithley 6514 """ immediate = "IMM" timer = "TIM" bus = "BUS" tlink = "TLIN" stest = "STES" pstest = "PST" nstest = "NST" manual = "MAN"
[docs] class ValidRange(Enum): """ Enum containing valid measurement ranges for the Keithley 6514 """ voltage = (2, 20, 200) current = ( 20e-12, 200e-12, 2e-9, 20e-9, 200e-9, 2e-6, 20e-6, 200e-6, 2e-3, 20e-3, ) resistance = (2e3, 20e3, 200e3, 2e6, 20e6, 200e6, 2e9, 20e9, 200e9) charge = (20e-9, 200e-9, 2e-6, 20e-6)
# CONSTANTS # _MODE_UNITS = { Mode.voltage: u.volt, Mode.current: u.amp, Mode.resistance: u.ohm, Mode.charge: u.coulomb, } # PRIVATE METHODS # def _valid_range(self, mode): if mode == self.Mode.voltage: return self.ValidRange.voltage elif mode == self.Mode.current: return self.ValidRange.current elif mode == self.Mode.resistance: return self.ValidRange.resistance elif mode == self.Mode.charge: return self.ValidRange.charge else: raise ValueError("Invalid mode.") def _parse_measurement(self, ascii): # TODO: don't assume ASCII data format # pylint: disable=fixme vals = list(map(float, ascii.split(","))) reading = vals[0] * self.unit timestamp = vals[1] status = vals[2] return reading, timestamp, status # PROPERTIES # # The mode values have quotes around them for some annoying reason. mode = enum_property( "FUNCTION", Mode, input_decoration=lambda val: val[1:-1], # output_decoration=lambda val: '"{}"'.format(val), set_fmt='{} "{}"', doc=""" Gets/sets the measurement mode of the Keithley 6514. """, ) trigger_mode = enum_property( "TRIGGER:SOURCE", TriggerMode, doc=""" Gets/sets the trigger mode of the Keithley 6514. """, ) arm_source = enum_property( "ARM:SOURCE", ArmSource, doc=""" Gets/sets the arm source of the Keithley 6514. """, ) zero_check = bool_property( "SYST:ZCH", inst_true="ON", inst_false="OFF", doc=""" Gets/sets the zero checking status of the Keithley 6514. """, ) zero_correct = bool_property( "SYST:ZCOR", inst_true="ON", inst_false="OFF", doc=""" Gets/sets the zero correcting status of the Keithley 6514. """, ) @property def unit(self): return self._MODE_UNITS[self.mode] @property def auto_range(self): """ Gets/sets the auto range setting :type: `bool` """ # pylint: disable=no-member out = self.query(f"{self.mode.value}:RANGE:AUTO?") return True if out == "1" else False @auto_range.setter def auto_range(self, newval): # pylint: disable=no-member self.sendcmd("{}:RANGE:AUTO {}".format(self.mode.value, "1" if newval else "0")) @property def input_range(self): """ Gets/sets the upper limit of the current range. :type: `~pint.Quantity` """ # pylint: disable=no-member mode = self.mode out = self.query(f"{mode.value}:RANGE:UPPER?") return float(out) * self._MODE_UNITS[mode] @input_range.setter def input_range(self, newval): # pylint: disable=no-member mode = self.mode val = newval.to(self._MODE_UNITS[mode]).magnitude if val not in self._valid_range(mode).value: raise ValueError("Unexpected range limit for currently selected mode.") self.sendcmd(f"{mode.value}:RANGE:UPPER {val:e}") # METHODS ##
[docs] def auto_config(self, mode): """ This command causes the device to do the following: - Switch to the specified mode - Reset all related controls to default values - Set trigger and arm to the 'immediate' setting - Set arm and trigger counts to 1 - Set trigger delays to 0 - Place unit in idle state - Disable all math calculations - Disable buffer operation - Enable autozero """ self.sendcmd(f"CONF:{mode.value}")
[docs] def fetch(self): """ Request the latest post-processed readings using the current mode. (So does not issue a trigger) Returns a tuple of the form (reading, timestamp) """ raw = self.query("FETC?") reading, timestamp, _ = self._parse_measurement(raw) return reading, timestamp
[docs] def read_measurements(self): """ Trigger and acquire readings using the current mode. Returns a tuple of the form (reading, timestamp) """ raw = self.query("READ?") reading, timestamp, _ = self._parse_measurement(raw) return reading, timestamp