diff --git a/frappy_psi/hcp.py b/frappy_psi/hcp.py new file mode 100644 index 00000000..1c67cc10 --- /dev/null +++ b/frappy_psi/hcp.py @@ -0,0 +1,154 @@ +# ***************************************************************************** +# 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 +# Anik Stark +# ***************************************************************************** + +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())