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