Source code for instruments.keithley.keithley6514

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Provides support for the Keithley 6514 electrometer
"""

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

from __future__ import absolute_import
from __future__ import division
from builtins import map

from enum import Enum

import quantities as pq

from instruments.abstract_instruments import Electrometer
from instruments.generic_scpi import SCPIInstrument
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 quantities as pq >>> 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: pq.volt, Mode.current: pq.amp, Mode.resistance: pq.ohm, Mode.charge: pq.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('{}:RANGE:AUTO?'.format(self.mode.value)) 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: `~quantities.Quantity` """ # pylint: disable=no-member mode = self.mode out = self.query('{}:RANGE:UPPER?'.format(mode.value)) return float(out) * self._MODE_UNITS[mode] @input_range.setter def input_range(self, newval): # pylint: disable=no-member mode = self.mode val = newval.rescale(self._MODE_UNITS[mode]).item() if val not in self._valid_range(mode).value: raise ValueError( 'Unexpected range limit for currently selected mode.') self.sendcmd('{}:RANGE:UPPER {:e}'.format(mode.value, val)) # 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('CONF:{}'.format(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