155 lines
5.8 KiB
Python
155 lines
5.8 KiB
Python
# *****************************************************************************
|
|
# 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>
|
|
# Anik Stark <anik.stark@psi.ch>
|
|
# *****************************************************************************
|
|
|
|
from frappy.core import StringIO, HasIO, Drivable, Parameter, Property, FloatRange, BoolType, \
|
|
EnumType, BUSY, IDLE, WARN
|
|
from frappy.errors import CommunicationFailedError
|
|
|
|
|
|
class IO(StringIO):
|
|
end_of_line = '\n'
|
|
identification = [('*IDN?', 'FUG.*')] # returns the fabrication number
|
|
default_settings = {'baudrate': 230000}
|
|
|
|
|
|
class Voltage(HasIO, Drivable):
|
|
|
|
ioClass = IO # define IO class for automatic creation of the IO module
|
|
|
|
value = Parameter('voltage reading', FloatRange(unit='V', fmtstr='%.2f'))
|
|
target = Parameter('voltage target value', FloatRange(0, 6500, unit='V', fmtstr='%.2f'), readonly=False)
|
|
setpoint = Parameter('effective voltage setpoint', FloatRange(unit='V', fmtstr='%.2f'))
|
|
on = Parameter('turn voltage on/off', BoolType(), readonly=False)
|
|
current = Parameter('current reading', FloatRange(unit='A', fmtstr='%.8f'))
|
|
overcurrent_protection = Parameter('flag to turn voltage off in current control', BoolType(), readonly=False, default=False)
|
|
current_limit = Parameter('current limit', FloatRange(unit='A', fmtstr='%.8f'), readonly=False, value=1e-6)
|
|
ramp = Parameter('ramp voltage value', FloatRange(unit='V/s', fmtstr='%.2f'), readonly=False)
|
|
ramp_mode = Parameter('ramp mode', EnumType(off=0, on=1, rampup_only=2), readonly=False)
|
|
tolerance = Property('tolerance for target vs. readback voltage', FloatRange(0, 5, unit='V'), default=1)
|
|
offset = Parameter('expected offset correction for target', FloatRange(-3, 0, unit='V'), default=-2.8, readonly=False)
|
|
_busy = False
|
|
_idle_status = IDLE, ''
|
|
_count = 0
|
|
_setpoint = 0
|
|
|
|
def request(self, name):
|
|
reply, txtvalue = self.communicate(f'>{name} ?').split(':')
|
|
if reply != name:
|
|
raise CommunicationFailedError(f'bad reply: {reply}')
|
|
return float(txtvalue) # from scientific notation to float
|
|
|
|
def set(self, name, value):
|
|
reply = self.communicate(f'>{name} {value:g}')
|
|
if reply != 'E0':
|
|
raise CommunicationFailedError(f'bad reply: {reply}')
|
|
return self.request(name)
|
|
|
|
def read_on(self):
|
|
return self.request('BON')
|
|
|
|
def write_on(self, value):
|
|
return self.set('BON', value)
|
|
|
|
def read_value(self):
|
|
return self.request('M0')
|
|
|
|
def write_target(self, target):
|
|
self._count = 0
|
|
self.target = target
|
|
self._setpoint = max(0, target + self.offset)
|
|
self.setFastPoll(True, 0.25)
|
|
if self.target > 0:
|
|
self._idle_status = IDLE, ''
|
|
if not self.read_on():
|
|
self.write_on(1)
|
|
self._off = False
|
|
else:
|
|
self._idle_status = IDLE, 'off'
|
|
self._busy = True
|
|
self.set('S0', self._setpoint)
|
|
self.read_status()
|
|
|
|
def read_setpoint(self):
|
|
return self.request('S0A')
|
|
|
|
def read_current(self):
|
|
return self.request('M1')
|
|
|
|
def write_overcurrent_protection(self, value):
|
|
self._idle_status = IDLE, ''
|
|
self.read_status()
|
|
|
|
def read_current_limit(self):
|
|
return self.request('S1')
|
|
|
|
def write_current_limit(self, value):
|
|
self._idle_status = IDLE, ''
|
|
self.read_status()
|
|
return self.set('S1', value)
|
|
|
|
def read_ramp(self):
|
|
return self.request('S0R')
|
|
|
|
def write_ramp(self, value):
|
|
return self.set('S0R', value)
|
|
|
|
def read_ramp_mode(self):
|
|
return self.request('S0B')
|
|
|
|
def write_ramp_mode(self, value):
|
|
return self.set('S0B', value.value)
|
|
|
|
def read_status(self):
|
|
name, status = self.communicate('>KS?').split(':')
|
|
if name != 'KS':
|
|
raise CommunicationFailedError(f'bad reply: {name}')
|
|
setpoint = self.read_setpoint()
|
|
if setpoint == 0 and self.value + self.offset < self.tolerance: # turn device off if ramp at 0
|
|
self.write_on(0)
|
|
self._idle_status = IDLE, 'off'
|
|
if status[0] == '1': # current control mode
|
|
if self.overcurrent_protection:
|
|
self.write_on(0)
|
|
self._busy = False
|
|
self.setFastPoll(False)
|
|
self._idle_status = WARN, 'overcurrent: device turned off'
|
|
return self._idle_status
|
|
if self._busy:
|
|
self._count += 1
|
|
if self._count > 80:
|
|
self.setFastPoll(False)
|
|
self._busy = False
|
|
return WARN, 'current limited'
|
|
if not self._busy:
|
|
return self._idle_status
|
|
if abs(setpoint - self._setpoint) > 0.1:
|
|
return BUSY, 'ramping'
|
|
target = max(self.target, -self.offset) if self.on else 0
|
|
if abs(target - self.value) < self.tolerance:
|
|
self.setFastPoll(False)
|
|
self._busy = False
|
|
return self._idle_status
|
|
return BUSY, ''
|
|
|
|
def stop(self):
|
|
"""stop ramping"""
|
|
if self.isBusy():
|
|
self.write_target(self.read_setpoint())
|