diff --git a/frappy_psi/SR830.py b/frappy_psi/SR830.py new file mode 100644 index 0000000..bed3ea3 --- /dev/null +++ b/frappy_psi/SR830.py @@ -0,0 +1,171 @@ +#!/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: Oksana Shliakhtun +# ***************************************************************************** + +import re +from frappy.core import StringIO, HasIO, Parameter, EnumType, FloatRange, TupleOf, ERROR, IDLE, WARN + + +class SR830_IO(StringIO): + end_of_line = b'\r' # should be or + identification = [('*IDN?', r'Stanford_Research_Systems,.*')] + + +class XY(HasIO): + XY = Parameter('X, Y', datatype=TupleOf(FloatRange(unit='V'), FloatRange(unit='V'))) + # channel = Property('output channel') + amp = Parameter('oscill. amplit. control', FloatRange(4e-3, 5), unit='V', readonly=False) + freq = Parameter('oscill. frequen. control', FloatRange(1e-3, 102000), unit='Hz', readonly=False) + phase = Parameter('reference phase control', FloatRange(-360, 729), unit='deg', readonly=False) + + sen_range = ['2nV', '5nV', '10nV', '20nV', '50nV', '100nV', '200nV', '500nV', + '1uV', '2uV', '5uV', '10uV', '20uV', '50uV', '100uV', '200uV', '500uV', + '1mV', '2mV', '5mV', '10mV', '20mV', '50mV', '100mV', '200mV', '500mV', + '1V'] + + irange = Parameter('sensitivity index', EnumType('sensitivity index range', + {name: idx for idx, name in enumerate(sen_range)})) + + range = Parameter('sensitivity value', FloatRange(2e-9, 1), unit='V', default=1, readonly=False) + + time_const = {name: value for value, name in enumerate( + ['10us', '30us', '100us', '300us', '1ms', '3ms', '10ms', '30ms', '100ms', '300ms', + '1s', '3s', '10s', '30s', '100s', '300s', '1ks', '3ks', '10ks', '30ks'] + )} + + tc = Parameter('time const. value', FloatRange(1e-6, 3e4), unit='s', readonly=False) + itc = Parameter('time const. index', EnumType('time const. index range', time_const), readonly=False) + + ioClass = SR830_IO + + status_messages = [ + (ERROR, 'execution error', 2, 4), + (ERROR, 'illegal command', 2, 5), + (ERROR, 'reserve/input overload', 3, 0), + (ERROR, 'tc overload', 3, 1), + (ERROR, 'output overload', 3, 2), + (WARN, 'input queue overflow, cleared', 2, 0), + (WARN, 'output queue overflow, cleared', 2, 2), + (WARN, 'reference unlock', 3, 3), + (WARN, 'freq crosses 200 Hz', 3, 4), + (IDLE, 'no scan in progress', 1, 0), + (IDLE, 'no command execution in progress', 1, 1), + (IDLE, 'unused', 1, 7), + (IDLE, '', 2, 1), + (IDLE, '', 2, 3), + (IDLE, '', 3, 7), + (IDLE, '', 4, 0), + (IDLE, '', 4, 3), + ] + + # status = serial poll status byte, standard event status byte, lock-in status byte, error status byte + def read_status(self): + status_values = [ + int(self.communicate('*STB?')), # serial poll status byte + int(self.communicate('*ESR?')), # standard event status byte + int(self.communicate('LIAS?')), # lock-in status byte + int(self.communicate('ERRS?')), # error status byte + ] + + for vi in range(1, 5): + value = status_values[vi-1] + + for status_type, status_msg, curr_vi, bit in self.status_messages: + if curr_vi == vi and value & (1 << bit): + # conv_status = HasConvergence.read_status(self) + return status_type, status_msg + + def string_to_value(self, value): + value_with_unit = re.compile(r'(\d+)([pnumkMG]?)') + value, pfx = value_with_unit.match(value).groups() + pfx_dict = {'p': 1e-12, 'n': 1e-9, 'u': 1e-6, 'm': 1e-3, 'k': 1e3, 'M': 1e6, 'G': 1e9} + if pfx in pfx_dict: + value = round(float(value) * pfx_dict[pfx], 12) + return float(value) + + def read_value(self): + self.XY = self.communicate('SNAP? 1, 2') + return XY + + def read_irange(self): + return int(self.communicate('SENS?')) + + def read_range(self): + idx = self.read_irange() + name = self.sen_range[idx] + value = self.string_to_value(name) + return value + + def write_irange(self, irange): + value = int(irange) + self.communicate(f'SENS {value}') + return value + + def write_range(self, target): + target = float(target) + cl_idx = None + cl_value = float('-inf') + + for idx, sen_value in enumerate(self.sen_range): + value = self.string_to_value(self.sen_range) + + if target >= value > cl_value: + cl_value = value + cl_idx = idx + + self.communicate(f'SENS {cl_idx}') + return cl_value + + def read_tc(self): + return float(self.communicate('OFLT?')) + + def write_tc(self, target): + self.communicate(f'OFLT {target}') + + def read_itc(self): + return int(self.communicate(f'OFLT?')) + + def write_itc(self, target): + self.communicate(f'OFLT {target}') + return self.read_itc() + + def read_phase(self): + return float(self.communicate('PHAS?')) + + def write_phase(self, value): + self.communicate(f'PHAS {value}') + return value + + def read_freq(self): + return float(self.communicate('FREQ?')) + + def write_freq(self, value): + self.communicate(f'FREQ {value}') + return value + + def read_amp(self): + return float(self.communicate('SLVL?')) + + def write_amp(self, value): + self.communicate(f'SLVL {value}') + return value + + def auto_phase(self): + return self.communicate('APHS') +