216 lines
7.6 KiB
Python
216 lines
7.6 KiB
Python
import ch.psi.pshell.epics as epics
|
|
import math
|
|
|
|
class Keithley(object):
|
|
RANGE_STATES = ['AUTO', '20 mA', '2 mA', '200 uA', '20 uA', '2 uA', '200 nA', '20 nA', '2 nA', '200 pA', '20 pA']
|
|
TTYPE_STATES = ['IMM', 'TLIN', 'BUS', 'EXT']
|
|
USER_MODE_STATES = ['def setting', 'poll curr fast', 'poll curr medi', 'poll curr slow', 'trig setting', 'trigger BUS', 'trigger TLIN', 'trigger EXT', 'poll volt medi']
|
|
SCAN_STATES = ['Passive', 'Event', 'I/O Intr', '10 second', '5 second', '2 second', '1 second', '.5 second', '.2 second', '.1 second']
|
|
SCAN_INTERVALS = [0., 0., 0., 10., 5., 2., 1., .5, .2, .1]
|
|
|
|
def __init__(self, base_name, base_channel):
|
|
self.dwell = 0.
|
|
self.triggered = False
|
|
self.base_channel = base_channel
|
|
self.base_name = base_name
|
|
self.usermodeCh = None
|
|
self.rangeCh = None
|
|
self.scanCh = None
|
|
self.ttypeCh = None
|
|
self.nplcCh = None
|
|
self.navgCh = None
|
|
self.tottimeCh = None
|
|
self.doinitCh = None
|
|
self.dotriggerCh = None
|
|
self.dofetchCh = None
|
|
self.readoutCh = None
|
|
self.invertreadoutCh = None
|
|
self.setvoltageCh = None
|
|
self.voltoutCh = None
|
|
|
|
def initialize(self):
|
|
self.usermodeCh = epics.ChannelString(self.base_name + "UserMode", self.base_channel + "USER_MODE")
|
|
self.rangeCh = epics.ChannelInteger(self.base_name + "Range", self.base_channel + "RANGE")
|
|
self.scanCh = epics.ChannelInteger(self.base_name + "Scan", self.base_channel + "READSCAN.SCAN")
|
|
self.ttypeCh = epics.ChannelInteger(self.base_name + "TType", self.base_channel + "TTYPE")
|
|
self.nplcCh = epics.ChannelDouble(self.base_name + "Nplc", self.base_channel + "NPLC")
|
|
self.navgCh = epics.ChannelDouble(self.base_name + "Navg", self.base_channel + "NAVG")
|
|
self.tottimeCh = epics.ChannelDouble(self.base_name + "TotTime", self.base_channel + "TOTTIME")
|
|
self.doinitCh = epics.ChannelInteger(self.base_name + "DoInit", self.base_channel + "DOINIT")
|
|
self.dotriggerCh = epics.ChannelInteger(self.base_name + "DoTrigger", self.base_channel + "DOTRIGGER")
|
|
self.dofetchCh = epics.ChannelInteger(self.base_name + "DoFetch", self.base_channel + "DOFETCH")
|
|
self.readoutCh = epics.ChannelDouble(self.base_name + "Readout", self.base_channel + "READOUT")
|
|
self.invertreadoutCh = epics.ChannelInteger(self.base_name + "InvertReadout", self.base_channel + "INVERTREADOUT")
|
|
self.setvoltageCh = epics.ChannelDouble(self.base_name + "SetVoltage", self.base_channel + "SETVOLTAGE")
|
|
self.voltoutCh = epics.ChannelInteger(self.base_name + "VoltOut", self.base_channel + "VOLTOUT")
|
|
# DOZCHOFF
|
|
# ZCH_SP
|
|
# DOCURRENT
|
|
# DORESET
|
|
# DOSETDEFAULT
|
|
|
|
self.usermodeCh.initialize()
|
|
self.rangeCh.initialize()
|
|
self.scanCh.initialize()
|
|
self.ttypeCh.initialize()
|
|
self.nplcCh.initialize()
|
|
self.navgCh.initialize()
|
|
self.tottimeCh.initialize()
|
|
self.doinitCh.initialize()
|
|
self.dotriggerCh.initialize()
|
|
self.dofetchCh.initialize()
|
|
self.readoutCh.initialize()
|
|
self.invertreadoutCh.initialize()
|
|
self.setvoltageCh.initialize()
|
|
self.voltoutCh.initialize()
|
|
|
|
def setup():
|
|
"""
|
|
EXPERIMENTAL
|
|
|
|
to set up the keithley after Reset, there are two options
|
|
1) do set defaults, set scan, set range
|
|
2) set user mode, set range
|
|
|
|
"""
|
|
#self.dosetdefaultCh.write(1)
|
|
#self.scanCh.write(9)
|
|
|
|
def prepare(self, dwell, triggered):
|
|
"""
|
|
prepare keithley for gpib polling.
|
|
|
|
setting keithley parameters has several issues.
|
|
the dwell time and trigger mode cannot be set programmatically at the moment.
|
|
the user should select poll slow (100 ms), medium (20 ms) or fast (2 ms).
|
|
this method just reads the current value and stores it in self.dwell.
|
|
|
|
dwell: dwell time in seconds.
|
|
0.1 - 20.0 in triggered mode,
|
|
0.1 - 1.0 in free running mode.
|
|
triggered:
|
|
True: wait for self.trig call and trigger once per call.
|
|
False: 1 Hz free run using EPICS SCAN attribute.
|
|
"""
|
|
self.triggered = False
|
|
self.dwell = self.tottimeCh.read() / 1000.
|
|
|
|
def prepare_not_working(self, dwell, triggered):
|
|
"""
|
|
prepare keithley for gpib polling:
|
|
scan passive, bus triggered, set dwell time
|
|
|
|
this doesn't to work.
|
|
|
|
dwell: dwell time in seconds.
|
|
0.1 - 20.0 in triggered mode,
|
|
0.1 - 1.0 in free running mode.
|
|
triggered:
|
|
True: wait for self.trig call and trigger once per call.
|
|
False: 1 Hz free run using EPICS SCAN attribute.
|
|
"""
|
|
self.triggered = triggered
|
|
if triggered:
|
|
self.scanCh.write(0)
|
|
self.ttypeCh.write(2)
|
|
else:
|
|
self.ttypeCh.write(0)
|
|
self.scanCh.write(6)
|
|
dwell = min(dwell, 1.)
|
|
|
|
nplc = 5.
|
|
navg = dwell / 0.1
|
|
if navg > 100:
|
|
nplc *= 2
|
|
navg /= 2
|
|
navg = min(navg, 100.)
|
|
nplc = min(nplc, 10.)
|
|
|
|
self.nplcCh.write(nplc)
|
|
self.navgCh.write(navg)
|
|
self.dwell = self.tottimeCh.read() / 1000.
|
|
|
|
def trig(self):
|
|
"""
|
|
trigger keithleys, wait until done, and read the result into EPICS.
|
|
the value can then be read by pshell from the channel.
|
|
|
|
if self.prepare was called with triggered = False,
|
|
this method has no effect.
|
|
"""
|
|
if self.triggered:
|
|
self.doinitCh.write(1)
|
|
self.dotriggerCh.write(1)
|
|
|
|
def get_dwell(self):
|
|
"""
|
|
get dwell time in seconds.
|
|
"""
|
|
dwell = self.tottimeCh.read() / 1000.
|
|
self.dwell = dwell
|
|
return dwell
|
|
|
|
def fetch(self):
|
|
"""
|
|
fetch the current value from the keithley into EPICS.
|
|
|
|
if self.prepare was called with triggered = False,
|
|
this method has no effect.
|
|
"""
|
|
if self.triggered:
|
|
self.dofetchCh.write(1)
|
|
|
|
def read(self):
|
|
"""
|
|
read the curent value.
|
|
"""
|
|
return self.readoutCh.read()
|
|
|
|
def release(self):
|
|
"""
|
|
switch keithleys to free run.
|
|
0.1 s dwell time, 1 s poll interval.
|
|
|
|
_do nothing for now!_
|
|
"""
|
|
return None
|
|
|
|
self.nplcCh.write(5.)
|
|
self.navgCh.write(1.)
|
|
self.scanCh.write(6)
|
|
self.ttypeCh.write(0)
|
|
|
|
def reset(self):
|
|
"""
|
|
switch to zero check
|
|
"""
|
|
self.doresetCh.write(1)
|
|
|
|
def set_range(self, value):
|
|
"""
|
|
set the current range.
|
|
value can be:
|
|
- float: current limit in A.
|
|
values between 2e-11 and 2e-3 set a fixed range.
|
|
values greater than 0.02 select AUTO.
|
|
- str: state label of EPICS channel (cf. self.RANGE_STATES).
|
|
must be one of the state labels, otherwise a ValueError is raised.
|
|
- int: state index of EPICS channel (0...10)
|
|
"""
|
|
if isinstance(value, float):
|
|
try:
|
|
value = int(-math.log10(value / 2)) - 1
|
|
value = max(value, 0)
|
|
value = min(value, len(self.RANGE_STATES)-1)
|
|
except ValueError:
|
|
value = 0
|
|
elif isinstance(value, str):
|
|
value = self.RANGE_STATES.index(value)
|
|
self.rangeCh.write(value)
|
|
|
|
KeiSample = Keithley("SampleKeithley", "X03DA-KEITHLEY-1:")
|
|
KeiReference = Keithley("ReferenceKeithley", "X03DA-KEITHLEY-2:")
|
|
|
|
KeiSample.initialize()
|
|
KeiReference.initialize()
|