from . import VISAInstrumentDriver
from lightlab.equipment.abstract_drivers import Configurable
from lightlab.laboratory.instruments import Keithley
import numpy as np
import time
from lightlab import logger
[docs]class Keithley_2400_SM(VISAInstrumentDriver, Configurable):
''' A Keithley 2400 driver.
`Manual: <http://research.physics.illinois.edu/bezryadin/labprotocol/Keithley2400Manual.pdf>`__
Usage: :any:`/ipynbs/Hardware/Keithley.ipynb`
Capable of sourcing current and measuring voltage, such as a Keithley
Also provides interface methods for measuring resistance and measuring power
'''
instrument_category = Keithley
autoDisable = None # in seconds. NOT IMPLEMENTED
_latestCurrentVal = 0
_latestVoltageVal = 0
currStep = None
voltStep = None
rampStepTime = 0.01 # in seconds.
def __init__(self, name=None, address=None, **kwargs):
'''
Args:
currStep (float): amount to step if ramping in current mode. Default (None) is no ramp
voltStep (float): amount to step if ramping in voltage mode. Default (None) is no ramp
rampStepTime (float): time to wait on each ramp step point
'''
self.currStep = kwargs.pop("currStep", None)
self.voltStep = kwargs.pop("voltStep", None)
self.rampStepTime = kwargs.pop("rampStepTime", 0.01)
VISAInstrumentDriver.__init__(self, name=name, address=address, **kwargs)
Configurable.__init__(self, headerIsOptional=False, verboseIsOptional=False)
[docs] def startup(self):
self.write('*RST')
[docs] def setPort(self, port):
if port == 'Front':
self.setConfigParam('ROUT:TERM', 'FRON')
elif port == 'Rear':
self.setConfigParam('ROUT:TERM', 'REAR')
def __setSourceMode(self, isCurrentSource):
if isCurrentSource:
sourceStr, meterStr = ('CURR', 'VOLT')
else:
sourceStr, meterStr = ('VOLT', 'CURR')
self.setConfigParam('SOURCE:FUNC', sourceStr)
self.setConfigParam('SOURCE:{}:MODE'.format(sourceStr), 'FIXED')
self.setConfigParam('SENSE:FUNCTION:OFF:ALL')
self.setConfigParam('SENSE:FUNCTION:ON', '"{}"'.format(meterStr))
self.setConfigParam('SENSE:{}:RANGE:AUTO'.format(meterStr), 'ON')
self.setConfigParam('RES:MODE', 'MAN') # Manual resistance ranging
[docs] def setVoltageMode(self, protectionCurrent=0.05):
self.enable(False)
self.__setSourceMode(isCurrentSource=False)
self.setProtectionCurrent(protectionCurrent)
self._configVoltage(0)
[docs] def setCurrentMode(self, protectionVoltage=1):
self.enable(False)
self.__setSourceMode(isCurrentSource=True)
self.setProtectionVoltage(protectionVoltage)
self._configCurrent(0)
def _configCurrent(self, currAmps):
currAmps = float(currAmps)
if currAmps >= 0:
currAmps = np.clip(currAmps, a_min=1e-9, a_max=1.)
else:
currAmps = np.clip(currAmps, a_min=-1, a_max=-1e-9)
if currAmps != 0:
needRange = 10 ** np.ceil(np.log10(abs(currAmps)))
self.setConfigParam('SOURCE:CURR:RANGE', needRange)
self.setConfigParam('SOURCE:CURR', currAmps)
self._latestCurrentVal = currAmps
def _configVoltage(self, voltVolts):
voltVolts = float(voltVolts)
if voltVolts != 0:
needRange = 10 ** np.ceil(np.log10(np.abs(voltVolts)))
self.setConfigParam('SOURCE:VOLT:RANGE', needRange)
self.setConfigParam('SOURCE:VOLT', voltVolts)
self._latestVoltageVal = voltVolts
[docs] def setCurrent(self, currAmps):
''' This leaves the output on indefinitely '''
currTemp = self._latestCurrentVal
if not self.enable() or self.currStep is None:
self._configCurrent(currAmps)
else:
nSteps = int(np.floor(abs(currTemp - currAmps) / self.currStep))
for curr in np.linspace(currTemp, currAmps, 1 + nSteps)[1:]:
self._configCurrent(curr)
time.sleep(self.rampStepTime)
[docs] def setVoltage(self, voltVolts):
voltTemp = self._latestVoltageVal
if not self.enable() or self.voltStep is None:
self._configVoltage(voltVolts)
else:
nSteps = int(np.floor(abs(voltTemp - voltVolts) / self.voltStep))
for volt in np.linspace(voltTemp, voltVolts, 1 + nSteps)[1:]:
self._configVoltage(volt)
time.sleep(self.rampStepTime)
[docs] def getCurrent(self):
currGlob = self.getConfigParam('SOURCE:CURR')
if type(currGlob) is dict:
currGlob = currGlob['&']
return currGlob
[docs] def getVoltage(self):
voltGlob = self.getConfigParam('SOURCE:VOLT')
if type(voltGlob) is dict:
voltGlob = voltGlob['&']
return voltGlob
[docs] def setProtectionVoltage(self, protectionVoltage):
self.setConfigParam('VOLT:PROT', protectionVoltage)
[docs] def setProtectionCurrent(self, protectionCurrent):
self.setConfigParam('CURR:PROT', protectionCurrent)
@property
def protectionVoltage(self):
return self.getConfigParam('VOLT:PROT')
@property
def protectionCurrent(self):
return self.getConfigParam('CURR:PROT')
[docs] def measVoltage(self):
retStr = self.query('MEASURE:VOLT?')
v = float(retStr.split(',')[0]) # first number is voltage always
if v >= self.protectionVoltage:
logger.warning('Keithley compliance voltage of %s reached', self.protectionVoltage)
logger.warning('You are sourcing %smW into the load.', v * self._latestCurrentVal * 1e-3)
return v
[docs] def measCurrent(self):
retStr = self.query('MEASURE:CURR?')
i = float(retStr.split(',')[1]) # second number is current always
if i >= self.protectionCurrent:
logger.warning('Keithley compliance current of %s reached', self.protectionCurrent)
logger.warning('You are sourcing %smW into the load.', i * self._latestVoltageVal * 1e-3)
return i
[docs] def enable(self, newState=None):
''' get/set enable state
'''
if newState is False:
if self.getConfigParam('SOURCE:FUNC') == 'CURR':
self.setCurrent(0)
else:
self.setVoltage(0)
if newState is not None:
self.setConfigParam('OUTP:STATE', 1 if newState else 0, forceHardware=True)
retVal = self.getConfigParam('OUTP:STATE', forceHardware=True)
return retVal in ['ON', 1, '1']