# ***************************************************************************** # 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())