drivers for AH700 and K2601B
AH2700: Andeen Hagerling capacitance bridge This is a use case for 'return Done' in read_<parameter> K2601b: Keithley source meter Current and Source are two dependend SECoP modules: only one of them might be active. Writing to target is allowed only when active, but both values are always readable. A common SourceMeter bare Module is present for common functionality. Change-Id: I6f1875298ef928bcc2d60b89560b837139160775 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22071 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
17
cfg/ah2700test.cfg
Normal file
17
cfg/ah2700test.cfg
Normal file
@ -0,0 +1,17 @@
|
||||
[node AH2700Test.psi.ch]
|
||||
description = AH2700 capacitance bridge test
|
||||
|
||||
[interface tcp]
|
||||
type = tcp
|
||||
bindto = 0.0.0.0
|
||||
bindport = 5000
|
||||
|
||||
[module cap]
|
||||
class = secop_psi.ah2700.Capacitance
|
||||
description = capacitance
|
||||
uri=ldmse3-ts:3015
|
||||
|
||||
#[module ahcom]
|
||||
#class = secop_psi.ah2700.StringIO
|
||||
#uri=ldmse3-ts:3015
|
||||
#description = serial communicator to an AH2700
|
80
secop_psi/ah2700.py
Executable file
80
secop_psi/ah2700.py
Executable file
@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
# *****************************************************************************
|
||||
"""Andeen Hagerling capacitance bridge"""
|
||||
|
||||
from secop import Readable, Parameter, Override, FloatRange, HasIodev, StringIO, Done
|
||||
|
||||
|
||||
class Ah2700IO(StringIO):
|
||||
end_of_line = '\r\n'
|
||||
timeout = 5
|
||||
|
||||
|
||||
class Capacitance(HasIodev, Readable):
|
||||
parameters = {
|
||||
'value': Override('capacitance', FloatRange(unit='pF'), poll=True),
|
||||
'freq': Parameter('frequency', FloatRange(unit='Hz'), readonly=False, default=0),
|
||||
'voltage': Parameter('voltage', FloatRange(unit='V'), readonly=False, default=0),
|
||||
'loss': Parameter('loss', FloatRange(unit='deg'), readonly=False, default=0),
|
||||
}
|
||||
iodevClass = Ah2700IO
|
||||
|
||||
def read_value(self):
|
||||
reply = self.sendRecv('SI') # single trigger
|
||||
if reply.startswith('SI'): # this is an echo
|
||||
self.sendRecv('SERIAL ECHO OFF')
|
||||
reply = self.sendRecv('SI')
|
||||
if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH"
|
||||
self.status = [self.Status.ERROR, reply]
|
||||
return Done
|
||||
self.status = [self.Status.IDLE, '']
|
||||
# examples of replies:
|
||||
# 'F= 1000.0 HZ C= 0.000001 PF L> 0.0 DS V= 15.0 V'
|
||||
# 'F= 1000.0 HZ C= 0.0000059 PF L=-0.4 DS V= 15.0 V OVEN'
|
||||
# 'LOSS TOO HIGH'
|
||||
# make sure there is always a space after '=' and '>'
|
||||
# split() ignores multiple white space
|
||||
reply = reply.replace('=', '= ').replace('>', '> ').split()
|
||||
_, freq, _, _, cap, _, _, loss, lossunit, _, volt = reply[:11]
|
||||
self.freq = freq
|
||||
self.voltage = volt
|
||||
self.value = cap
|
||||
if lossunit == 'DS':
|
||||
self.loss = loss
|
||||
else: # the unit was wrong, we want DS = tan(delta), not NS = nanoSiemens
|
||||
reply = self.sendRecv('UN DS').split() # UN DS returns a reply similar to SI
|
||||
try:
|
||||
self.loss = reply[7]
|
||||
except IndexError:
|
||||
pass # don't worry, loss will be updated next time
|
||||
return Done
|
||||
|
||||
def read_freq(self):
|
||||
self.read_value()
|
||||
return Done
|
||||
|
||||
def read_loss(self):
|
||||
self.read_value()
|
||||
return Done
|
||||
|
||||
def read_volt(self):
|
||||
self.read_value()
|
||||
return Done
|
165
secop_psi/k2601b.py
Executable file
165
secop_psi/k2601b.py
Executable file
@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
# *****************************************************************************
|
||||
"""Keithley 2601B source meter
|
||||
|
||||
not tested yet"""
|
||||
|
||||
from secop import Writable, Module, Parameter, Override, Attached,\
|
||||
BoolType, FloatRange, EnumType, HasIodev, StringIO
|
||||
|
||||
|
||||
class K2601bIO(StringIO):
|
||||
identification = [('print(localnode.description)', 'Keithley Instruments SMU 2601B.*')]
|
||||
|
||||
|
||||
SOURCECMDS = {
|
||||
1: 'reset()'
|
||||
'smua.source.func = smua.OUTPUT_DCVOLTS '
|
||||
'display.smua.measure.func = display.MEASURE_DCAMP '
|
||||
'smua.source.autorangev = 1',
|
||||
2: 'reset()'
|
||||
'smua.source.func = smua.OUTPUT_DCAMPS '
|
||||
'smua.source.autorangei = 1',
|
||||
}
|
||||
|
||||
|
||||
class SourceMeter(HasIodev, Module):
|
||||
parameters = {
|
||||
'resistivity': Parameter('readback resistivity', FloatRange(unit='Ohm'), poll=True),
|
||||
'power': Parameter('readback power', FloatRange(unit='W'), poll=True),
|
||||
'mode': Parameter('measurement mode', EnumType(off=0, current=1, voltage=2),
|
||||
readonly=False, default=0),
|
||||
'active': Parameter('output enable', BoolType(), readonly=False, poll=True),
|
||||
}
|
||||
iodevClass = K2601bIO
|
||||
|
||||
def read_resistivity(self):
|
||||
return self.sendRecv('print(smua.measure.r())')
|
||||
|
||||
def read_power(self):
|
||||
return self.sendRecv('print(smua.measure.p())')
|
||||
|
||||
def read_active(self):
|
||||
return self.sendRecv('print(smua.source.output)')
|
||||
|
||||
def write_active(self, value):
|
||||
return self.sendRecv('smua.source.output = %d print(smua.source.output)' % value)
|
||||
|
||||
# for now, mode will not be read from hardware
|
||||
|
||||
def write_mode(self, value):
|
||||
if value == 0:
|
||||
self.write_active(0)
|
||||
else:
|
||||
self.sendRecv(SOURCECMDS[value] + ' print(0)')
|
||||
return value
|
||||
|
||||
|
||||
class Current(HasIodev, Writable):
|
||||
properties = {
|
||||
'sourcemeter': Attached(),
|
||||
}
|
||||
parameters = {
|
||||
'value': Override('measured current', FloatRange(unit='A'), poll=True),
|
||||
'target': Override('set current', FloatRange(unit='A'), poll=True),
|
||||
'active': Parameter('current is controlled', BoolType(), default=False), # polled from Current/Voltage
|
||||
'limit': Parameter('current limit', FloatRange(0, 2.0, unit='A'), default=2, poll=True),
|
||||
}
|
||||
|
||||
def read_value(self):
|
||||
return self.sendRecv('print(smua.measure.i())')
|
||||
|
||||
def read_target(self):
|
||||
return self.sendRecv('print(smua.source.leveli)')
|
||||
|
||||
def write_target(self, value):
|
||||
if not self.active:
|
||||
raise ValueError('current source is disabled')
|
||||
if value > self.limit:
|
||||
raise ValueError('current exceeds limit')
|
||||
return self.sendRecv('smua.source.leveli = %g print(smua.source.leveli)' % value)
|
||||
|
||||
def read_limit(self):
|
||||
if self.active:
|
||||
return self.limit
|
||||
return self.sendRecv('print(smua.source.limiti)')
|
||||
|
||||
def write_limit(self, value):
|
||||
if self.active:
|
||||
return value
|
||||
return self.sendRecv('smua.source.limiti = %g print(smua.source.limiti)' % value)
|
||||
|
||||
def read_active(self):
|
||||
return self._sourcemeter.mode == 1 and self._sourcemeter.read_active()
|
||||
|
||||
def write_active(self, value):
|
||||
if self._sourcemeter.mode != 1:
|
||||
if value:
|
||||
self._sourcemeter.write_mode(1) # switch to current
|
||||
else:
|
||||
return 0
|
||||
return self._sourcemeter.write_active(value)
|
||||
|
||||
|
||||
class Voltage(HasIodev, Writable):
|
||||
properties = {
|
||||
'sourcemeter': Attached(),
|
||||
}
|
||||
parameters = {
|
||||
'value': Override('measured voltage', FloatRange(unit='V'), poll=True),
|
||||
'target': Override('set voltage', FloatRange(unit='V'), poll=True),
|
||||
'active': Parameter('voltage is controlled', BoolType(), poll=True),
|
||||
'limit': Parameter('current limit', FloatRange(0, 2.0, unit='V'), default=2, poll=True),
|
||||
}
|
||||
|
||||
def read_value(self):
|
||||
return self.sendRecv('print(smua.measure.v())')
|
||||
|
||||
def read_target(self):
|
||||
return self.sendRecv('print(smua.source.levelv)')
|
||||
|
||||
def write_target(self, value):
|
||||
if not self.active:
|
||||
raise ValueError('voltage source is disabled')
|
||||
if value > self.limit:
|
||||
raise ValueError('voltage exceeds limit')
|
||||
return self.sendRecv('smua.source.levelv = %g print(smua.source.levelv)' % value)
|
||||
|
||||
def read_limit(self):
|
||||
if self.active:
|
||||
return self.limit
|
||||
return self.sendRecv('print(smua.source.limitv)')
|
||||
|
||||
def write_limit(self, value):
|
||||
if self.active:
|
||||
return value
|
||||
return self.sendRecv('smua.source.limitv = %g print(smua.source.limitv)' % value)
|
||||
|
||||
def read_active(self):
|
||||
return self._sourcemeter.mode == 2 and self._sourcemeter.read_active()
|
||||
|
||||
def write_active(self, value):
|
||||
if self._sourcemeter.mode != 2:
|
||||
if value:
|
||||
self._sourcemeter.write_mode(2) # switch to voltage
|
||||
else:
|
||||
return 0
|
||||
return self._sourcemeter.write_active(value)
|
Reference in New Issue
Block a user