def __init__(self, parent_instrument, command_prefix="POWer"): super().__init__(parent_instrument) self.cmd_prefix = command_prefix # setting properties requiring methods of class # properties need to be set as attributes of class not of instance setattr(self.__class__, 'auto_range', Instrument.control( self._cmd("RANGe:Auto?"), self._cmd("RANGe:Auto %d"), "Auto Range Setting", set_process=lambda v: bool(v), get_process=lambda v: bool(v) )) setattr(self.__class__, 'range_min', Instrument.measurement( self._cmd("RANGe? MINimum"), "Minimum settable range")) setattr(self.__class__, 'range_max', Instrument.measurement( self._cmd("RANGe? MAXimum"), "Maximum settable range")) setattr(self.__class__, 'reference_min', Instrument.measurement( self._cmd("REFerence? MINimum"), "Minimum settable reference value")) setattr(self.__class__, 'reference_state', Instrument.control( self._cmd("REFerence:STATe?"), self._cmd("REFerence:STATe %d"), "Switch to delta mode", set_process=lambda v: bool(v))) setattr(self.__class__, 'reference_max', Instrument.measurement( self._cmd("REFerence? MAXimum"), "Maximum settable reference value")) setattr(self.__class__, 'reference_default', Instrument.measurement(self._cmd("REFerence? DEFault"), "Default reference value"))
class SR510(Instrument): TIME_CONSTANTS = {1e-3: 1, 3e-3: 2, 10e-3: 3, 30e-3: 4, 100e-3: 5, 300e-3: 6, 1: 7, 3: 8, 10: 9, 30: 10, 100: 11, } SENSITIVITIES = {10e-9: 1, 20e-9: 2, 50e-9: 3, 100e-9: 4, 200e-9: 5, 500e-9: 6, 1e-6: 7, 2e-6: 8, 5e-6: 9, 10e-6: 10, 20e-6: 11, 50e-6: 12, 100e-6: 13, 200e-6: 14, 500e-6: 15, 1e-3: 16, 2e-3: 17, 5e-3: 18, 10e-3: 19, 20e-3: 20, 50e-3: 21, 100e-3: 22, 200e-3: 23, 500e-3: 24, } phase = Instrument.control("P", "P %g", """A float property that represents the SR510 reference to input phase offset in degrees. Queries return values between -180 and 180 degrees. This property can be set with a range of values between -999 to 999 degrees. Set values are mapped internal in the lockin to -180 and 180 degrees.""", validator=truncated_range, values=[-999, 999], ) time_constant = Instrument.control("T1", "T1,%d", """A float property that represents the SR510 PRE filter time constant. This property can be set.""", validator=truncated_discrete_set, values=TIME_CONSTANTS, map_values=True, ) sensitivity = Instrument.control("G", "G%d", """A float property that represents the SR510 sensitivity value. This property can be set.""", validator=truncated_discrete_set, values=SENSITIVITIES, map_values=True, ) frequency = Instrument.measurement("F", """A float property representing the SR510 input reference frequency""", ) status = Instrument.measurement("Y", """A string property representing the bits set within the SR510 status byte""", get_process=lambda s: bin(int(s))[2:], ) output = Instrument.measurement("Q", """A float property that represents the SR510 output voltage in Volts.""", ) def __init__(self, resourceName, **kwargs): kwargs.setdefault('write_termination', '\r') super(SR510, self).__init__( resourceName, "Stanford Research Systems SR510 Lock-in amplifier", includeSCPI=False, **kwargs, )
class Fluke7341(Instrument): """ Represents the compact constant temperature bath from Fluke """ set_point = Instrument.control( "s", "s=%g", """ A `float` property to set the bath temperature set-point. Valid values are in the range -40 to 150 °C. The unit is as defined in property :attr:`~.unit`. This property can be read """, validator=strict_range, values=(-40, 150), ) unit = Instrument.control( "u", "u=%s", """ A string property that controls the temperature unit. Possible values are `c` for Celsius and `f` for Fahrenheit`.""", validator=strict_discrete_set, values=('c', 'f'), ) temperature = Instrument.measurement( "t", """ Read the current bath temperature. The unit is as defined in property :attr:`unit`.""", ) id = Instrument.measurement( "*ver", """ Read the instrument model """, preprocess_reply=lambda x: x, get_process=lambda x: "Fluke,{},NA,{}".format(x[0][4:], x[1])) def __init__(self, resource_name, **kwargs): super().__init__(resource_name, "Fluke 7341", timeout=2000, write_termination='\r\n', preprocess_reply=lambda x: x.split()[1], includeSCPI=False, **kwargs) if isinstance(self.adapter, VISAAdapter): self.adapter.connection.baud_rate = 2400
class GenericInstrument(FakeInstrument): # Use truncated_range as this easily lets us test for the range boundaries fake_ctrl = Instrument.control( "", "%d", "docs", validator=truncated_range, values=(1, 10), dynamic=True, ) fake_setting = Instrument.setting( "%d", "docs", validator=truncated_range, values=(1, 10), dynamic=True, ) fake_measurement = Instrument.measurement( "", "docs", values={ 'X': 1, 'Y': 2, 'Z': 3 }, map_values=True, dynamic=True, )
class Keithley6487(Instrument): """ Keithley 6487 Picoammeter """ ############## # Properties # ############## # sweep_state = Instrument.measurement( # ":SOUR:VOLT:SWE:STAT?", # """ Query if sweep running: 1 = sweep in progress. """ # ) buffer_size = Instrument.measurement( ":TRAC:POIN:ACT?", """ Returns number of readings actually stored in buffer. """) ########### # Methods # ########### def __init__(self, adapter, **kwargs): super(Keithley6487, self).__init__(adapter, "Keithley 6487", **kwargs) def reset(self): self.write("*RST") def configure_sweep(self, start, stop, step, delay, nplc, polarity): self.write('SYST:ZCH OFF') log.info("Zero-checking turned off") self.write('AVER:COUN {:d}'.format(3)) self.write('AVER:TCON {:s}'.format('rep')) self.write('AVER ON') self.write('SENS:CURR:NPLC {:0.2f}'.format(nplc)) self.write('SOUR:VOLT:SWE:STAR {:0.1f}'.format(start)) log.info("Sweep start value set") if polarity == 'Anode': self.write('SOUR:VOLT:SWE:STOP {:0.1f}'.format(-stop)) if polarity == 'Cathode': self.write('SOUR:VOLT:SWE:STOP {:0.1f}'.format(stop)) self.write('SOUR:VOLT:SWE:STEP {:0.2f}'.format(step)) self.write('SOUR:VOLT:SWE:DEL {:0.3f}'.format(delay / 1e3)) self.write('FORM:ELEM ALL') # Include all elements in the trace data self.write('FORM:SREG ASC' ) # Set output format of status register to ascii (decimal) self.write('ARM:COUN {:d}'.format(int(abs((stop - start) / step) + 1))) def start_sweep(self): self.write("SOUR:VOLT:SWE:INIT") self.write("INIT") def sweep_state(self): self.write("*CLS") try: resp = int(self.ask("*STB?")) resp = resp & 0x80 except: resp = 1 return resp
class AH2700A(AH2500A): """ Andeen Hagerling 2700A Precision Capacitance Bridge implementation """ def __init__(self, adapter, name="Andeen Hagerling 2700A Precision Capacitance Bridge", timeout=5000, **kwargs): super(AH2700A, self).__init__(adapter, name=name, timeout=timeout, **kwargs) id = Instrument.measurement("*IDN?", """ Reads the instrument identification """) config = Instrument.measurement( "SHOW ALL", """ Read out configuration """, ) frequency = Instrument.control( "SH FR", "FR %.1f", """test frequency used for the measurements. Allowed are values between 50 and 20000 Hz. The device selects the closest possible frequency to the given value.""", validator=strict_range, values=[50, 20000], # typical reply: "FREQUENCY 1200.0 Hz" get_process=lambda v: float(AH2500A._renumeric.search(v).group(0)), ) def reset(self): """ Resets the instrument. """ self.write("*RST") def trigger(self): """ Triggers a new measurement without blocking and waiting for the return value. """ self.write("*TRG") self._triggered = True
class AdvantestR3767CG(Instrument): """ Represents the Advantest R3767CG VNA. Implements controls to change the analysis range and to retreve the data for the trace. """ id = Instrument.measurement("*IDN?", """ Reads the instrument identification """) center_frequency = Instrument.control(":FREQ:CENT?", ":FREQ:CENT %d", """Center Frequency in Hz""", validator=strict_range, values=[300000, 8000000000]) span_frequency = Instrument.control(":FREQ:SPAN?", ":FREQ:SPAN %d", """Span Frequency in Hz""", validator=strict_range, values=[1, 8000000000]) start_frequency = Instrument.control(":FREQ:STAR?", ":FREQ:STAR %d", """ Starting frequency in Hz """, validator=strict_range, values=[1, 8000000000]) stop_frequency = Instrument.control(":FREQ:STOP?", ":FREQ:STOP %d", """ Stoping frequency in Hz """, validator=strict_range, values=[1, 8000000000]) trace_1 = Instrument.measurement( "TRAC:DATA? FDAT1", """ Reads the Data array from trace 1 after formatting """) def __init__(self, resourceName, **kwargs): super(AdvantestR3767CG, self).__init__(resourceName, "Advantest R3767CG", **kwargs) # Tell unit to operate in IEEE488.2-1987 command mode. self.write("OLDC OFF")
class Fake(FakeInstrument): x = Instrument.measurement( "", "", values={ 'X': 1, 'Y': 2, 'Z': 3 }, map_values=True, dynamic=dynamic, )
class HP34401A(Instrument): """ Represents the HP 34401A instrument. """ voltage_dc = Instrument.measurement("MEAS:VOLT:DC? DEF,DEF", "DC voltage, in Volts") voltage_ac = Instrument.measurement("MEAS:VOLT:AC? DEF,DEF", "AC voltage, in Volts") current_dc = Instrument.measurement("MEAS:CURR:DC? DEF,DEF", "DC current, in Amps") current_ac = Instrument.measurement("MEAS:CURR:AC? DEF,DEF", "AC current, in Amps") resistance = Instrument.measurement("MEAS:RES? DEF,DEF", "Resistance, in Ohms") resistance_4w = Instrument.measurement( "MEAS:FRES? DEF,DEF", "Four-wires (remote sensing) resistance, in Ohms") def __init__(self, resourceName, **kwargs): super(HP34401A, self).__init__(resourceName, "HP 34401A", **kwargs)
class Agilent34410A(Instrument): """ Represent the HP/Agilent/Keysight 34410A and related multimeters. Implemented measurements: voltage_dc, voltage_ac, current_dc, current_ac, resistance, resistance_4w """ # only the most simple functions are implemented voltage_dc = Instrument.measurement("MEAS:VOLT:DC? DEF,DEF", "DC voltage, in Volts") voltage_ac = Instrument.measurement("MEAS:VOLT:AC? DEF,DEF", "AC voltage, in Volts") current_dc = Instrument.measurement("MEAS:CURR:DC? DEF,DEF", "DC current, in Amps") current_ac = Instrument.measurement("MEAS:CURR:AC? DEF,DEF", "AC current, in Amps") resistance = Instrument.measurement("MEAS:RES? DEF,DEF", "Resistance, in Ohms") resistance_4w = Instrument.measurement( "MEAS:FRES? DEF,DEF", "Four-wires (remote sensing) resistance, in Ohms") def __init__(self, adapter, **kwargs): super(Agilent34410A, self).__init__( adapter, "HP/Agilent/Keysight 34410A Multimeter", **kwargs )
class ANC300Controller(Instrument): """ Attocube ANC300 Piezo stage controller with several axes :param host: host address of the instrument :param axisnames: a list of axis names which will be used to create properties with these names :param passwd: password for the attocube standard console :param query_delay: delay between sending and reading (default 0.05 sec) :param kwargs: Any valid key-word argument for TelnetAdapter """ version = Instrument.measurement( "ver", """ Version number and instrument identification """) controllerBoardVersion = Instrument.measurement( "getcser", """ Serial number of the controller board """) def __init__(self, host, axisnames, passwd, query_delay=0.05, **kwargs): kwargs['query_delay'] = query_delay super().__init__(AttocubeConsoleAdapter(host, 7230, passwd, **kwargs), "attocube ANC300 Piezo Controller", includeSCPI=False, **kwargs) self._axisnames = axisnames for i, axis in enumerate(axisnames): setattr(self, axis, Axis(self, i + 1)) def ground_all(self): """ Grounds all axis of the controller. """ for attr in self._axisnames: attribute = getattr(self, attr) if isinstance(attribute, Axis): attribute.mode = 'gnd' def stop_all(self): """ Stop all movements of the axis. """ for attr in self._axisnames: attribute = getattr(self, attr) if isinstance(attribute, Axis): attribute.stop()
class Keithley2750(Instrument): """ Represents the Keithley2750 multimeter/switch system and provides a high-level interface for interacting with the instrument. """ closed_channels = Instrument.measurement( ":ROUTe:CLOSe?", "Reads the list of closed channels", get_process=clean_closed_channels) def __init__(self, adapter, **kwargs): super(Keithley2750, self).__init__(adapter, "Keithley 2750 Multimeter/Switch System", **kwargs) def open(self, channel): """ Opens (disconnects) the specified channel. :param int channel: 3-digit number for the channel :return: None """ self.write(":ROUTe:MULTiple:OPEN (@{})".format(channel)) def close(self, channel): """ Closes (connects) the specified channel. :param int channel: 3-digit number for the channel :return: None """ # Note: if `MULTiple` is omitted, then the specified channel will close, # but all other channels will open. self.write(":ROUTe:MULTiple:CLOSe (@{})".format(channel)) def open_all(self): """ Opens (disconnects) all the channels on the switch matrix. :return: None """ self.write(":ROUTe:OPEN:ALL")
class Channel3(object): """ The optional channel 3 supports only reading it's impedance and coupling. Coupling is always AC, for simplicity this is hardcoded. """ IMP_MAPPING = { 50: '50', 50.0: '50', 1e6: '1M', 1000000: '1M', 1000000.0: '1M' } impedance = Instrument.measurement( "IMP?", """ A number property that reads the input impedance. The value returned is 50, or Not a Number 9.91E37 if Option 030/050 Channel 3 is not installed. """) coupling = "AC" def __init__(self, instrument, number): self.instrument = instrument self.number = number def values(self, command, **kwargs): """ Reads a set of values from the instrument through the adapter, passing on any key-word arguments. """ return self.instrument.values(":INP%d:%s" % (self.number, command), **kwargs) def ask(self, command): self.instrument.ask(":INP%d:%s" % (self.number, command)) def write(self, command): self.instrument.write(":INP%d:%s" % (self.number, command)) def read(self): self.instrument.read()
class SR860(Instrument): SENSITIVITIES = [ 1e-9, 2e-9, 5e-9, 10e-9, 20e-9, 50e-9, 100e-9, 200e-9, 500e-9, 1e-6, 2e-6, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6, 500e-6, 1e-3, 2e-3, 5e-3, 10e-3, 20e-3, 50e-3, 100e-3, 200e-3, 500e-3, 1 ] TIME_CONSTANTS = [ 1e-6, 3e-6, 10e-6, 30e-6, 100e-6, 300e-6, 1e-3, 3e-3, 10e-3, 30e-3, 100e-3, 300e-3, 1, 3, 10, 30, 100, 300, 1e3, 3e3, 10e3, 30e3 ] ON_OFF_VALUES = ['0', '1'] SCREEN_LAYOUT_VALUES = ['0', '1', '2', '3', '4', '5'] EXPANSION_VALUES = ['0', '1', '2,'] CHANNEL_VALUES = ['OCH1', 'OCH2'] OUTPUT_VALUES = ['XY', 'RTH'] INPUT_TIMEBASE = ['AUTO', 'IN'] INPUT_DCMODE = ['COM', 'DIF', 'common', 'difference'] INPUT_REFERENCESOURCE = ['INT', 'EXT', 'DUAL', 'CHOP'] INPUT_REFERENCETRIGGERMODE = ['SIN', 'POS', 'NEG', 'POSTTL', 'NEGTTL'] INPUT_REFERENCEEXTERNALINPUT = ['50OHMS', '1MEG'] INPUT_SIGNAL_INPUT = ['VOLT', 'CURR', 'voltage', 'current'] INPUT_VOLTAGE_MODE = ['A', 'A-B'] INPUT_COUPLING = ['AC', 'DC'] INPUT_SHIELDS = ['Float', 'Ground'] INPUT_RANGE = ['1V', '300M', '100M', '30M', '10M'] INPUT_GAIN = ['1MEG', '100MEG'] INPUT_FILTER = ['Off', 'On'] LIST_PARAMETER = [ 'i=', '0=Xoutput', '1=Youtput', '2=Routput', 'Thetaoutput', '4=Aux IN1', '5=Aux IN2', '6=Aux IN3', '7=Aux IN4', '8=Xnoise', '9=Ynoise', '10=AUXOut1', '11=AuxOut2', '12=Phase', '13=Sine Out amplitude', '14=DCLevel', '15I=nt.referenceFreq', '16=Ext.referenceFreq' ] LIST_HORIZONTAL_TIME_DIV = [ '0=0.5s', '1=1s', '2=2s', '3=5s', '4=10s', '5=30s', '6=1min', '7=2min', '8=5min', '9=10min', '10=30min', '11=1hour', '12=2hour', '13=6hour', '14=12hour', '15=1day', '16=2days' ] x = Instrument.measurement("OUTP? 0", """ Reads the X value in Volts """) y = Instrument.measurement("OUTP? 1", """ Reads the Y value in Volts """) magnitude = Instrument.measurement("OUTP? 2", """ Reads the magnitude in Volts. """) theta = Instrument.measurement("OUTP? 3", """ Reads the theta value in degrees. """) phase = Instrument.control( "PHAS?", "PHAS %0.7f", """ A floating point property that represents the lock-in phase in degrees. This property can be set. """, validator=truncated_range, values=[-360, 360]) frequency = Instrument.control( "FREQ?", "FREQ %0.6e", """ A floating point property that represents the lock-in frequency in Hz. This property can be set. """, validator=truncated_range, values=[0.001, 500000]) internalfrequency = Instrument.control( "FREQINT?", "FREQINT %0.6e", """A floating property that represents the internal lock-in frequency in Hz This property can be set.""", validator=truncated_range, values=[0.001, 500000]) harmonic = Instrument.control( "HARM?", "Harm %d", """An integer property that controls the harmonic that is measured. Allowed values are 1 to 99. Can be set.""", validator=strict_discrete_set, values=range(1, 99)) harmonicdual = Instrument.control( "HARMDUAL?", "HARMDUAL %d", """An integer property that controls the harmonic in dual reference mode that is measured. Allowed values are 1 to 99. Can be set.""", validator=strict_discrete_set, values=range(1, 99)) sine_voltage = Instrument.control( "SLVL?", "SLVL %0.9e", """A floating point property that represents the reference sine-wave voltage in Volts. This property can be set.""", validator=truncated_range, values=[1e-9, 2]) timebase = Instrument.control( "TBMODE?", "TBMODE %d", """Sets the external 10 MHZ timebase to auto(i=0) or internal(i=1).""", validator=strict_discrete_set, values=[0, 1], map_values=True) dcmode = Instrument.control( "REFM?", "REFM %d", """A string property that represents the sine out dc mode. This property can be set. Allowed values are:{}""".format( INPUT_DCMODE), validator=strict_discrete_set, values=INPUT_DCMODE, map_values=True) reference_source = Instrument.control( "RSRC?", "RSRC %d", """A string property that represents the reference source. This property can be set. Allowed values are:{}""".format( INPUT_REFERENCESOURCE), validator=strict_discrete_set, values=INPUT_REFERENCESOURCE, map_values=True) reference_triggermode = Instrument.control( "RTRG?", "RTRG %d", """A string property that represents the external reference trigger mode. This property can be set. Allowed values are:{}""".format( INPUT_REFERENCETRIGGERMODE), validator=strict_discrete_set, values=INPUT_REFERENCETRIGGERMODE, map_values=True) reference_externalinput = Instrument.control( "REFZ?", "REFZ&d", """A string property that represents the external reference input. This property can be set. Allowed values are:{}""".format( INPUT_REFERENCEEXTERNALINPUT), validator=strict_discrete_set, values=INPUT_REFERENCEEXTERNALINPUT, map_values=True) input_signal = Instrument.control( "IVMD?", "IVMD %d", """A string property that represents the signal input. This property can be set. Allowed values are:{}""".format( INPUT_SIGNAL_INPUT), validator=strict_discrete_set, values=INPUT_SIGNAL_INPUT, map_values=True) input_voltage_mode = Instrument.control( "ISRC?", "ISRC %d", """A string property that represents the voltage input mode. This property can be set. Allowed values are:{}""".format( INPUT_VOLTAGE_MODE), validator=strict_discrete_set, values=INPUT_VOLTAGE_MODE, map_values=True) input_coupling = Instrument.control( "ICPL?", "ICPL %d", """A string property that represents the input coupling. This property can be set. Allowed values are:{}""".format( INPUT_COUPLING), validator=strict_discrete_set, values=INPUT_COUPLING, map_values=True) input_shields = Instrument.control( "IGND?", "IGND %d", """A string property that represents the input shield grounding. This property can be set. Allowed values are:{}""".format( INPUT_SHIELDS), validator=strict_discrete_set, values=INPUT_SHIELDS, map_values=True) input_range = Instrument.control( "IRNG?", "IRNG %d", """A string property that represents the input range. This property can be set. Allowed values are:{}""".format(INPUT_RANGE), validator=strict_discrete_set, values=INPUT_RANGE, map_values=True) input_current_gain = Instrument.control( "ICUR?", "ICUR %d", """A string property that represents the current input gain. This property can be set. Allowed values are:{}""".format(INPUT_GAIN), validator=strict_discrete_set, values=INPUT_GAIN, map_values=True) sensitvity = Instrument.control( "SCAL?", "SCAL %d", """ A floating point property that controls the sensitivity in Volts, which can take discrete values from 2 nV to 1 V. Values are truncated to the next highest level if they are not exact. """, validator=truncated_discrete_set, values=SENSITIVITIES, map_values=True) time_constant = Instrument.control( "OFLT?", "OFLT %d", """ A floating point property that controls the time constant in seconds, which can take discrete values from 10 microseconds to 30,000 seconds. Values are truncated to the next highest level if they are not exact. """, validator=truncated_discrete_set, values=TIME_CONSTANTS, map_values=True) filter_slope = Instrument.control( "OFSL?", "OFSL %d", """A integer property that sets the filter slope to 6 dB/oct(i=0), 12 DB/oct(i=1), 18 dB/oct(i=2), 24 dB/oct(i=3).""", validator=strict_discrete_set, values=range(0, 3)) filer_synchronous = Instrument.control( "SYNC?", "SYNC %d", """A string property that represents the synchronous filter. This property can be set. Allowed values are:{}""".format( INPUT_FILTER), validator=strict_discrete_set, values=INPUT_FILTER, map_values=True) filter_advanced = Instrument.control( "ADVFILT?", "ADVFIL %d", """A string property that represents the advanced filter. This property can be set. Allowed values are:{}""".format( INPUT_FILTER), validator=strict_discrete_set, values=INPUT_FILTER, map_values=True) frequencypreset1 = Instrument.control( "PSTF? 0", "PSTF 0, %0.6e", """A floating point property that represents the preset frequency for the F1 preset button. This property can be set.""", validator=truncated_range, values=[0.001, 500000]) frequencypreset2 = Instrument.control( "PSTF? 1", "PSTF 1, %0.6e", """A floating point property that represents the preset frequency for the F2 preset button. This property can be set.""", validator=truncated_range, values=[0.001, 500000]) frequencypreset3 = Instrument.control( "PSTF? 2", "PSTF2, %0.6e", """A floating point property that represents the preset frequency for the F3 preset button. This property can be set.""", validator=truncated_range, values=[0.001, 500000]) frequencypreset4 = Instrument.control( "PSTF? 3", "PSTF3, %0.6e", """A floating point property that represents the preset frequency for the F4 preset button. This property can be set.""", validator=truncated_range, values=[0.001, 500000]) sine_amplitudepreset1 = Instrument.control( "PSTA? 0", "PSTA0, %0.9e", """Floating point property representing the preset sine out amplitude, for the A1 preset button. This property can be set.""", validator=truncated_range, values=[1e-9, 2]) sine_amplitudepreset2 = Instrument.control( "PSTA? 1", "PSTA1, %0.9e", """Floating point property representing the preset sine out amplitude, for the A2 preset button. This property can be set.""", validator=truncated_range, values=[1e-9, 2]) sine_amplitudepreset3 = Instrument.control( "PSTA? 2", "PSTA2, %0.9e", """Floating point property representing the preset sine out amplitude, for the A3 preset button. This property can be set.""", validator=truncated_range, values=[1e-9, 2]) sine_amplitudepreset4 = Instrument.control( "PSTA? 3", "PSTA 3, %0.9e", """Floating point property representing the preset sine out amplitude, for the A3 preset button. This property can be set.""", validator=truncated_range, values=[1e-9, 2]) sine_dclevelpreset1 = Instrument.control( "PSTL? 0", "PSTL 0, %0.3e", """A floating point property that represents the preset sine out dc level for the L1 button. This property can be set.""", validator=truncated_range, values=[-5, 5]) sine_dclevelpreset2 = Instrument.control( "PSTL? 1", "PSTL 1, %0.3e", """A floating point property that represents the preset sine out dc level for the L2 button. This property can be set.""", validator=truncated_range, values=[-5, 5]) sine_dclevelpreset3 = Instrument.control( "PSTL? 2", "PSTL 2, %0.3e", """A floating point property that represents the preset sine out dc level for the L3 button. This property can be set.""", validator=truncated_range, values=[-5, 5]) sine_dclevelpreset4 = Instrument.control( "PSTL? 3", "PSTL3, %0.3e", """A floating point property that represents the preset sine out dc level for the L4 button. This property can be set.""", validator=truncated_range, values=[-5, 5]) aux_out_1 = Instrument.control( "AUXV? 0", "AUXV 1, %f", """ A floating point property that controls the output of Aux output 1 in Volts, taking values between -10.5 V and +10.5 V. This property can be set.""", validator=truncated_range, values=[-10.5, 10.5]) # For consistency with other lock-in instrument classes dac1 = aux_out_1 aux_out_2 = Instrument.control( "AUXV? 1", "AUXV 2, %f", """ A floating point property that controls the output of Aux output 2 in Volts, taking values between -10.5 V and +10.5 V. This property can be set.""", validator=truncated_range, values=[-10.5, 10.5]) # For consistency with other lock-in instrument classes dac2 = aux_out_2 aux_out_3 = Instrument.control( "AUXV? 2", "AUXV 3, %f", """ A floating point property that controls the output of Aux output 3 in Volts, taking values between -10.5 V and +10.5 V. This property can be set.""", validator=truncated_range, values=[-10.5, 10.5]) # For consistency with other lock-in instrument classes dac3 = aux_out_3 aux_out_4 = Instrument.control( "AUXV? 3", "AUXV 4, %f", """ A floating point property that controls the output of Aux output 4 in Volts, taking values between -10.5 V and +10.5 V. This property can be set.""", validator=truncated_range, values=[-10.5, 10.5]) # For consistency with other lock-in instrument classes dac4 = aux_out_4 aux_in_1 = Instrument.measurement( "OAUX? 0", """ Reads the Aux input 1 value in Volts with 1/3 mV resolution. """) # For consistency with other lock-in instrument classes adc1 = aux_in_1 aux_in_2 = Instrument.measurement( "OAUX? 1", """ Reads the Aux input 2 value in Volts with 1/3 mV resolution. """) # For consistency with other lock-in instrument classes adc2 = aux_in_2 aux_in_3 = Instrument.measurement( "OAUX? 2", """ Reads the Aux input 3 value in Volts with 1/3 mV resolution. """) # For consistency with other lock-in instrument classes adc3 = aux_in_3 aux_in_4 = Instrument.measurement( "OAUX? 3", """ Reads the Aux input 4 value in Volts with 1/3 mV resolution. """) # For consistency with other lock-in instrument classes adc4 = aux_in_4 def snap(self, val1="X", val2="Y", val3=None): """retrieve 2 or 3 parameters at once parameters can be chosen by index, or enumeration as follows: j enumeration parameter j enumeration parameter 0 X X output 9 YNOise Ynoise 1 Y Youtput 10 OUT1 Aux Out1 2 R R output 11 OUT2 Aux Out2 3 THeta θ output 12 PHAse Reference Phase 4 IN1 Aux In1 13 SAMp Sine Out Amplitude 5 IN2 Aux In2 14 LEVel DC Level 6 IN3 Aux In3 15 FInt Int. Ref. Frequency 7 IN4 Aux In4 16 FExt Ext. Ref. Frequency 8 XNOise Xnoise :param val1: parameter enumeration/index :param val2: parameter enumeration/index :param val3: parameter enumeration/index (optional) Defaults: val1 = "X" val2 = "Y" val3 = None """ if val3 is None: return self.adapter.values( command=f"SNAP? {val1}, {val2}", separator=",", cast=float, ) else: return self.adapter.values( command=f"SNAP? {val1}, {val2}, {val3}", separator=",", cast=float, ) gettimebase = Instrument.measurement( "TBSTAT?", """Returns the current 10 MHz timebase source.""") extfreqency = Instrument.measurement( "FREQEXT?", """Returns the external frequency in Hz.""") detectedfrequency = Instrument.measurement( "FREQDET?", """Returns the actual detected frequency in HZ.""") get_signal_strength_indicator = Instrument.measurement( "ILVL?", """Returns the signal strength indicator.""") get_noise_bandwidth = Instrument.measurement( "ENBW?", """Returns the equivalent noise bandwidth, in hertz.""") # Display Commands front_panel = Instrument.control( "DBLK?", "DBLK %i", """Turns the front panel blanking on(i=0) or off(i=1).""", validator=strict_discrete_set, values=ON_OFF_VALUES, map_values=True) screen_layout = Instrument.control( "DLAY?", "DLAY %i", """A integer property that Sets the screen layout to trend(i=0), full strip chart history(i=1), half strip chart history(i=2), full FFT(i=3), half FFT(i=4) or big numerical(i=5).""", validator=strict_discrete_set, values=SCREEN_LAYOUT_VALUES, map_values=True) def screenshot(self): """Take screenshot on device The DCAP command saves a screenshot to a USB memory stick. This command is the same as pressing the [Screen Shot] key. A USB memory stick must be present in the front panel USB port. """ self.write("DCAP") parameter_DAT1 = Instrument.control( "CDSP? 0", "CDSP 0, %i", """A integer property that assigns a parameter to data channel 1(green). This parameters can be set. Allowed values are:{}""".format( LIST_PARAMETER), validator=strict_discrete_set, values=range(0, 16)) parameter_DAT2 = Instrument.control( "CDSP? 1", "CDSP 1, %i", """A integer property that assigns a parameter to data channel 2(blue). This parameters can be set. Allowed values are:{}""".format( LIST_PARAMETER), validator=strict_discrete_set, values=range(0, 16)) parameter_DAT3 = Instrument.control( "CDSP? 2", "CDSP 2, %i", """A integer property that assigns a parameter to data channel 3(yellow). This parameters can be set. Allowed values are:{}""".format( LIST_PARAMETER), validator=strict_discrete_set, values=range(0, 16)) parameter_DAT4 = Instrument.control( "CDSP? 3", "CDSP 3, %i", """A integer property that assigns a parameter to data channel 3(orange). This parameters can be set. Allowed values are:{}""".format( LIST_PARAMETER), validator=strict_discrete_set, values=range(0, 16)) strip_chart_dat1 = Instrument.control( "CGRF? 0", "CGRF 0, %i", """A integer property that turns the strip chart graph of data channel 1 off(i=0) or on(i=1). """, validator=strict_discrete_set, values=ON_OFF_VALUES, map_values=True) strip_chart_dat2 = Instrument.control( "CGRF? 1", "CGRF 1, %i", """A integer property that turns the strip chart graph of data channel 2 off(i=0) or on(i=1). """, validator=strict_discrete_set, values=ON_OFF_VALUES, map_values=True) strip_chart_dat3 = Instrument.control( "CGRF? 2", "CGRF 2, %i", """A integer property that turns the strip chart graph of data channel 1 off(i=0) or on(i=1). """, validator=strict_discrete_set, values=ON_OFF_VALUES, map_values=True) strip_chart_dat4 = Instrument.control( "CGRF? 3", "CGRF 3, %i", """A integer property that turns the strip chart graph of data channel 4 off(i=0) or on(i=1). """, validator=strict_discrete_set, values=ON_OFF_VALUES, map_values=True) # Strip Chart commands horizontal_time_div = Instrument.control( "GSPD?", "GSDP %i", """A integer property for the horizontal time/div according to the following table:{} """.format(LIST_HORIZONTAL_TIME_DIV), validator=strict_discrete_set, values=range(0, 16)) def __init__(self, resourceName, **kwargs): super().__init__(resourceName, "Stanford Research Systems SR860 Lock-in amplifier", **kwargs)
class ITC503(Instrument): """Represents the Oxford Intelligent Temperature Controller 503. .. code-block:: python itc = ITC503("GPIB::24") # Default channel for the ITC503 itc.control_mode = "RU" # Set the control mode to remote itc.heater_gas_mode = "AUTO" # Turn on auto heater and flow itc.auto_pid = True # Turn on auto-pid print(itc.temperature_setpoint) # Print the current set-point itc.temperature_setpoint = 300 # Change the set-point to 300 K itc.wait_for_temperature() # Wait for the temperature to stabilize print(itc.temperature_1) # Print the temperature at sensor 1 """ control_mode = Instrument.control( "X", "$C%d", """ A string property that sets the ITC in LOCAL or REMOTE and LOCKES, or UNLOCKES, the LOC/REM button. Allowed values are: LL: LOCAL & LOCKED RL: REMOTE & LOCKED LU: LOCAL & UNLOCKED RU: REMOTE & UNLOCKED. """, get_process=lambda v: int(v[5:6]), validator=strict_discrete_set, values={ "LL": 0, "RL": 1, "LU": 2, "RU": 3 }, map_values=True, ) heater_gas_mode = Instrument.control( "X", "$A%d", """ A string property that sets the heater and gas flow control to AUTO or MANUAL. Allowed values are: MANUAL: HEATER MANUAL, GAS MANUAL AM: HEATER AUTO, GAS MANUAL MA: HEATER MANUAL, GAS AUTO AUTO: HEATER AUTO, GAS AUTO. """, get_process=lambda v: int(v[3:4]), validator=strict_discrete_set, values={ "MANUAL": 0, "AM": 1, "MA": 2, "AUTO": 3 }, map_values=True, ) auto_pid = Instrument.control( "X", "$L%d", """ A boolean property that sets the Auto-PID mode on (True) or off (False). """, get_process=lambda v: int(v[12:13]), validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True, ) sweep_status = Instrument.control( "X", "$S%d", """ An integer property that sets the sweep status. Values are: 0: Sweep not running 1: Start sweep / sweeping to first set-point 2P - 1: Sweeping to set-point P 2P: Holding at set-point P. """, get_process=lambda v: int(v[7:9]), validator=strict_range, values=[0, 32]) temperature_setpoint = Instrument.control( "R0", "$T%f", """ A floating point property that controls the temperature set-point of the ITC in kelvin. """, get_process=lambda v: float(v[1:]), validator=truncated_range, values=[0, 301]) temperature_1 = Instrument.measurement( "R1", """ Reads the temperature of the sensor 1 in Kelvin. """, get_process=lambda v: float(v[1:]), ) temperature_2 = Instrument.measurement( "R2", """ Reads the temperature of the sensor 2 in Kelvin. """, get_process=lambda v: float(v[1:]), ) temperature_2 = Instrument.measurement( "R3", """ Reads the temperature of the sensor 3 in Kelvin. """, get_process=lambda v: float(v[1:]), ) temperature_error = Instrument.measurement( "R4", """ Reads the difference between the set-point and the measured temperature in Kelvin. Positive when set-point is larger than measured. """, get_process=lambda v: float(v[1:]), ) xpointer = Instrument.setting( "$x%d", """ An integer property to set pointers into tables for loading and examining values in the table. For programming the sweep table values from 1 to 16 are allowed, corresponding to the maximum number of steps. """, validator=strict_range, values=[0, 128]) ypointer = Instrument.setting( "$y%d", """ An integer property to set pointers into tables for loading and examining values in the table. For programming the sweep table the allowed values are: 1: Setpoint temperature, 2: Sweep-time to set-point, 3: Hold-time at set-point. """, validator=strict_range, values=[0, 128]) sweep_table = Instrument.control( "r", "$s%f", """ A property that sets values in the sweep table. Relies on the xpointer and ypointer to point at the location in the table that is to be set. """, get_process=lambda v: float(v[1:]), ) def __init__(self, resourceName, clear_buffer=True, **kwargs): super(ITC503, self).__init__(resourceName, "Oxford ITC503", includeSCPI=False, send_end=True, read_termination="\r", **kwargs) # Clear the buffer in order to prevent communication problems if clear_buffer: self.adapter.connection.clear() def wait_for_temperature(self, error=0.01, timeout=3600, check_interval=0.5, stability_interval=10, thermalize_interval=300, should_stop=lambda: False): """ Wait for the ITC to reach the set-point temperature. :param error: The maximum error in Kelvin under which the temperature is considered at set-point :param timeout: The maximum time the waiting is allowed to take. If timeout is exceeded, a TimeoutError is raised. If timeout is set to zero, no timeout will be used. :param check_interval: The time between temperature queries to the ITC. :param stability_interval: The time over which the temperature_error is to be below error to be considered stable. :param thermalize_interval: The time to wait after stabilizing for the system to thermalize. :param should_stop: Optional function (returning a bool) to allow the waiting to be stopped before its end. """ number_of_intervals = int(stability_interval / check_interval) stable_intervals = 0 attempt = 0 t0 = time() while True: if abs(self.temperature_error) < error: stable_intervals += 1 else: stable_intervals = 0 attempt += 1 if stable_intervals >= number_of_intervals: break if timeout >= 0 and (time() - t0) > timeout: raise TimeoutError( "Timeout expired while waiting for the Oxford ITC305 to \ reach the set-point temperature") if should_stop(): return sleep(check_interval) if attempt == 0: return t1 = time() + thermalize_interval while time() < t1: sleep(check_interval) if should_stop(): return return def program_sweep(self, temperatures, sweep_time, hold_time, steps=None): """ Program a temperature sweep in the controller. Stops any running sweep. After programming the sweep, it can be started using OxfordITC503.sweep_status = 1. :param temperatures: An array containing the temperatures for the sweep :param sweep_time: The time (or an array of times) to sweep to a set-point in minutes (between 0 and 1339.9). :param hold_time: The time (or an array of times) to hold at a set-point in minutes (between 0 and 1339.9). :param steps: The number of steps in the sweep, if given, the temperatures, sweep_time and hold_time will be interpolated into (approximately) equal segments """ # Check if in remote control if not self.control_mode.startswith("R"): raise AttributeError("Oxford ITC503 not in remote control mode") # Stop sweep if running to be able to write the program self.sweep_status = 0 # Convert input np.ndarrays temperatures = numpy.array(temperatures, ndmin=1) sweep_time = numpy.array(sweep_time, ndmin=1) hold_time = numpy.array(hold_time, ndmin=1) # Make steps array if steps is None: steps = temperatures.size steps = numpy.linspace(1, steps, steps) # Create interpolated arrays interpolator = numpy.round( numpy.linspace(1, steps.size, temperatures.size)) temperatures = numpy.interp(steps, interpolator, temperatures) interpolator = numpy.round( numpy.linspace(1, steps.size, sweep_time.size)) sweep_time = numpy.interp(steps, interpolator, sweep_time) interpolator = numpy.round( numpy.linspace(1, steps.size, hold_time.size)) hold_time = numpy.interp(steps, interpolator, hold_time) # Pad with zeros to wipe unused steps (total 16) of the sweep program padding = 16 - temperatures.size temperatures = numpy.pad(temperatures, (0, padding), 'constant', constant_values=temperatures[-1]) sweep_time = numpy.pad(sweep_time, (0, padding), 'constant') hold_time = numpy.pad(hold_time, (0, padding), 'constant') # Setting the arrays to the controller for line, (setpoint, sweep, hold) in \ enumerate(zip(temperatures, sweep_time, hold_time), 1): self.xpointer = line self.ypointer = 1 self.sweep_table = setpoint self.ypointer = 2 self.sweep_table = sweep self.ypointer = 3 self.sweep_table = hold
class Keithley6221(Instrument, KeithleyBuffer): """ Represents the Keithely 6221 AC and DC current source and provides a high-level interface for interacting with the instrument. .. code-block:: python keithley = Keithley6221("GPIB::1") keithley.clear() # Use the keithley as an AC source keithley.waveform_function = "square" # Set a square waveform keithley.waveform_amplitude = 0.05 # Set the amplitude in Amps keithley.waveform_offset = 0 # Set zero offset keithley.source_compliance = 10 # Set compliance (limit) in V keithley.waveform_dutycycle = 50 # Set duty cycle of wave in % keithley.waveform_frequency = 347 # Set the frequency in Hz keithley.waveform_ranging = "best" # Set optimal output ranging keithley.waveform_duration_cycles = 100 # Set duration of the waveform # Link end of waveform to Service Request status bit keithley.operation_event_enabled = 128 # OSB listens to end of wave keithley.srq_event_enabled = 128 # SRQ listens to OSB keithley.waveform_arm() # Arm (load) the waveform keithley.waveform_start() # Start the waveform keithley.adapter.wait_for_srq() # Wait for the pulse to finish keithley.waveform_abort() # Disarm (unload) the waveform keithley.shutdown() # Disables output """ ########## # OUTPUT # ########## source_enabled = Instrument.control( "OUTPut?", "OUTPut %d", """A boolean property that controls whether the source is enabled, takes values True or False. The convenience methods :meth:`~.Keithley6221.enable_source` and :meth:`~.Keithley6221.disable_source` can also be used.""", validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True) source_delay = Instrument.control( ":SOUR:DEL?", ":SOUR:DEL %g", """ A floating point property that sets a manual delay for the source after the output is turned on before a measurement is taken. When this property is set, the auto delay is turned off. Valid values are between 1e-3 [seconds] and 999999.999 [seconds].""", validator=truncated_range, values=[1e-3, 999999.999], ) ########## # SOURCE # ########## source_current = Instrument.control( ":SOUR:CURR?", ":SOUR:CURR %g", """ A floating point property that controls the source current in Amps. """, validator=truncated_range, values=[-0.105, 0.105]) source_compliance = Instrument.control( ":SOUR:CURR:COMP?", ":SOUR:CURR:COMP %g", """A floating point property that controls the compliance of the current source in Volts. valid values are in range 0.1 [V] to 105 [V].""", validator=truncated_range, values=[0.1, 105]) source_range = Instrument.control( ":SOUR:CURR:RANG?", ":SOUR:CURR:RANG:AUTO 0;:SOUR:CURR:RANG %g", """ A floating point property that controls the source current range in Amps, which can take values between -0.105 A and +0.105 A. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[-0.105, 0.105]) source_auto_range = Instrument.control( ":SOUR:CURR:RANG:AUTO?", ":SOUR:CURR:RANG:AUTO %d", """ A boolean property that controls the auto range of the current source. Valid values are True or False. """, values={ True: 1, False: 0 }, map_values=True, ) ################## # WAVE FUNCTIONS # ################## waveform_function = Instrument.control( ":SOUR:WAVE:FUNC?", ":SOUR:WAVE:FUNC %s", """ A string property that controls the selected wave function. Valid values are "sine", "ramp", "square", "arbitrary1", "arbitrary2", "arbitrary3" and "arbitrary4". """, values={ "sine": "SIN", "ramp": "RAMP", "square": "SQU", "arbitrary1": "ARB1", "arbitrary2": "ARB2", "arbitrary3": "ARB3", "arbitrary4": "ARB4", }, map_values=True) waveform_frequency = Instrument.control( ":SOUR:WAVE:FREQ?", ":SOUR:WAVE:FREQ %g", """A floating point property that controls the frequency of the waveform in Hertz. Valid values are in range 1e-3 to 1e5. """, validator=truncated_range, values=[1e-3, 1e5]) waveform_amplitude = Instrument.control( ":SOUR:WAVE:AMPL?", ":SOUR:WAVE:AMPL %g", """A floating point property that controls the (peak) amplitude of the waveform in Amps. Valid values are in range 2e-12 to 0.105. """, validator=truncated_range, values=[2e-12, 0.105]) waveform_offset = Instrument.control( ":SOUR:WAVE:OFFS?", ":SOUR:WAVE:OFFS %g", """A floating point property that controls the offset of the waveform in Amps. Valid values are in range -0.105 to 0.105. """, validator=truncated_range, values=[-0.105, 0.105]) waveform_dutycycle = Instrument.control( ":SOUR:WAVE:DCYC?", ":SOUR:WAVE:DCYC %g", """A floating point property that controls the duty-cycle of the waveform in percent for the square and ramp waves. Valid values are in range 0 to 100. """, validator=truncated_range, values=[0, 100]) waveform_duration_time = Instrument.control( ":SOUR:WAVE:DUR:TIME?", ":SOUR:WAVE:DUR:TIME %g", """A floating point property that controls the duration of the waveform in seconds. Valid values are in range 100e-9 to 999999.999. """, validator=truncated_range, values=[100e-9, 999999.999]) waveform_duration_cycles = Instrument.control( ":SOUR:WAVE:DUR:CYCL?", ":SOUR:WAVE:DUR:CYCL %g", """A floating point property that controls the duration of the waveform in cycles. Valid values are in range 1e-3 to 99999999900. """, validator=truncated_range, values=[1e-3, 99999999900]) def waveform_duration_set_infinity(self): """ Set the waveform duration to infinity. """ self.write(":SOUR:WAVE:DUR:TIME INF") waveform_ranging = Instrument.control( ":SOUR:WAVE:RANG?", ":SOUR:WAVE:RANG %s", """ A string property that controls the source ranging of the waveform. Valid values are "best" and "fixed". """, values={ "best": "BEST", "fixed": "FIX" }, map_values=True, ) waveform_use_phasemarker = Instrument.control( ":SOUR:WAVE:PMAR:STAT?", ":SOUR:WAVE:PMAR:STAT %s", """ A boolean property that controls whether the phase marker option is turned on or of. Valid values True (on) or False (off). Other settings for the phase marker have not yet been implemented.""", values={ True: 1, False: 0 }, map_values=True, ) def waveform_arm(self): """ Arm the current waveform function. """ self.write(":SOUR:WAVE:ARM") def waveform_start(self): """ Start the waveform output. Must already be armed """ self.write(":SOUR:WAVE:INIT") def waveform_abort(self): """ Abort the waveform output and disarm the waveform function. """ self.write(":SOUR:WAVE:ABOR") def define_arbitary_waveform(self, datapoints, location=1): """ Define the data points for the arbitrary waveform and copy the defined waveform into the given storage location. :param datapoints: a list (or numpy array) of the data points; all values have to be between -1 and 1; 100 points maximum. :param location: integer storage location to store the waveform in. Value must be in range 1 to 4. """ # Check validity of parameters if not isinstance(datapoints, (list, np.ndarray)): raise ValueError("datapoints must be a list or numpy array") elif len(datapoints) > 100: raise ValueError("datapoints cannot be longer than 100 points") elif not all([x >= -1 and x <= 1 for x in datapoints]): raise ValueError("all data points must be between -1 and 1") if location not in [1, 2, 3, 4]: raise ValueError("location must be in [1, 2, 3, 4]") # Make list of strings datapoints = [str(x) for x in datapoints] data = ", ".join(datapoints) # Write the data points to the Keithley 6221 self.write(":SOUR:WAVE:ARB:DATA %s" % data) # Copy the written data to the specified location self.write(":SOUR:WAVE:ARB:COPY %d" % location) # Select the newly made arbitrary waveform as waveform function self.waveform_function = "arbitrary%d" % location def __init__(self, adapter, **kwargs): super(Keithley6221, self).__init__(adapter, "Keithley 6221 SourceMeter", **kwargs) def enable_source(self): """ Enables the source of current or voltage depending on the configuration of the instrument. """ self.write("OUTPUT ON") def disable_source(self): """ Disables the source of current or voltage depending on the configuration of the instrument. """ self.write("OUTPUT OFF") def beep(self, frequency, duration): """ Sounds a system beep. :param frequency: A frequency in Hz between 65 Hz and 2 MHz :param duration: A time in seconds between 0 and 7.9 seconds """ self.write(":SYST:BEEP %g, %g" % (frequency, duration)) def triad(self, base_frequency, duration): """ Sounds a musical triad using the system beep. :param base_frequency: A frequency in Hz between 65 Hz and 1.3 MHz :param duration: A time in seconds between 0 and 7.9 seconds """ self.beep(base_frequency, duration) time.sleep(duration) self.beep(base_frequency * 5.0 / 4.0, duration) time.sleep(duration) self.beep(base_frequency * 6.0 / 4.0, duration) display_enabled = Instrument.control( ":DISP:ENAB?", ":DISP:ENAB %d", """ A boolean property that controls whether or not the display of the sourcemeter is enabled. Valid values are True and False. """, values={ True: 1, False: 0 }, map_values=True, ) @property def error(self): """ Returns a tuple of an error code and message from a single error. """ err = self.values(":system:error?") if len(err) < 2: err = self.read() # Try reading again code = err[0] message = err[1].replace('"', '') return (code, message) def check_errors(self): """ Logs any system errors reported by the instrument. """ code, message = self.error while code != 0: t = time.time() log.info("Keithley 6221 reported error: %d, %s" % (code, message)) code, message = self.error if (time.time() - t) > 10: log.warning("Timed out for Keithley 6221 error retrieval.") def reset(self): """ Resets the instrument and clears the queue. """ self.write("status:queue:clear;*RST;:stat:pres;:*CLS;") def trigger(self): """ Executes a bus trigger, which can be used when :meth:`~.trigger_on_bus` is configured. """ return self.write("*TRG") def trigger_immediately(self): """ Configures measurements to be taken with the internal trigger at the maximum sampling rate. """ self.write(":ARM:SOUR IMM;:TRIG:SOUR IMM;") def trigger_on_bus(self): """ Configures the trigger to detect events based on the bus trigger, which can be activated by :code:`GET` or :code:`*TRG`. """ self.write(":ARM:SOUR BUS;:TRIG:SOUR BUS;") def set_timed_arm(self, interval): """ Sets up the measurement to be taken with the internal trigger at a variable sampling rate defined by the interval in seconds between sampling points """ if interval > 99999.99 or interval < 0.001: raise RangeException("Keithley 6221 can only be time" " triggered between 1 mS and 1 Ms") self.write(":ARM:SOUR TIM;:ARM:TIM %.3f" % interval) def trigger_on_external(self, line=1): """ Configures the measurement trigger to be taken from a specific line of an external trigger :param line: A trigger line from 1 to 4 """ cmd = ":ARM:SOUR TLIN;:TRIG:SOUR TLIN;" cmd += ":ARM:ILIN %d;:TRIG:ILIN %d;" % (line, line) self.write(cmd) def output_trigger_on_external(self, line=1, after='DEL'): """ Configures the output trigger on the specified trigger link line number, with the option of supplying the part of the measurement after which the trigger should be generated (default to delay, which is right before the measurement) :param line: A trigger line from 1 to 4 :param after: An event string that determines when to trigger """ self.write(":TRIG:OUTP %s;:TRIG:OLIN %d;" % (after, line)) def disable_output_trigger(self): """ Disables the output trigger for the Trigger layer """ self.write(":TRIG:OUTP NONE") def shutdown(self): """ Disables the output. """ log.info("Shutting down %s." % self.name) self.disable_source() ############### # Status bits # ############### measurement_event_enabled = Instrument.control( ":STAT:MEAS:ENAB?", ":STAT:MEAS:ENAB %d", """ An integer value that controls which measurement events are registered in the Measurement Summary Bit (MSB) status bit. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. """, cast=int, validator=truncated_range, values=[0, 65535], ) operation_event_enabled = Instrument.control( ":STAT:OPER:ENAB?", ":STAT:OPER:ENAB %d", """ An integer value that controls which operation events are registered in the Operation Summary Bit (OSB) status bit. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. """, cast=int, validator=truncated_range, values=[0, 65535], ) questionable_event_enabled = Instrument.control( ":STAT:QUES:ENAB?", ":STAT:QUES:ENAB %d", """ An integer value that controls which questionable events are registered in the Questionable Summary Bit (QSB) status bit. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. """, cast=int, validator=truncated_range, values=[0, 65535], ) standard_event_enabled = Instrument.control( "ESE?", "ESE %d", """ An integer value that controls which standard events are registered in the Event Summary Bit (ESB) status bit. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. """, cast=int, validator=truncated_range, values=[0, 65535], ) srq_event_enabled = Instrument.control( "*SRE?", "*SRE %d", """ An integer value that controls which event registers trigger the Service Request (SRQ) status bit. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. """, cast=int, validator=truncated_range, values=[0, 255], ) measurement_events = Instrument.measurement( ":STAT:MEAS?", """ An integer value that reads which measurement events have been registered in the Measurement event registers. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. Reading this value clears the register. """, cast=int, ) operation_events = Instrument.measurement( ":STAT:OPER?", """ An integer value that reads which operation events have been registered in the Operation event registers. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. Reading this value clears the register. """, cast=int, ) questionable_events = Instrument.measurement( ":STAT:QUES?", """ An integer value that reads which questionable events have been registered in the Questionable event registers. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. Reading this value clears the register. """, cast=int, ) standard_events = Instrument.measurement( "*ESR?", """ An integer value that reads which standard events have been registered in the Standard event registers. Refer to the Model 6220/6221 Reference Manual for more information about programming the status bits. Reading this value clears the register. """, cast=int, )
class DSP7265(Instrument): """This is the class for the DSP 7265 lockin amplifier""" voltage = Instrument.control( "OA.", "OA. %g", """ A floating point property that represents the voltage in Volts. This property can be set. """, validator=truncated_range, values=[0, 5]) frequency = Instrument.control( "OF.", "OF. %g", """ A floating point property that represents the lock-in frequency in Hz. This property can be set. """, validator=truncated_range, values=[0, 2.5e5]) dac1 = Instrument.control( "DAC. 1", "DAC. 1 %g", """ A floating point property that represents the output value on DAC1 in Volts. This property can be set. """, validator=truncated_range, values=[-12, 12]) dac2 = Instrument.control( "DAC. 2", "DAC. 2 %g", """ A floating point property that represents the output value on DAC2 in Volts. This property can be set. """, validator=truncated_range, values=[-12, 12]) dac3 = Instrument.control( "DAC. 3", "DAC. 3 %g", """ A floating point property that represents the output value on DAC3 in Volts. This property can be set. """, validator=truncated_range, values=[-12, 12]) dac4 = Instrument.control( "DAC. 4", "DAC. 4 %g", """ A floating point property that represents the output value on DAC4 in Volts. This property can be set. """, validator=truncated_range, values=[-12, 12]) harmonic = Instrument.control( "REFN", "REFN %d", """ An integer property that represents the reference harmonic mode control, taking values from 1 to 65535. This property can be set. """, validator=truncated_discrete_set, values=list(range(65535))) phase = Instrument.control( "REFP.", "REFP. %g", """ A floating point property that represents the reference harmonic phase in degrees. This property can be set. """, validator=modular_range, values=[0, 360]) x = Instrument.measurement("X.", """ Reads the X value in Volts """) y = Instrument.measurement("Y.", """ Reads the Y value in Volts """) xy = Instrument.measurement( "X.Y.", """ Reads both the X and Y values in Volts """) mag = Instrument.measurement( "Mag.", # TODO: Should be capitalized? """ Reads the magnitude in Volts """) adc1 = Instrument.measurement( "ADC. 1", """ Reads the input value of ADC1 in Volts """) adc2 = Instrument.measurement( "ADC. 2", """ Reads the input value of ADC2 in Volts """) id = Instrument.measurement("ID", """ Reads the instrument identification """) sensitivity = Instrument.control( "SEN.", "SEN %d", """ A floating point property that controls the sensitivity range in Volts, which can take discrete values from 2 nV to 1 V. This property can be set. """, validator=truncated_discrete_set, values=[ 0.0, 2.0e-9, 5.0e-9, 10.0e-9, 20.0e-9, 50.0e-9, 100.0e-9, 200.0e-9, 500.0e-9, 1.0e-6, 2.0e-6, 5.0e-6, 10.0e-6, 20.0e-6, 50.0e-6, 100.0e-6, 200.0e-6, 500.0e-6, 1.0e-3, 2.0e-3, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3, 200.0e-3, 500.0e-3, 1.0 ], map_values=True) slope = Instrument.control( "SLOPE", "SLOPE %d", """ A integer property that controls the filter slope in dB/octave, which can take the values 6, 12, 18, or 24 dB/octave. This property can be set. """, validator=truncated_discrete_set, values=[6, 12, 18, 24], map_values=True) time_constant = Instrument.control( "TC.", "TC %d", """ A floating point property that controls the time constant in seconds, which takes values from 10 microseconds to 50,000 seconds. This property can be set. """, validator=truncated_discrete_set, values=[ 10.0e-6, 20.0e-6, 40.0e-6, 80.0e-6, 160.0e-6, 320.0e-6, 640.0e-6, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3, 200.0e-3, 500.0e-3, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1.0e3, 2.0e3, 5.0e3, 10.0e3, 20.0e3, 50.0e3 ], map_values=True) def __init__(self, resourceName, **kwargs): super(DSP7265, self).__init__(resourceName, "Signal Recovery DSP 7265", **kwargs) self.curve_bits = { 'x': 1, 'y': 2, 'mag': 4, 'phase': 8, 'ADC1': 32, 'ADC2': 64, 'ADC3': 128 } # Pre-condition self.adapter.config(datatype='str', converter='s') def values(self, command): """ Rewrite the method because of extra character in return string.""" result = self.ask(command).strip() result = result.replace('\x00', '') # Remove extra unicode character try: return [float(x) for x in result.split(",")] except: return result def setDifferentialMode(self, lineFiltering=True): self.write("VMODE 3") self.write("LF %d 0" % 3 if lineFiltering else 0) def setChannelAMode(self): self.write("VMODE 1") @property def adc3(self): # 50,000 for 1V signal over 1 s integral = self.values("ADC 3") return float(integral) / (50000.0 * self.adc3_time) @property def adc3_time(self): # Returns time in seconds return self.values("ADC3TIME") / 1000.0 @adc3_time.setter def adc3_time(self, value): # Takes time in seconds self.write("ADC3TIME %g" % int(1000 * value)) sleep(value * 1.2) @property def auto_gain(self): return (int(self.values("AUTOMATIC")) == 1) @auto_gain.setter def auto_gain(self, value): if value: self.write("AUTOMATIC 1") else: self.write("AUTOMATIC 0") def auto_sensitivity(self): self.write("AS") def auto_phase(self): self.write("AQN") @property def gain(self): return self.values("ACGAIN") @gain.setter def gain(self, value): self.write("ACGAIN %d" % int(value / 10.0)) @property def reference(self): return "external" if int(self.ask("IE")) == 2 else "internal" @reference.setter def reference(self, value): if value == "internal": val = 0 elif value == "external": val = 2 else: raise Exception("Incorrect value for reference type." " Must be either internal or extenal.") self.write("IE %d" % val) def set_buffer(self, points, quantities=['x'], interval=10.0e-3): num = 0 for q in quantities: num += self.curve_bits[q] self.points = points self.write("CBD %d" % int(num)) self.write("LEN %d" % int(points)) # interval in increments of 5ms interval = int(float(interval) / 5.0e-3) self.write("STR %d" % interval) self.write("NC") def start_buffer(self): self.write("TD") def get_buffer(self, quantity='x', timeout=1.00, average=False): count = 0 maxCount = int(timeout / 0.05) failed = False while int(self.values("M")) != 0: # Sleeping sleep(0.05) if count > maxCount: # Count reached max value, wait longer before asking! failed = True break if not failed: data = [] # Getting data for i in range(self.length): val = self.values("DC. %d" % self.curve_bits[quantity]) data.append(val) if average: return np.mean(data) else: return data else: return [0.0]
class KeysightN5767A(Instrument): """ Represents the Keysight N5767A Power supply interface for interacting with the instrument. .. code-block:: python """ ############### # Current (A) # ############### current_range = Instrument.control( ":CURR?", ":CURR %g", """ A floating point property that controls the DC current range in Amps, which can take values from 0 to 25 A. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[0, 25], ) current = Instrument.measurement(":MEAS:CURR?", """ Reads a setting current in Amps. """) ############### # Voltage (V) # ############### voltage_range = Instrument.control( ":VOLT?", ":VOLT %g V", """ A floating point property that controls the DC voltage range in Volts, which can take values from 0 to 60 V. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[0, 60]) voltage = Instrument.measurement( "MEAS:VOLT?", """ Reads a DC voltage measurement in Volts. """) ############## #_status (0/1) # ############## _status = Instrument.measurement( ":OUTP?", """ Read power supply current output status. """, ) def enable(self): """ Enables the flow of current. """ self.write(":OUTP 1") def disable(self): """ Disables the flow of current. """ self.write(":OUTP 0") def is_enabled(self): """ Returns True if the current supply is enabled. """ return bool(self._status) def __init__(self, adapter, **kwargs): super(KeysightN5767A, self).__init__(adapter, "Keysight N5767A power supply", **kwargs) # Set up data transfer format if isinstance(self.adapter, VISAAdapter): self.adapter.config(is_binary=False, datatype='float32', converter='f', separator=',') def check_errors(self): """ Read all errors from the instrument.""" while True: err = self.values(":SYST:ERR?") if int(err[0]) != 0: errmsg = "Keysight N5767A: %s: %s" % (err[0], err[1]) log.error(errmsg + '\n') else: break
class FWBell5080(Instrument): """ Represents the F.W. Bell 5080 Handheld Gaussmeter and provides a high-level interface for interacting with the instrument :param port: The serial port of the instrument .. code-block:: python meter = FWBell5080('/dev/ttyUSB0') # Connects over serial port /dev/ttyUSB0 (Linux) meter.units = 'gauss' # Sets the measurement units to Gauss meter.range = 3e3 # Sets the range to 3 kG print(meter.field) # Reads and prints a field measurement in G fields = meter.fields(100) # Samples 100 field measurements print(fields.mean(), fields.std()) # Prints the mean and standard deviation of the samples """ id = Instrument.measurement( "*IDN?", """ Reads the idenfitication information. """ ) field = Instrument.measurement( ":MEAS:FLUX?", """ Reads a floating point value of the field in the appropriate units. """, get_process=lambda v: v.split(' ')[0] # Remove units ) UNITS = { 'gauss': 'DC:GAUSS', 'gauss ac': 'AC:GAUSS', 'tesla': 'DC:TESLA', 'tesla ac': 'AC:TESLA', 'amp-meter': 'DC:AM', 'amp-meter ac': 'AC:AM' } units = Instrument.control( ":UNIT:FLUX?", ":UNIT:FLUX%s", """ A string property that controls the field units, which can take the values: 'gauss', 'gauss ac', 'tesla', 'tesla ac', 'amp-meter', and 'amp-meter ac'. The AC versions configure the instrument to measure AC. """, validator=strict_discrete_set, values=UNITS, map_values=True, get_process=lambda v: v.replace(' ', ':') # Make output consistent with input ) def __init__(self, port): super().__init__( SerialAdapter(port, 2400, timeout=0.5), "F.W. Bell 5080 Handheld Gaussmeter" ) @property def range(self): """ A floating point property that controls the maximum field range in the active units. This can take the values of 300 G, 3 kG, and 30 kG for Gauss, 30 mT, 300 mT, and 3 T for Tesla, and 23.88 kAm, 238.8 kAm, and 2388 kAm for Amp-meter. """ i = self.values(":SENS:FLUX:RANG?", cast=int) if 'gauss' in self.units: return [300, 3e3, 30e3][i] elif 'tesla' in self.units: return [30e-3, 300e-3, 3][i] elif 'amp-meter' in self.units: return [23.88e3, 238.8e3, 2388e3][i] @range.setter def range(self, value): if 'gauss' in self.units: i = truncated_discrete_set(value, [300, 3e3, 30e3]) elif 'tesla' in self.units: i = truncated_discrete_set(value, [30e-3, 300e-3, 3]) elif 'amp-meter' in self.units: i = truncated_discrete_set(value, [23.88e3, 238.8e3, 2388e3]) self.write(":SENS:FLUX:RANG %d" % i) def read(self): """ Overwrites the :meth:`Instrument.read <pymeasure.instruments.Instrument.read>` method to remove the last 2 characters from the output. """ return super().read()[:-2] def ask(self, command): """ Overwrites the :meth:`Instrument.ask <pymeasure.instruments.Instrument.ask>` method to remove the last 2 characters from the output. """ return super().ask()[:-2] def values(self, command): """ Overwrites the :meth:`Instrument.values <pymeasure.instruments.Instrument.values>` method to remove the lastv2 characters from the output. """ return super().values()[:-2] def reset(self): """ Resets the instrument. """ self.write("*OPC") def fields(self, samples=1): """ Returns a numpy array of field samples for a given sample number. :param samples: The number of samples to preform """ if samples < 1: raise Exception("F.W. Bell 5080 does not support samples less than 1.") else: data = [self.field for i in range(int(samples))] return array(data, dtype=float64) def auto_range(self): """ Enables the auto range functionality. """ self.write(":SENS:FLUX:RANG:AUTO")
class Ametek7270(Instrument): """This is the class for the Ametek DSP 7270 lockin amplifier""" SENSITIVITIES = [ 0.0, 2.0e-9, 5.0e-9, 10.0e-9, 20.0e-9, 50.0e-9, 100.0e-9, 200.0e-9, 500.0e-9, 1.0e-6, 2.0e-6, 5.0e-6, 10.0e-6, 20.0e-6, 50.0e-6, 100.0e-6, 200.0e-6, 500.0e-6, 1.0e-3, 2.0e-3, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3, 200.0e-3, 500.0e-3, 1.0 ] TIME_CONSTANTS = [ 10.0e-6, 20.0e-6, 50.0e-6, 100.0e-6, 200.0e-6, 500.0e-6, 1.0e-3, 2.0e-3, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3, 200.0e-3, 500.0e-3, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1.0e3, 2.0e3, 5.0e3, 10.0e3, 20.0e3, 50.0e3, 100.0e3 ] sensitivity = Instrument.control( # NOTE: only for IMODE = 1. "SEN.", "SEN %d", """ A floating point property that controls the sensitivity range in Volts, which can take discrete values from 2 nV to 1 V. This property can be set. """, validator=truncated_discrete_set, values=SENSITIVITIES, map_values=True ) slope = Instrument.control( "SLOPE", "SLOPE %d", """ A integer property that controls the filter slope in dB/octave, which can take the values 6, 12, 18, or 24 dB/octave. This property can be set. """, validator=truncated_discrete_set, values=[6, 12, 18, 24], map_values=True ) time_constant = Instrument.control( # NOTE: only for NOISEMODE = 0 "TC.", "TC %d", """ A floating point property that controls the time constant in seconds, which takes values from 10 microseconds to 100,000 seconds. This property can be set. """, validator=truncated_discrete_set, values=TIME_CONSTANTS, map_values=True ) # TODO: Figure out if this actually can send for X1. X2. Y1. Y2. or not. # There's nothing in the manual about it but UtilMOKE sends these. x = Instrument.measurement("X.", """ Reads the X value in Volts """ ) y = Instrument.measurement("Y.", """ Reads the Y value in Volts """ ) x1 = Instrument.measurement("X1.", """ Reads the first harmonic X value in Volts """ ) y1 = Instrument.measurement("Y1.", """ Reads the first harmonic Y value in Volts """ ) x2 = Instrument.measurement("X2.", """ Reads the second harmonic X value in Volts """ ) y2 = Instrument.measurement("Y2.", """ Reads the second harmonic Y value in Volts """ ) xy = Instrument.measurement("XY.", """ Reads both the X and Y values in Volts """ ) mag = Instrument.measurement("MAG.", """ Reads the magnitude in Volts """ ) harmonic = Instrument.control( "REFN", "REFN %d", """ An integer property that represents the reference harmonic mode control, taking values from 1 to 127. This property can be set. """, validator=truncated_discrete_set, values=list(range(1,128)) ) phase = Instrument.control( "REFP.", "REFP. %g", """ A floating point property that represents the reference harmonic phase in degrees. This property can be set. """, validator=modular_range, values=[0,360] ) voltage = Instrument.control( "OA.", "OA. %g", """ A floating point property that represents the voltage in Volts. This property can be set. """, validator=truncated_range, values=[0,5] ) frequency = Instrument.control( "OF.", "OF. %g", """ A floating point property that represents the lock-in frequency in Hz. This property can be set. """, validator=truncated_range, values=[0,2.5e5] ) dac1 = Instrument.control( "DAC. 1", "DAC. 1 %g", """ A floating point property that represents the output value on DAC1 in Volts. This property can be set. """, validator=truncated_range, values=[-10,10] ) dac2 = Instrument.control( "DAC. 2", "DAC. 2 %g", """ A floating point property that represents the output value on DAC2 in Volts. This property can be set. """, validator=truncated_range, values=[-10,10] ) dac3 = Instrument.control( "DAC. 3", "DAC. 3 %g", """ A floating point property that represents the output value on DAC3 in Volts. This property can be set. """, validator=truncated_range, values=[-10,10] ) dac4 = Instrument.control( "DAC. 4", "DAC. 4 %g", """ A floating point property that represents the output value on DAC4 in Volts. This property can be set. """, validator=truncated_range, values=[-10,10] ) adc1 = Instrument.measurement("ADC. 1", """ Reads the input value of ADC1 in Volts """ ) adc2 = Instrument.measurement("ADC. 2", """ Reads the input value of ADC2 in Volts """ ) adc3 = Instrument.measurement("ADC. 3", """ Reads the input value of ADC3 in Volts """ ) adc4 = Instrument.measurement("ADC. 4", """ Reads the input value of ADC4 in Volts """ ) id = Instrument.measurement("ID", """ Reads the instrument identification """ ) def __init__(self, resourceName, **kwargs): super(Ametek7270, self).__init__( resourceName, "Ametek DSP 7270", **kwargs ) def set_voltage_mode(self): """ Sets instrument to voltage control mode """ self.write("IMODE 0") def set_differential_mode(self, lineFiltering=True): """ Sets instrument to differential mode -- assuming it is in voltage mode """ self.write("VMODE 3") self.write("LF %d 0" % 3 if lineFiltering else 0) def set_channel_A_mode(self): """ Sets instrument to channel A mode -- assuming it is in voltage mode """ self.write("VMODE 1") @property def auto_gain(self): return (int(self.ask("AUTOMATIC")) == 1) @auto_gain.setter def auto_gain(self, setval): if setval: self.write("AUTOMATIC 1") else: self.write("AUTOMATIC 0") def shutdown(self): """ Ensures the instrument in a safe state """ self.voltage = 0. self.isShutdown = True log.info("Shutting down %s" % self.name)
class LakeShore331(Instrument): """ Represents the Lake Shore 331 Temperature Controller and provides a high-level interface for interacting with the instrument. .. code-block:: python controller = LakeShore331("GPIB::1") print(controller.setpoint_1) # Print the current setpoint for loop 1 controller.setpoint_1 = 50 # Change the setpoint to 50 K controller.heater_range = 'low' # Change the heater range to Low controller.wait_for_temperature() # Wait for the temperature to stabilize print(controller.temperature_A) # Print the temperature at sensor A """ temperature_A = Instrument.measurement( "KRDG? A", """ Reads the temperature of the sensor A in Kelvin. """) temperature_B = Instrument.measurement( "KRDG? B", """ Reads the temperature of the sensor B in Kelvin. """) setpoint_1 = Instrument.control( "SETP? 1", "SETP 1, %g", """ A floating point property that controls the setpoint temperature in Kelvin for Loop 1. """) setpoint_2 = Instrument.control( "SETP? 2", "SETP 2, %g", """ A floating point property that controls the setpoint temperature in Kelvin for Loop 2. """) heater_range = Instrument.control( "RANGE?", "RANGE %d", """ A string property that controls the heater range, which can take the values: off, low, medium, and high. These values correlate to 0, 0.5, 5 and 50 W respectively. """, validator=strict_discrete_set, values={ 'off': 0, 'low': 1, 'medium': 2, 'high': 3 }, map_values=True) def __init__(self, adapter, **kwargs): super(LakeShore331, self).__init__(adapter, "Lake Shore 331 Temperature Controller", **kwargs) def disable_heater(self): """ Turns the :attr:`~.heater_range` to :code:`off` to disable the heater. """ self.heater_range = 'off' def wait_for_temperature(self, accuracy=0.1, interval=0.1, sensor='A', setpoint=1, timeout=360, should_stop=lambda: False): """ Blocks the program, waiting for the temperature to reach the setpoint within the accuracy (%), checking this each interval time in seconds. :param accuracy: An acceptable percentage deviation between the setpoint and temperature :param interval: A time in seconds that controls the refresh rate :param sensor: The desired sensor to read, either A or B :param setpoint: The desired setpoint loop to read, either 1 or 2 :param timeout: A timeout in seconds after which an exception is raised :param should_stop: A function that returns True if waiting should stop, by default this always returns False """ temperature_name = 'temperature_%s' % sensor setpoint_name = 'setpoint_%d' % setpoint # Only get the setpoint once, assuming it does not change setpoint_value = getattr(self, setpoint_name) def percent_difference(temperature): return abs(100 * (temperature - setpoint_value) / setpoint_value) t = time() while percent_difference(getattr(self, temperature_name)) > accuracy: sleep(interval) if (time() - t) > timeout: raise Exception( ("Timeout occurred after waiting %g seconds for " "the LakeShore 331 temperature to reach %g K.") % (timeout, setpoint)) if should_stop(): return
class Keithley2182(Instrument, KeithleyBuffer): """ Represents the Keithley 2182 Nanovoltmeter and provides a high-level interface for interacting with the instrument. .. code-block:: python meter = Keithley2182("GPIB::1") meter.measure_voltage() print(meter.voltage) """ MODES = { # 'current':'CURR:DC', 'current ac':'CURR:AC', 'voltage':'VOLT:DC', 'voltage ac':'VOLT:AC', # 'resistance':'RES', 'resistance 4W':'FRES', # 'period':'PER', 'frequency':'FREQ', 'temperature':'TEMP', 'diode':'DIOD', # 'continuity':'CONT' } mode = Instrument.control( ":CONF?", ":CONF:%s", """ A string property that controls the configuration mode for measurements, which can take the values: :code:'current' (DC), :code:'current ac', :code:'voltage' (DC), :code:'voltage ac', :code:'resistance' (2-wire), :code:'resistance 4W' (4-wire), :code:'period', :code:'frequency', :code:'temperature', :code:'diode', and :code:'frequency'. """, validator=strict_discrete_set, values=MODES, map_values=True, get_process=lambda v: v.replace('"', '') ) ############### # Voltage (V) # ############### voltage = Instrument.measurement(":READ?", """ Reads a DC or AC voltage measurement in Volts, based on the active :attr:`~.Keithley2182.mode`. """ ) voltage_range = Instrument.control( ":SENS:VOLT:RANG?", ":SENS:VOLT:RANG:AUTO 0;:SENS:VOLT:RANG %g", """ A floating point property that controls the DC voltage range in Volts, which can take values from 0 to 1010 V. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[0, 1010] ) voltage_reference = Instrument.control( ":SENS:VOLT:REF?", ":SENS:VOLT:REF %g", """ A floating point property that controls the DC voltage reference value in Volts, which can take values from -1010 to 1010 V. """, validator=truncated_range, values=[-1010, 1010] ) voltage_nplc = Instrument.control( ":SENS:CURRVOLT:NPLC?", ":SENS:VOLT:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the DC voltage measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """ ) voltage_digits = Instrument.control( ":SENS:VOLT:DIG?", ":SENS:VOLT:DIG %d", """ An integer property that controls the number of digits in the DC voltage readings, which can take values from 4 to 7. """, validator=truncated_discrete_set, values=[4, 5, 6, 7], cast=int ) voltage_ac_range = Instrument.control( ":SENS:VOLT:AC:RANG?", ":SENS:VOLT:RANG:AUTO 0;:SENS:VOLT:AC:RANG %g", """ A floating point property that controls the AC voltage range in Volts, which can take values from 0 to 757.5 V. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[0, 757.5] ) voltage_ac_reference = Instrument.control( ":SENS:VOLT:AC:REF?", ":SENS:VOLT:AC:REF %g", """ A floating point property that controls the AC voltage reference value in Volts, which can take values from -757.5 to 757.5 Volts. """, validator=truncated_range, values=[-757.5, 757.5] ) voltage_ac_nplc = Instrument.control( ":SENS:VOLT:AC:NPLC?", ":SENS:VOLT:AC:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the AC voltage measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """ ) voltage_ac_digits = Instrument.control( ":SENS:VOLT:AC:DIG?", ":SENS:VOLT:AC:DIG %d", """ An integer property that controls the number of digits in the AC voltage readings, which can take values from 4 to 7. """, validator=truncated_discrete_set, values=[4, 5, 6, 7], cast=int ) voltage_ac_bandwidth = Instrument.control( ":SENS:VOLT:AC:DET:BAND?", ":SENS:VOLT:AC:DET:BAND %g", """ A floating point property that sets the AC voltage detector bandwidth in Hz, which can take the values 3, 30, and 300 Hz. """, validator=truncated_discrete_set, values=[3, 30, 300] ) ################### # Temperature (C) # ################### temperature = Instrument.measurement(":READ?", """ Reads a temperature measurement in Celsius, based on the active :attr:`~.Keithley2182.mode`. """ ) temperature_reference = Instrument.control( ":SENS:TEMP:REF?", ":SENS:TEMP:REF %g", """ A floating point property that controls the temperature reference value in Celsius, which can take values from -200 to 1372 C. """, validator=truncated_range, values=[-200, 1372] ) temperature_nplc = Instrument.control( ":SENS:TEMP:NPLC?", ":SENS:TEMP:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the temperature measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """ ) temperature_digits = Instrument.control( ":SENS:TEMP:DIG?", ":SENS:TEMP:DIG %d", """ An integer property that controls the number of digits in the temperature readings, which can take values from 4 to 7. """, validator=truncated_discrete_set, values=[4, 5, 6, 7], cast=int ) ########### # Trigger # ########### trigger_count = Instrument.control( ":TRIG:COUN?", ":TRIG:COUN %d", """ An integer property that controls the trigger count, which can take values from 1 to 9,999. """, validator=truncated_range, values=[1, 9999], cast=int ) trigger_delay = Instrument.control( ":TRIG:SEQ:DEL?", ":TRIG:SEQ:DEL %g", """ A floating point property that controls the trigger delay in seconds, which can take values from 1 to 9,999,999.999 s. """, validator=truncated_range, values=[0, 999999.999] ) def __init__(self, adapter, **kwargs): super(Keithley2182, self).__init__( adapter, "Keithley 2182 Nanovoltmeter", **kwargs ) # Set up data transfer format if isinstance(self.adapter, VISAAdapter): self.adapter.config( is_binary=False, datatype='float32', converter='f', separator=',' ) # TODO: Clean up error checking def check_errors(self): """ Read all errors from the instrument.""" while True: err = self.values(":SYST:ERR?") if int(err[0]) != 0: errmsg = "Keithley 2182: %s: %s" % (err[0],err[1]) log.error(errmsg + '\n') else: break def measure_voltage(self, max_voltage=1, ac=False): """ Configures the instrument to measure voltage, based on a maximum voltage to set the range, and a boolean flag to determine if DC or AC is required. :param max_voltage: A voltage in Volts to set the voltage range :param ac: False for DC voltage, and True for AC voltage """ if ac: self.mode = 'voltage ac' self.voltage_ac_range = max_voltage else: self.mode = 'voltage' self.voltage_range = max_voltage def measure_current(self, max_current=10e-3, ac=False): """ Configures the instrument to measure current, based on a maximum current to set the range, and a boolean flag to determine if DC or AC is required. :param max_current: A current in Volts to set the current range :param ac: False for DC current, and True for AC current """ if ac: self.mode = 'current ac' self.current_ac_range = max_current else: self.mode = 'current' self.current_range = max_current def auto_range(self, mode=None): """ Sets the active mode to use auto-range, or can set another mode by its name. :param mode: A valid :attr:`~.Keithley2182.mode` name, or None for the active mode """ self.write(":SENS:%s:RANG:AUTO 1" % self._mode_command(mode)) def enable_reference(self, mode=None): """ Enables the reference for the active mode, or can set another mode by its name. :param mode: A valid :attr:`~.Keithley2182.mode` name, or None for the active mode """ self.write(":SENS:%s:REF:STAT 1" % self._mode_command(mode)) def disable_reference(self, mode=None): """ Disables the reference for the active mode, or can set another mode by its name. :param mode: A valid :attr:`~.Keithley2182.mode` name, or None for the active mode """ self.write(":SENS:%s:REF:STAT 0" % self._mode_command(mode)) def acquire_reference(self, mode=None): """ Sets the active value as the reference for the active mode, or can set another mode by its name. :param mode: A valid :attr:`~.Keithley2182.mode` name, or None for the active mode """ self.write(":SENS:%s:REF:ACQ" % self._mode_command(mode)) def enable_filter(self, mode=None, type='repeat', count=1): """ Enables the averaging filter for the active mode, or can set another mode by its name. :param mode: A valid :attr:`~.Keithley2182.mode` name, or None for the active mode :param type: The type of averaging filter, either 'repeat' or 'moving'. :param count: A number of averages, which can take take values from 1 to 100 """ self.write(":SENS:%s:AVER:STAT 1") self.write(":SENS:%s:AVER:TCON %s") self.write(":SENS:%s:AVER:COUN %d") def disable_filter(self, mode=None): """ Disables the averaging filter for the active mode, or can set another mode by its name. :param mode: A valid :attr:`~.Keithley2182.mode` name, or None for the active mode """ self.write(":SENS:%s:AVER:STAT 0" % self._mode_command(mode)) def local(self): """ Returns control to the instrument panel, and enables the panel if disabled. """ self.write(":SYST:LOC") def remote(self): """ Places the instrument in the remote state, which is does not need to be explicity called in general. """ self.write(":SYST:REM") def remote_lock(self): """ Disables and locks the front panel controls to prevent changes during remote operations. This is disabled by calling :meth:`~.Keithley2182.local`. """ self.write(":SYST:RWL") def reset(self): """ Resets the instrument state. """ self.write(":STAT:QUEUE:CLEAR;*RST;:STAT:PRES;:*CLS;") def beep(self, frequency, duration): """ Sounds a system beep. :param frequency: A frequency in Hz between 65 Hz and 2 MHz :param duration: A time in seconds between 0 and 7.9 seconds """ self.write(":SYST:BEEP %g, %g" % (frequency, duration))
class Keithley2400(Instrument, KeithleyBuffer): """ Represents the Keithely 2400 SourceMeter and provides a high-level interface for interacting with the instrument. .. code-block:: python keithley = Keithley2400("GPIB::1") keithley.apply_current() # Sets up to source current keithley.source_current_range = 10e-3 # Sets the source current range to 10 mA keithley.compliance_voltage = 10 # Sets the compliance voltage to 10 V keithley.source_current = 0 # Sets the source current to 0 mA keithley.enable_source() # Enables the source output keithley.measure_voltage() # Sets up to measure voltage keithley.ramp_to_current(5e-3) # Ramps the current to 5 mA print(keithley.voltage) # Prints the voltage in Volts keithley.shutdown() # Ramps the current to 0 mA and disables output """ # TODO: Add measurement mode property source_mode = Instrument.control( ":SOUR:FUNC?", ":SOUR:FUNC %s", """ A string property that controls the source mode, which can take the values 'current' or 'voltage'. The convenience methods :meth:`~.Keithley2400.apply_current` and :meth:`~.Keithley2400.apply_voltage` can also be used. """, validator=strict_discrete_set, values={'current':'CURR', 'voltage':'VOLT'}, map_values=True ) source_enabled = Instrument.measurement("OUTPUT?", """ Reads a boolean value that is True if the source is enabled. """, cast=bool ) ############### # Current (A) # ############### current = Instrument.measurement(":READ?", """ Reads the current in Amps, if configured for this reading. """ ) current_range = Instrument.control( ":SENS:CURR:RANG?", ":SENS:CURR:RANG:AUTO 0;:SENS:CURR:RANG %g", """ A floating point property that controls the measurement current range in Amps, which can take values between -1.05 and +1.05 A. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[-1.05, 1.05] ) current_nplc = Instrument.control( ":SENS:CURR:NPLC?", ":SENS:CURR:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the DC current measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """ ) compliance_current = Instrument.control( ":SENS:CURR:PROT?", ":SENS:CURR:PROT %g", """ A floating point property that controls the compliance current in Amps. """, validator=truncated_range, values=[-1.05, 1.05] ) source_current = Instrument.control( ":SOUR:CURR?", ":SOUR:CURR:LEV %g", """ A floating point property that controls the source current in Amps. """ ) source_current_range = Instrument.control( ":SOUR:CURR:RANG?", ":SOUR:CURR:RANG:AUTO 0;:SOUR:CURR:RANG %g", """ A floating point property that controls the source current range in Amps, which can take values between -1.05 and +1.05 A. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[-1.05, 1.05] ) ############### # Voltage (V) # ############### voltage = Instrument.measurement(":READ?", """ Reads the voltage in Volts, if configured for this reading. """ ) voltage_range = Instrument.control( ":SENS:VOLT:RANG?", ":SENS:VOLT:RANG:AUTO 0;:SENS:VOLT:RANG %g", """ A floating point property that controls the measurement voltage range in Volts, which can take values from -210 to 210 V. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[-210, 210] ) voltage_nplc = Instrument.control( ":SENS:CURRVOLT:NPLC?", ":SENS:VOLT:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the DC voltage measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """ ) compliance_voltage = Instrument.control( ":SENS:VOLT:PROT?", ":SENS:VOLT:PROT %g", """ A floating point property that controls the compliance voltage in Volts. """, validator=truncated_range, values=[-210, 210] ) source_voltage = Instrument.control( ":SOUR:VOLT?", ":SOUR:VOLT:LEV %g", """ A floating point property that controls the source voltage in Volts. """ ) source_voltage_range = Instrument.control( ":SOUR:VOLT:RANG?", ":SOUR:VOLT:RANG:AUTO 0;:SOUR:VOLT:RANG %g", """ A floating point property that controls the source voltage range in Volts, which can take values from -210 to 210 V. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[-210, 210] ) #################### # Resistance (Ohm) # #################### resistance = Instrument.measurement(":READ?", """ Reads the resistance in Ohms, if configured for this reading. """ ) resistance_range = Instrument.control( ":SENS:RES:RANG?", ":SENS:RES:RANG:AUTO 0;:SENS:RES:RANG %g", """ A floating point property that controls the resistance range in Ohms, which can take values from 0 to 210 MOhms. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[0, 210e6] ) resistance_nplc = Instrument.control( ":SENS:RES:NPLC?", ":SENS:RES:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the 2-wire resistance measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """ ) wires = Instrument.control( ":SYSTEM:RSENSE?", ":SYSTEM:RSENSE %d", """ An integer property that controls the number of wires in use for resistance measurements, which can take the value of 2 or 4. """, validator=strict_discrete_set, values={4:1, 2:2}, map_values=True ) buffer_points = Instrument.control( ":TRAC:POIN?", ":TRAC:POIN %d", """ An integer property that controls the number of buffer points. This does not represent actual points in the buffer, but the configuration value instead. """, validator=truncated_range, values=[1, 2500], cast=int ) means = Instrument.measurement( ":CALC3:FORM MEAN;:CALC3:DATA?;", """ Reads the calculated means (averages) for voltage, current, and resistance from the buffer data as a list. """ ) maximums = Instrument.measurement( ":CALC3:FORM MAX;:CALC3:DATA?;", """ Returns the calculated maximums for voltage, current, and resistance from the buffer data as a list. """ ) minimums = Instrument.measurement( ":CALC3:FORM MIN;:CALC3:DATA?;", """ Returns the calculated minimums for voltage, current, and resistance from the buffer data as a list. """ ) standard_devs = Instrument.measurement( ":CALC3:FORM SDEV;:CALC3:DATA?;", """ Returns the calculated standard deviations for voltage, current, and resistance from the buffer data as a list. """ ) ########### # Trigger # ########### trigger_count = Instrument.control( ":TRIG:COUN?", ":TRIG:COUN %d", """ An integer property that controls the trigger count, which can take values from 1 to 9,999. """, validator=truncated_range, values=[1, 2500], cast=int ) trigger_delay = Instrument.control( ":TRIG:SEQ:DEL?", ":TRIG:SEQ:DEL %g", """ A floating point property that controls the trigger delay in seconds, which can take values from 0 to 999.9999 s. """, validator=truncated_range, values=[0, 999.9999] ) def __init__(self, adapter, **kwargs): super(Keithley2400, self).__init__( adapter, "Keithley 2400 SourceMeter", **kwargs ) def enable_source(self): """ Enables the source of current or voltage depending on the configuration of the instrument. """ self.write("OUTPUT ON") def disable_source(self): """ Disables the source of current or voltage depending on the configuration of the instrument. """ self.write("OUTPUT OFF") def measure_resistance(self, nplc=1, resistance=2.1e5, auto_range=True): """ Configures the measurement of resistance. :param nplc: Number of power line cycles (NPLC) from 0.01 to 10 :param resistance: Upper limit of resistance in Ohms, from -210 MOhms to 210 MOhms :param auto_range: Enables auto_range if True, else uses the set resistance """ log.info("%s is measuring resistance." % self.name) self.write(":SENS:FUNC RES;" ":SENS:RES:MODE MAN;" ":SENS:RES:NPLC %f;:FORM:ELEM RES;" % nplc) if auto_range: self.write(":SENS:RES:RANG:AUTO 1;") else: self.resistance_range = resistance self.check_errors() def measure_voltage(self, nplc=1, voltage=21.0, auto_range=True): """ Configures the measurement of voltage. :param nplc: Number of power line cycles (NPLC) from 0.01 to 10 :param voltage: Upper limit of voltage in Volts, from -210 V to 210 V :param auto_range: Enables auto_range if True, else uses the set voltage """ log.info("%s is measuring voltage." % self.name) self.write(":SENS:FUNC 'VOLT';" ":SENS:VOLT:NPLC %f;:FORM:ELEM VOLT;" % nplc) if auto_range: self.write(":SENS:VOLT:RANG:AUTO 1;") else: self.voltage_range = voltage self.check_errors() def measure_current(self, nplc=1, current=1.05e-4, auto_range=True): """ Configures the measurement of current. :param nplc: Number of power line cycles (NPLC) from 0.01 to 10 :param current: Upper limit of current in Amps, from -1.05 A to 1.05 A :param auto_range: Enables auto_range if True, else uses the set current """ log.info("%s is measuring current." % self.name) self.write(":SENS:FUNC 'CURR';" ":SENS:CURR:NPLC %f;:FORM:ELEM CURR;" % nplc) if auto_range: self.write(":SENS:CURR:RANG:AUTO 1;") else: self.current_range = current self.check_errors() def auto_range_source(self): """ Configures the source to use an automatic range. """ if self.source_mode == 'current': self.write(":SOUR:CURR:RANG:AUTO 1") else: self.write(":SOUR:VOLT:RANG:AUTO 1") def apply_current(self, current_range=None, compliance_voltage=0.1): """ Configures the instrument to apply a source current, and uses an auto range unless a current range is specified. The compliance voltage is also set. :param compliance_voltage: A float in the correct range for a :attr:`~.Keithley2400.compliance_voltage` :param current_range: A :attr:`~.Keithley2400.current_range` value or None """ log.info("%s is sourcing current." % self.name) self.source_mode = 'current' if current_range is None: self.auto_range_source() else: self.source_current_range = current_range self.compliance_voltage = compliance_voltage self.check_errors() def apply_voltage(self, voltage_range=None, compliance_current=0.1): """ Configures the instrument to apply a source voltage, and uses an auto range unless a voltage range is specified. The compliance current is also set. :param compliance_current: A float in the correct range for a :attr:`~.Keithley2400.compliance_current` :param voltage_range: A :attr:`~.Keithley2400.voltage_range` value or None """ log.info("%s is sourcing voltage." % self.name) self.source_mode = 'voltage' if voltage_range is None: self.auto_range_source() else: self.source_voltage_range = voltage_range self.compliance_current = compliance_current self.check_errors() def beep(self, frequency, duration): """ Sounds a system beep. :param frequency: A frequency in Hz between 65 Hz and 2 MHz :param duration: A time in seconds between 0 and 7.9 seconds """ self.write(":SYST:BEEP %g, %g" % (frequency, duration)) def triad(self, base_frequency, duration): """ Sounds a musical triad using the system beep. :param base_frequency: A frequency in Hz between 65 Hz and 1.3 MHz :param duration: A time in seconds between 0 and 7.9 seconds """ self.beep(base_frequency, duration) time.sleep(duration) self.beep(base_frequency*5.0/4.0, duration) time.sleep(duration) self.beep(base_frequency*6.0/4.0, duration) @property def error(self): """ Returns a tuple of an error code and message from a single error. """ err = self.values(":system:error?") if len(err) < 2: err = self.read() # Try reading again code = err[0] message = err[1].replace('"', '') return (code, message) def check_errors(self): """ Logs any system errors reported by the instrument. """ code, message = self.error while code != 0: t = time.time() log.info("Keithley 2400 reported error: %d, %s" % (code, message)) code, message = self.error if (time.time()-t)>10: log.warning("Timed out for Keithley 2400 error retrieval.") def reset(self): """ Resets the instrument and clears the queue. """ self.write("status:queue:clear;*RST;:stat:pres;:*CLS;") def ramp_to_current(self, target_current, steps=30, pause=20e-3): """ Ramps to a target current from the set current value over a certain number of linear steps, each separated by a pause duration. :param target_current: A current in Amps :param steps: An integer number of steps :param pause: A pause duration in seconds to wait between steps """ currents = np.linspace( self.source_current, target_current, steps ) for current in currents: self.source_current = current time.sleep(pause) def ramp_to_voltage(self, target_voltage, steps=30, pause=20e-3): """ Ramps to a target voltage from the set voltage value over a certain number of linear steps, each separated by a pause duration. :param target_voltage: A voltage in Amps :param steps: An integer number of steps :param pause: A pause duration in seconds to wait between steps """ voltages = np.linspace( self.source_voltage, target_voltage, steps ) for voltage in voltages: self.source_voltage = voltage time.sleep(pause) def trigger(self): """ Executes a bus trigger, which can be used when :meth:`~.trigger_on_bus` is configured. """ return self.write("*TRG") def trigger_immediately(self): """ Configures measurements to be taken with the internal trigger at the maximum sampling rate. """ self.write(":ARM:SOUR IMM;:TRIG:SOUR IMM;") def trigger_on_bus(self): """ Configures the trigger to detect events based on the bus trigger, which can be activated by :code:`GET` or :code:`*TRG`. """ self.write(":ARM:COUN 1;:ARM:SOUR BUS;:TRIG:SOUR BUS;") def set_trigger_counts(self, arm, trigger): """ Sets the number of counts for both the sweeps (arm) and the points in those sweeps (trigger), where the total number of points can not exceed 2500 """ if arm * trigger > 2500 or arm * trigger < 0: raise RangeException("Keithley 2400 has a combined maximum " "of 2500 counts") if arm < trigger: self.write(":ARM:COUN %d;:TRIG:COUN %d" % (arm, trigger)) else: self.write(":TRIG:COUN %d;:ARM:COUN %d" % (trigger, arm)) def sample_continuously(self): """ Causes the instrument to continuously read samples and turns off any buffer or output triggering """ self.disable_buffer() self.disable_output_trigger() self.trigger_immediately() def set_timed_arm(self, interval): """ Sets up the measurement to be taken with the internal trigger at a variable sampling rate defined by the interval in seconds between sampling points """ if interval > 99999.99 or interval < 0.001: raise RangeException("Keithley 2400 can only be time" " triggered between 1 mS and 1 Ms") self.write(":ARM:SOUR TIM;:ARM:TIM %.3f" % interval) def trigger_on_external(self, line=1): """ Configures the measurement trigger to be taken from a specific line of an external trigger :param line: A trigger line from 1 to 4 """ cmd = ":ARM:SOUR TLIN;:TRIG:SOUR TLIN;" cmd += ":ARM:ILIN %d;:TRIG:ILIN %d;" % (line, line) self.write(cmd) def output_trigger_on_external(self, line=1, after='DEL'): """ Configures the output trigger on the specified trigger link line number, with the option of supplying the part of the measurement after which the trigger should be generated (default to delay, which is right before the measurement) :param line: A trigger line from 1 to 4 :param after: An event string that determines when to trigger """ self.write(":TRIG:OUTP %s;:TRIG:OLIN %d;" % (after, line)) def disable_output_trigger(self): """ Disables the output trigger for the Trigger layer """ self.write(":TRIG:OUTP NONE") @property def mean_voltage(self): """ Returns the mean voltage from the buffer """ return self.means[0] @property def max_voltage(self): """ Returns the maximum voltage from the buffer """ return self.maximums[0] @property def min_voltage(self): """ Returns the minimum voltage from the buffer """ return self.minimums[0] @property def std_voltage(self): """ Returns the voltage standard deviation from the buffer """ return self.standard_devs[0] @property def mean_current(self): """ Returns the mean current from the buffer """ return self.means[1] @property def max_current(self): """ Returns the maximum current from the buffer """ return self.maximums[1] @property def min_current(self): """ Returns the minimum current from the buffer """ return self.minimums[1] @property def std_current(self): """ Returns the current standard deviation from the buffer """ return self.standard_devs[1] @property def mean_resistance(self): """ Returns the mean resistance from the buffer """ return self.means[2] @property def max_resistance(self): """ Returns the maximum resistance from the buffer """ return self.maximums()[2] @property def min_resistance(self): """ Returns the minimum resistance from the buffer """ return self.minimums[2] @property def std_resistance(self): """ Returns the resistance standard deviation from the buffer """ return self.standard_devs[2] def status(self): return self.ask("status:queue?;") def RvsI(self, startI, stopI, stepI, compliance, delay=10.0e-3, backward=False): num = int(float(stopI-startI)/float(stepI)) + 1 currRange = 1.2*max(abs(stopI),abs(startI)) # self.write(":SOUR:CURR 0.0") self.write(":SENS:VOLT:PROT %g" % compliance) self.write(":SOUR:DEL %g" % delay) self.write(":SOUR:CURR:RANG %g" % currRange ) self.write(":SOUR:SWE:RANG FIX") self.write(":SOUR:CURR:MODE SWE") self.write(":SOUR:SWE:SPAC LIN") self.write(":SOUR:CURR:STAR %g" % startI) self.write(":SOUR:CURR:STOP %g" % stopI) self.write(":SOUR:CURR:STEP %g" % stepI) self.write(":TRIG:COUN %d" % num) if backward: currents = np.linspace(stopI, startI, num) self.write(":SOUR:SWE:DIR DOWN") else: currents = np.linspace(startI, stopI, num) self.write(":SOUR:SWE:DIR UP") self.connection.timeout = 30.0 self.enable_source() data = self.values(":READ?") self.check_errors() return zip(currents,data) def RvsIaboutZero(self, minI, maxI, stepI, compliance, delay=10.0e-3): data = [] data.extend(self.RvsI(minI, maxI, stepI, compliance=compliance, delay=delay)) data.extend(self.RvsI(minI, maxI, stepI, compliance=compliance, delay=delay, backward=True)) self.disable_source() data.extend(self.RvsI(-minI, -maxI, -stepI, compliance=compliance, delay=delay)) data.extend(self.RvsI(-minI, -maxI, -stepI, compliance=compliance, delay=delay, backward=True)) self.disable_source() return data def use_rear_terminals(self): """ Enables the rear terminals for measurement, and disables the front terminals. """ self.write(":ROUT:TERM REAR") def use_front_terminals(self): """ Enables the front terminals for measurement, and disables the rear terminals. """ self.write(":ROUT:TERM FRON") def shutdown(self): """ Ensures that the current or voltage is turned to zero and disables the output. """ log.info("Shutting down %s." % self.name) if self.source_mode == 'current': self.ramp_to_current(0.0) else: self.ramp_to_voltage(0.0) self.stop_buffer() self.disable_source()
class Channel(object): def __init__(self, instrument, channel): self.instrument = instrument self.channel = channel def ask(self, cmd): return float( self.instrument.ask('print(smu%s.%s)' % (self.channel, cmd))) def write(self, cmd): self.instrument.write('smu%s.%s' % (self.channel, cmd)) def values(self, cmd, **kwargs): """ Reads a set of values from the instrument through the adapter, passing on any key-word arguments. """ return self.instrument.values('print(smu%s.%s)' % (self.channel, cmd)) def binary_values(self, cmd, header_bytes=0, dtype=np.float32): return self.instrument.binary_values( 'print(smu%s.%s)' % ( self.channel, cmd, ), header_bytes, dtype) def check_errors(self): return self.instrument.check_errors() source_output = Instrument.control( 'source.output', 'source.output=%d', """Property controlling the channel output state (ON of OFF) """, validator=strict_discrete_set, values={ 'OFF': 0, 'ON': 1 }, map_values=True) source_mode = Instrument.control( 'source.func', 'source.func=%d', """Property controlling the channel soource function (Voltage or Current) """, validator=strict_discrete_set, values={ 'voltage': 1, 'current': 0 }, map_values=True) measure_nplc = Instrument.control( 'measure.nplc', 'measure.nplc=%f', """ Property controlling the nplc value """, validator=truncated_range, values=[0.001, 25], map_values=True) ############### # Current (A) # ############### current = Instrument.measurement('measure.i()', """ Reads the current in Amps """) source_current = Instrument.control( 'source.leveli', 'source.leveli=%f', """ Property controlling the applied source current """, validator=truncated_range, values=[-1.5, 1.5]) compliance_current = Instrument.control( 'source.limiti', 'source.limiti=%f', """ Property controlling the source compliance current """, validator=truncated_range, values=[-1.5, 1.5]) source_current_range = Instrument.control( 'source.rangei', 'source.rangei=%f', """Property controlling the source current range """, validator=truncated_range, values=[-1.5, 1.5]) current_range = Instrument.control( 'measure.rangei', 'measure.rangei=%f', """Property controlling the measurement current range """, validator=truncated_range, values=[-1.5, 1.5]) ############### # Voltage (V) # ############### voltage = Instrument.measurement('measure.v()', """ Reads the voltage in Volts """) source_voltage = Instrument.control( 'source.levelv', 'source.levelv=%f', """ Property controlling the applied source voltage """, validator=truncated_range, values=[-200, 200]) compliance_voltage = Instrument.control( 'source.limitv', 'source.limiv=%f', """ Property controlling the source compliance voltage """, validator=truncated_range, values=[-200, 200]) source_voltage_range = Instrument.control( 'source.rangev', 'source.rangev=%f', """Property controlling the source current range """, validator=truncated_range, values=[-200, 200]) voltage_range = Instrument.control( 'measure.rangev', 'measure.rangev=%f', """Property controlling the measurement voltage range """, validator=truncated_range, values=[-200, 200]) #################### # Resistance (Ohm) # #################### resistance = Instrument.measurement('measure.r()', """ Reads the resistance in Ohms """) wires_mode = Instrument.control( 'sense', 'sense=%d', """Property controlling the resistance measurement mode: 4 wires or 2 wires""", validator=strict_discrete_set, values={ '4': 1, '2': 0 }, map_values=True) ####################### # Measurement Methods # ####################### def measure_voltage(self, nplc=1, voltage=21.0, auto_range=True): """ Configures the measurement of voltage. :param nplc: Number of power line cycles (NPLC) from 0.001 to 25 :param voltage: Upper limit of voltage in Volts, from -200 V to 200 V :param auto_range: Enables auto_range if True, else uses the set voltage """ log.info("%s is measuring voltage." % self.channel) self.write('measure.v()') self.write('measure.nplc=%f' % nplc) if auto_range: self.write('measure.autorangev=1') else: self.voltage_range = voltage self.check_errors() def measure_current(self, nplc=1, current=1.05e-4, auto_range=True): """ Configures the measurement of current. :param nplc: Number of power line cycles (NPLC) from 0.001 to 25 :param current: Upper limit of current in Amps, from -1.5 A to 1.5 A :param auto_range: Enables auto_range if True, else uses the set current """ log.info("%s is measuring current." % self.channel) self.write('measure.i()') self.write('measure.nplc=%f' % nplc) if auto_range: self.write('measure.autorangei=1') else: self.current_range = current self.check_errors() def auto_range_source(self): """ Configures the source to use an automatic range. """ if self.source_mode == 'current': self.write('source.autorangei=1') else: self.write('source.autorangev=1') def apply_current(self, current_range=None, compliance_voltage=0.1): """ Configures the instrument to apply a source current, and uses an auto range unless a current range is specified. The compliance voltage is also set. :param compliance_voltage: A float in the correct range for a :attr:`~.Keithley2600.compliance_voltage` :param current_range: A :attr:`~.Keithley2600.current_range` value or None """ log.info("%s is sourcing current." % self.channel) self.source_mode = 'current' if current_range is None: self.auto_range_source() else: self.source_current_range = current_range self.compliance_voltage = compliance_voltage self.check_errors() def apply_voltage(self, voltage_range=None, compliance_current=0.1): """ Configures the instrument to apply a source voltage, and uses an auto range unless a voltage range is specified. The compliance current is also set. :param compliance_current: A float in the correct range for a :attr:`~.Keithley2600.compliance_current` :param voltage_range: A :attr:`~.Keithley2600.voltage_range` value or None """ log.info("%s is sourcing voltage." % self.channel) self.source_mode = 'voltage' if voltage_range is None: self.auto_range_source() else: self.source_voltage_range = voltage_range self.compliance_current = compliance_current self.check_errors() def ramp_to_voltage(self, target_voltage, steps=30, pause=0.1): """ Ramps to a target voltage from the set voltage value over a certain number of linear steps, each separated by a pause duration. :param target_voltage: A voltage in Amps :param steps: An integer number of steps :param pause: A pause duration in seconds to wait between steps """ voltages = np.linspace(self.source_voltage, target_voltage, steps) for voltage in voltages: self.source_voltage = voltage time.sleep(pause) def ramp_to_current(self, target_current, steps=30, pause=0.1): """ Ramps to a target current from the set current value over a certain number of linear steps, each separated by a pause duration. :param target_current: A current in Amps :param steps: An integer number of steps :param pause: A pause duration in seconds to wait between steps """ currents = np.linspace(self.source_current, target_current, steps) for current in currents: self.source_current = current time.sleep(pause) def shutdown(self): """ Ensures that the current or voltage is turned to zero and disables the output. """ log.info("Shutting down channel %s." % self.channel) if self.source_mode == 'current': self.ramp_to_current(0.0) else: self.ramp_to_voltage(0.0) self.source_output = 'OFF'
class AgilentE4980(Instrument): """Represents LCR meter E4980A/AL""" ac_voltage = Instrument.control(":VOLT:LEV?", ":VOLT:LEV %g", "AC voltage level, in Volts", validator=strict_range, values=[0, 20]) ac_current = Instrument.control(":CURR:LEV?", ":CURR:LEV %g", "AC current level, in Amps", validator=strict_range, values=[0, 0.1]) frequency = Instrument.control( ":FREQ:CW?", ":FREQ:CW %g", "AC frequency (range depending on model), in Hertz", validator=strict_range, values=[20, 2e6]) # FETCH? returns [A,B,state]: impedance returns only A,B impedance = Instrument.measurement( ":FETCH?", "Measured data A and B, according to :attr:`~.AgilentE4980.mode`", get_process=lambda x: x[:2]) mode = Instrument.control("FUNCtion:IMPedance:TYPE?", "FUNCtion:IMPedance:TYPE %s", """ Select quantities to be measured: * CPD: Parallel capacitance [F] and dissipation factor [number] * CPQ: Parallel capacitance [F] and quality factor [number] * CPG: Parallel capacitance [F] and parallel conductance [S] * CPRP: Parallel capacitance [F] and parallel resistance [Ohm] * CSD: Series capacitance [F] and dissipation factor [number] * CSQ: Series capacitance [F] and quality factor [number] * CSRS: Series capacitance [F] and series resistance [Ohm] * LPD: Parallel inductance [H] and dissipation factor [number] * LPQ: Parallel inductance [H] and quality factor [number] * LPG: Parallel inductance [H] and parallel conductance [S] * LPRP: Parallel inductance [H] and parallel resistance [Ohm] * LSD: Series inductance [H] and dissipation factor [number] * LSQ: Seriesinductance [H] and quality factor [number] * LSRS: Series inductance [H] and series resistance [Ohm] * RX: Resitance [Ohm] and reactance [Ohm] * ZTD: Impedance, magnitude [Ohm] and phase [deg] * ZTR: Impedance, magnitude [Ohm] and phase [rad] * GB: Conductance [S] and susceptance [S] * YTD: Admittance, magnitude [Ohm] and phase [deg] * YTR: Admittance magnitude [Ohm] and phase [rad] """, validator=strict_discrete_set, values=[ "CPD", "CPQ", "CPG", "CPRP", "CSD", "CSQ", "CSRS", "LPD", "LPQ", "LPG", "LPRP", "LSD", "LSQ", "LSRS", "RX", "ZTD", "ZTR", "GB", "YTD", "YTR", ]) trigger_source = Instrument.control("TRIG:SOUR?", "TRIG:SOUR %s", """ Select trigger source; accept the values: * HOLD: manual * INT: internal * BUS: external bus (GPIB/LAN/USB) * EXT: external connector""", validator=strict_discrete_set, values=["HOLD", "INT", "BUS", "EXT"]) def __init__(self, adapter, **kwargs): super(AgilentE4980, self).__init__(adapter, "Agilent E4980A/AL LCR meter", **kwargs) self.timeout = 30000 # format: output ascii self.write("FORM ASC") def freq_sweep(self, freq_list, return_freq=False): """ Run frequency list sweep using sequential trigger. :param freq_list: list of frequencies :param return_freq: if True, returns the frequencies read from the instrument Returns values as configured with :attr:`~.AgilentE4980.mode` """ # manual, page 299 # self.write("*RST;*CLS") self.write("TRIG:SOUR BUS") self.write("DISP:PAGE LIST") self.write("FORM ASC") # trigger in sequential mode self.write("LIST:MODE SEQ") lista_str = ",".join(['%e' % f for f in freq_list]) self.write("LIST:FREQ %s" % lista_str) # trigger self.write("INIT:CONT ON") self.write(":TRIG:IMM") # wait for completed measurement # using the Error signal (there should be a better way) while 1: try: measured = self.values(":FETCh:IMPedance:FORMatted?") break except VisaIOError: pass # at the end return to manual trigger self.write(":TRIG:SOUR HOLD") # gets 4-ples of numbers, first two are data A and B a_data = [measured[_] for _ in range(0, 4 * len(freq_list), 4)] b_data = [measured[_] for _ in range(1, 4 * len(freq_list), 4)] if return_freq: read_freqs = self.values("LIST:FREQ?") return a_data, b_data, read_freqs else: return a_data, b_data # TODO: maybe refactor as property? def aperture(self, time=None, averages=1): """ Set and get aperture. :param time: integration time as string: SHORT, MED, LONG (case insensitive); if None, get values :param averages: number of averages, numeric """ if time is None: read_values = self.ask(":APER?").split(',') return read_values[0], int(read_values[1]) else: if time.upper() in ["SHORT", "MED", "LONG"]: self.write(":APER {0}, {1}".format(time, averages)) else: raise Exception("Time must be a string: SHORT, MED, LONG")
class ITC503(Instrument): """Represents the Oxford Intelligent Temperature Controller 503. .. code-block:: python itc = ITC503("GPIB::24") # Default channel for the ITC503 itc.control_mode = "RU" # Set the control mode to remote itc.heater_gas_mode = "AUTO" # Turn on auto heater and flow itc.auto_pid = True # Turn on auto-pid print(itc.temperature_setpoint) # Print the current set-point itc.temperature_setpoint = 300 # Change the set-point to 300 K itc.wait_for_temperature() # Wait for the temperature to stabilize print(itc.temperature_1) # Print the temperature at sensor 1 """ def __init__(self, adapter, name="Oxford ITC503", clear_buffer=True, min_temperature=0, max_temperature=1677.7, **kwargs): if isinstance(adapter, (int, str)): kwargs.setdefault('read_termination', '\r') kwargs.setdefault('send_end', True) adapter = OxfordInstrumentsAdapter( adapter, asrl={ 'baud_rate': 9600, 'data_bits': 8, 'parity': 0, 'stop_bits': 20, }, preprocess_reply=lambda v: v[1:], **kwargs, ) super().__init__( adapter=adapter, name=name, includeSCPI=False, ) # Clear the buffer in order to prevent communication problems if clear_buffer: self.adapter.connection.clear() self.temperature_setpoint_values = [min_temperature, max_temperature] class FLOW_CONTROL_STATUS(IntFlag): """ IntFlag class for decoding the flow control status. Contains the following flags: === ====================== ============================================== bit flag meaning === ====================== ============================================== 4 HEATER_ERROR_SIGN Sign of heater-error; True means negative 3 TEMPERATURE_ERROR_SIGN Sign of temperature-error; True means negative 2 SLOW_VALVE_ACTION Slow valve action occurring 1 COOLDOWN_TERMINATION Cooldown-termination occurring 0 FAST_COOLDOWN Fast-cooldown occurring === ====================== ============================================== """ HEATER_ERROR_SIGN = 16 TEMPERATURE_ERROR_SIGN = 8 SLOW_VALVE_ACTION = 4 COOLDOWN_TERMINATION = 2 FAST_COOLDOWN = 1 version = Instrument.measurement( "V", """ A string property that returns the version of the IPS. """, preprocess_reply=lambda v: v, ) control_mode = Instrument.control( "X", "C%d", """ A string property that sets the ITC in `local` or `remote` and `locked` or `unlocked`, locking the LOC/REM button. Allowed values are: ===== ================= value state ===== ================= LL local & locked RL remote & locked LU local & unlocked RU remote & unlocked ===== ================= """, preprocess_reply=lambda v: v[5:6], cast=int, validator=strict_discrete_set, values={ "LL": 0, "RL": 1, "LU": 2, "RU": 3 }, map_values=True, ) heater_gas_mode = Instrument.control( "X", "A%d", """ A string property that sets the heater and gas flow control to `auto` or `manual`. Allowed values are: ====== ======================= value state ====== ======================= MANUAL heater & gas manual AM heater auto, gas manual MA heater manual, gas auto AUTO heater & gas auto ====== ======================= """, preprocess_reply=lambda v: v[3:4], cast=int, validator=strict_discrete_set, values={ "MANUAL": 0, "AM": 1, "MA": 2, "AUTO": 3 }, map_values=True, ) heater = Instrument.control( "R5", "O%f", """ A floating point property that represents the heater output power as a percentage of the maximum voltage. Can be set if the heater is in manual mode. Valid values are in range 0 [off] to 99.9 [%]. """, validator=truncated_range, values=[0, 99.9]) heater_voltage = Instrument.measurement( "R6", """ A floating point property that represents the heater output power in volts. For controlling the heater, use the :class:`ITC503.heater` property. """, ) gasflow = Instrument.control( "R7", "G%f", """ A floating point property that controls gas flow when in manual mode. The value is expressed as a percentage of the maximum gas flow. Valid values are in range 0 [off] to 99.9 [%]. """, validator=truncated_range, values=[0, 99.9]) proportional_band = Instrument.control( "R8", "P%f", """ A floating point property that controls the proportional band for the PID controller in Kelvin. Can be set if the PID controller is in manual mode. Valid values are 0 [K] to 1677.7 [K]. """, validator=truncated_range, values=[0, 1677.7]) integral_action_time = Instrument.control( "R9", "I%f", """ A floating point property that controls the integral action time for the PID controller in minutes. Can be set if the PID controller is in manual mode. Valid values are 0 [min.] to 140 [min.]. """, validator=truncated_range, values=[0, 140]) derivative_action_time = Instrument.control( "R10", "D%f", """ A floating point property that controls the derivative action time for the PID controller in minutes. Can be set if the PID controller is in manual mode. Valid values are 0 [min.] to 273 [min.]. """, validator=truncated_range, values=[0, 273]) auto_pid = Instrument.control( "X", "L%d", """ A boolean property that sets the Auto-PID mode on (True) or off (False). """, preprocess_reply=lambda v: v[12:13], cast=int, validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True, ) sweep_status = Instrument.control( "X", "S%d", """ An integer property that sets the sweep status. Values are: ========= ========================================= value meaning ========= ========================================= 0 Sweep not running 1 Start sweep / sweeping to first set-point 2P - 1 Sweeping to set-point P 2P Holding at set-point P ========= ========================================= """, preprocess_reply=lambda v: v[7:9], cast=int, validator=strict_range, values=[0, 32]) temperature_setpoint = Instrument.control( "R0", "T%f", """ A floating point property that controls the temperature set-point of the ITC in kelvin. """, validator=truncated_range, values=[ 0, 1677.7 ], # Kelvin, 0 - 1677.7K is the maximum range of the instrument dynamic=True, ) temperature_1 = Instrument.measurement( "R1", """ Reads the temperature of the sensor 1 in Kelvin. """, ) temperature_2 = Instrument.measurement( "R2", """ Reads the temperature of the sensor 2 in Kelvin. """, ) temperature_3 = Instrument.measurement( "R3", """ Reads the temperature of the sensor 3 in Kelvin. """, ) temperature_error = Instrument.measurement( "R4", """ Reads the difference between the set-point and the measured temperature in Kelvin. Positive when set-point is larger than measured. """, ) front_panel_display = Instrument.setting( "F%d", """ A string property that controls what value is displayed on the front panel of the ITC. Valid values are: 'temperature setpoint', 'temperature 1', 'temperature 2', 'temperature 3', 'temperature error', 'heater', 'heater voltage', 'gasflow', 'proportional band', 'integral action time', 'derivative action time', 'channel 1 freq/4', 'channel 2 freq/4', 'channel 3 freq/4'. """, validator=strict_discrete_set, map=True, values={ "temperature setpoint": 0, "temperature 1": 1, "temperature 2": 2, "temperature 3": 3, "temperature error": 4, "heater": 5, "heater voltage": 6, "gasflow": 7, "proportional band": 8, "integral action time": 9, "derivative action time": 10, "channel 1 freq/4": 11, "channel 2 freq/4": 12, "channel 3 freq/4": 13, }, ) x_pointer = Instrument.setting( "x%d", """ An integer property to set pointers into tables for loading and examining values in the table. The significance and valid values for the pointer depends on what property is to be read or set. """, validator=strict_range, values=[0, 128]) y_pointer = Instrument.setting( "y%d", """ An integer property to set pointers into tables for loading and examining values in the table. The significance and valid values for the pointer depends on what property is to be read or set. """, validator=strict_range, values=[0, 128]) pointer = Instrument.setting( "$x%d\r$y%d", """ A tuple property to set pointers into tables for loading and examining values in the table, of format (x, y). The significance and valid values for the pointer depends on what property is to be read or set. The value for x and y can be in the range 0 to 128. """, validator=pointer_validator, values=[0, 128]) sweep_table = Instrument.control( "r", "s%f", """ A property that controls values in the sweep table. Relies on :class:`ITC503.x_pointer` and :class:`ITC503.y_pointer` (or :class:`ITC503.pointer`) to point at the location in the table that is to be set or read. The x-pointer selects the step of the sweep (1 to 16); the y-pointer selects the parameter: ========= ======================= y-pointer parameter ========= ======================= 1 set-point temperature 2 sweep-time to set-point 3 hold-time at set-point ========= ======================= """, ) auto_pid_table = Instrument.control( "q", "p%f", """ A property that controls values in the auto-pid table. Relies on :class:`ITC503.x_pointer` and :class:`ITC503.y_pointer` (or :class:`ITC503.pointer`) to point at the location in the table that is to be set or read. The x-pointer selects the table entry (1 to 16); the y-pointer selects the parameter: ========= ======================= y-pointer parameter ========= ======================= 1 upper temperature limit 2 proportional band 3 integral action time 4 derivative action time ========= ======================= """, ) target_voltage_table = Instrument.control( "t", "v%f", """ A property that controls values in the target heater voltage table. Relies on the :class:`ITC503.x_pointer` to select the entry in the table that is to be set or read (1 to 64). """, ) gasflow_configuration_parameter = Instrument.control( "d", "c%f", """ A property that controls the gas flow configuration parameters. Relies on the :class:`ITC503.x_pointer` to select which parameter is set or read: ========= ===================================== x-pointer parameter ========= ===================================== 1 valve gearing 2 target table & features configuration 3 gas flow scaling 4 temperature error sensitivity 5 heater voltage error sensitivity 6 minimum gas valve in auto ========= ===================================== """, ) gasflow_control_status = Instrument.measurement( "m", """ A property that reads the gas-flow control status. Returns the status in the form of a :class:`ITC503.FLOW_CONTROL_STATUS` IntFlag. """, cast=int, get_process=lambda v: ITC503.FLOW_CONTROL_STATUS(v), ) target_voltage = Instrument.measurement( "n", """ A float property that reads the current heater target voltage with which the actual heater voltage is being compared. Only valid if gas-flow in auto mode. """, ) valve_scaling = Instrument.measurement( "o", """ A float property that reads the valve scaling parameter. Only valid if gas-flow in auto mode. """, ) def wait_for_temperature( self, error=0.01, timeout=3600, check_interval=0.5, stability_interval=10, thermalize_interval=300, should_stop=lambda: False, ): """ Wait for the ITC to reach the set-point temperature. :param error: The maximum error in Kelvin under which the temperature is considered at set-point :param timeout: The maximum time the waiting is allowed to take. If timeout is exceeded, a TimeoutError is raised. If timeout is None, no timeout will be used. :param check_interval: The time between temperature queries to the ITC. :param stability_interval: The time over which the temperature_error is to be below error to be considered stable. :param thermalize_interval: The time to wait after stabilizing for the system to thermalize. :param should_stop: Optional function (returning a bool) to allow the waiting to be stopped before its end. """ number_of_intervals = int(stability_interval / check_interval) stable_intervals = 0 attempt = 0 t0 = time() while True: temp_error = self.temperature_error if abs(temp_error) < error: stable_intervals += 1 else: stable_intervals = 0 attempt += 1 if stable_intervals >= number_of_intervals: break if timeout is not None and (time() - t0) > timeout: raise TimeoutError( "Timeout expired while waiting for the Oxford ITC305 to " "reach the set-point temperature") if should_stop(): return sleep(check_interval) if attempt == 0: return t1 = time() + thermalize_interval while time() < t1: sleep(check_interval) if should_stop(): return return def program_sweep(self, temperatures, sweep_time, hold_time, steps=None): """ Program a temperature sweep in the controller. Stops any running sweep. After programming the sweep, it can be started using OxfordITC503.sweep_status = 1. :param temperatures: An array containing the temperatures for the sweep :param sweep_time: The time (or an array of times) to sweep to a set-point in minutes (between 0 and 1339.9). :param hold_time: The time (or an array of times) to hold at a set-point in minutes (between 0 and 1339.9). :param steps: The number of steps in the sweep, if given, the temperatures, sweep_time and hold_time will be interpolated into (approximately) equal segments """ # Check if in remote control if not self.control_mode.startswith("R"): raise AttributeError("Oxford ITC503 not in remote control mode") # Stop sweep if running to be able to write the program self.sweep_status = 0 # Convert input np.ndarrays temperatures = numpy.array(temperatures, ndmin=1) sweep_time = numpy.array(sweep_time, ndmin=1) hold_time = numpy.array(hold_time, ndmin=1) # Make steps array if steps is None: steps = temperatures.size steps = numpy.linspace(1, steps, steps) # Create interpolated arrays interpolator = numpy.round( numpy.linspace(1, steps.size, temperatures.size)) temperatures = numpy.interp(steps, interpolator, temperatures) interpolator = numpy.round( numpy.linspace(1, steps.size, sweep_time.size)) sweep_time = numpy.interp(steps, interpolator, sweep_time) interpolator = numpy.round( numpy.linspace(1, steps.size, hold_time.size)) hold_time = numpy.interp(steps, interpolator, hold_time) # Pad with zeros to wipe unused steps (total 16) of the sweep program padding = 16 - temperatures.size temperatures = numpy.pad(temperatures, (0, padding), 'constant', constant_values=temperatures[-1]) sweep_time = numpy.pad(sweep_time, (0, padding), 'constant') hold_time = numpy.pad(hold_time, (0, padding), 'constant') # Setting the arrays to the controller for line, (setpoint, sweep, hold) in \ enumerate(zip(temperatures, sweep_time, hold_time), 1): self.pointer = (line, 1) self.sweep_table = setpoint self.pointer = (line, 2) self.sweep_table = sweep self.pointer = (line, 3) self.sweep_table = hold def wipe_sweep_table(self): """ Wipe the currently programmed sweep table. """ self.write("w")
class WaveRunner(Instrument): """ Represents the WaveRunner 606Zi Oscilloscope and provides a high-level for interacting with the instrument """ def __init__(self, resourceName, **kwargs): super(WaveRunner, self).__init__(resourceName, name="LeCroy WaveRunner series Oscilloscope", **kwargs) self.measurement = WaveRunner.Measurement(self) def disconnect(self): return super(WaveRunner, self).disconnect() def recall_setup_from_file(self, filename): """ Command to setup osci from file based on filename cmd = "RCPN DISK, HDD, FILE," + filename. """ return self.write("%s" % ("RCPN DISK, HDD, FILE," + filename)) # TODO: must be new implemented self.port.tty.StoreHardcopyToFile('TIFF', '', filename) # WaveRunner.print_screen_tif= Instrument.control() id = Instrument.id query_options = Instrument.measurement("*OPT?", "" "Query the scope options. " "") timespan = Instrument.control( "TIME_DIV?", "TIME_DIV %g", """ A floating point property that controls the timespan of the horizontal axis This property can be set. """) def get_measurement_Px(self, num): """Use special access to Lecroy with VBS get the value of the ma""" value = self.adapter.ask( "VBS? 'return=app.Measure.P%d.Out.Result.Value' " % num) return value def print_screen_autoname(self): """this fucntion print the screen to HDD in Oscillo I met some problem with StoreHardcopyToFile due to oscillo crash""" #self.adapter.connection.StoreHardcopyToFile("TIFF", "", "D:\\HardCopy\\TIFFImage.tif") self.adapter.write("VBS? 'app.Hardcopy.Print' ") class Measurement(object): SOURCE_VALUES = ['C1', 'C2', 'C3', 'C4', 'F1', 'F2', 'F3', 'F4'] def __init__(self, parent): self.parent = parent def amplitude(self, source): """Get signal amplitude""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? AMPL')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def area(self, source): """Get signal integral""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? area')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def cyclesnumber(self, source): """Get the number of signal cycle""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? CYCL')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def falltime90to10(self, source): """Get fall time from 90% to 10% """ if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? FALL')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def frequency(self, source): """Get signal frequency""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? FREQ')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def max(self, source): """Get signal maximimum""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? MAX')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def mean(self, source): """Get signal mean""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? MEAN')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def min(self, source): if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? MIN')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def peak2peak(self, source): """Get signal peak to peak value""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? PKPK')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def period(self, source): """Get signal period""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? PER')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def risetime10to90(self, source): """Get rise time from 10% to 90% """ if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? RISE')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def rms(self, source): """Get signal RMS value""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? RMS')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def std_dev(self, source): """Get signal stansard deviation""" if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? SDEV')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def top(self, source): if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? TOP')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def width(self, source): if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? WID')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def width_neg(self, source): if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? widthn')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1]) def duty(self, source): if source in WaveRunner.Measurement.SOURCE_VALUES: value = self.parent.ask("%s" % (source + ':PAVA? DUTY')) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, source)) return float(value.split(',')[1])
class Agilent33500(Instrument): """Represents the Agilent 33500 Function/Arbitrary Waveform Generator family. Individual devices are represented by subclasses. .. code-block:: python generator = Agilent33500("GPIB::1") generator.shape = 'SIN' # Sets the output signal shape to sine generator.frequency = 1e3 # Sets the output frequency to 1 kHz generator.amplitude = 5 # Sets the output amplitude to 5 Vpp generator.output = 'on' # Enables the output generator.shape = 'ARB' # Set shape to arbitrary generator.arb_srate = 1e6 # Set sample rate to 1MSa/s generator.data_volatile_clear() # Clear volatile internal memory generator.data_arb( # Send data points of arbitrary waveform 'test', range(-10000, 10000, +20), # In this case a simple ramp data_format='DAC' # Data format is set to 'DAC' ) generator.arb_file = 'test' # Select the transmitted waveform 'test' """ id = Instrument.measurement("*IDN?", """ Reads the instrument identification """) def __init__(self, adapter, **kwargs): super().__init__( adapter, "Agilent 33500 Function/Arbitrary Waveform generator family", **kwargs) def beep(self): """ Causes a system beep. """ self.write("SYST:BEEP") shape = Instrument.control( "FUNC?", "FUNC %s", """ A string property that controls the output waveform. Can be set to: SIN<USOID>, SQU<ARE>, TRI<ANGLE>, RAMP, PULS<E>, PRBS, NOIS<E>, ARB, DC. """, validator=strict_discrete_set, values=[ "SIN", "SQU", "TRI", "RAMP", "PULS", "PRBS", "NOIS", "ARB", "DC" ], ) frequency = Instrument.control( "FREQ?", "FREQ %f", """ A floating point property that controls the frequency of the output waveform in Hz, from 1 uHz to 120 MHz (maximum range, can be lower depending on your device), depending on the specified function. Can be set. """, validator=strict_range, values=[1e-6, 120e+6], ) amplitude = Instrument.control( "VOLT?", "VOLT %f", """ A floating point property that controls the voltage amplitude of the output waveform in V, from 10e-3 V to 10 V. Depends on the output impedance. Can be set. """, validator=strict_range, values=[10e-3, 10], ) amplitude_unit = Instrument.control( "VOLT:UNIT?", "VOLT:UNIT %s", """ A string property that controls the units of the amplitude. Valid values are VPP (default), VRMS, and DBM. Can be set. """, validator=strict_discrete_set, values=["VPP", "VRMS", "DBM"], ) offset = Instrument.control( "VOLT:OFFS?", "VOLT:OFFS %f", """ A floating point property that controls the voltage offset of the output waveform in V, from 0 V to 4.995 V, depending on the set voltage amplitude (maximum offset = (Vmax - voltage) / 2). Can be set. """, validator=strict_range, values=[-4.995, +4.995], ) voltage_high = Instrument.control( "VOLT:HIGH?", "VOLT:HIGH %f", """ A floating point property that controls the upper voltage of the output waveform in V, from -4.990 V to 5 V (must be higher than low voltage by at least 1 mV). Can be set. """, validator=strict_range, values=[-4.99, 5], ) voltage_low = Instrument.control( "VOLT:LOW?", "VOLT:LOW %f", """ A floating point property that controls the lower voltage of the output waveform in V, from -5 V to 4.990 V (must be lower than high voltage by at least 1 mV). Can be set. """, validator=strict_range, values=[-5, 4.99], ) phase = Instrument.control( "PHAS?", "PHAS %f", """ A floating point property that controls the phase of the output waveform in degrees, from -360 degrees to 360 degrees. Not available for arbitrary waveforms or noise. Can be set. """, validator=strict_range, values=[-360, 360], ) square_dutycycle = Instrument.control( "FUNC:SQU:DCYC?", "FUNC:SQU:DCYC %f", """ A floating point property that controls the duty cycle of a square waveform function in percent, from 0.01% to 99.98%. The duty cycle is limited by the frequency and the minimal pulse width of 16 ns. See manual for more details. Can be set. """, validator=strict_range, values=[0.01, 99.98], ) ramp_symmetry = Instrument.control( "FUNC:RAMP:SYMM?", "FUNC:RAMP:SYMM %f", """ A floating point property that controls the symmetry percentage for the ramp waveform, from 0.0% to 100.0% Can be set. """, validator=strict_range, values=[0, 100], ) pulse_period = Instrument.control( "FUNC:PULS:PER?", "FUNC:PULS:PER %e", """ A floating point property that controls the period of a pulse waveform function in seconds, ranging from 33 ns to 1e6 s. Can be set and overwrites the frequency for *all* waveforms. If the period is shorter than the pulse width + the edge time, the edge time and pulse width will be adjusted accordingly. """, validator=strict_range, values=[33e-9, 1e6], ) pulse_hold = Instrument.control( "FUNC:PULS:HOLD?", "FUNC:PULS:HOLD %s", """ A string property that controls if either the pulse width or the duty cycle is retained when changing the period or frequency of the waveform. Can be set to: WIDT<H> or DCYC<LE>. """, validator=strict_discrete_set, values=["WIDT", "WIDTH", "DCYC", "DCYCLE"], ) pulse_width = Instrument.control( "FUNC:PULS:WIDT?", "FUNC:PULS:WIDT %e", """ A floating point property that controls the width of a pulse waveform function in seconds, ranging from 16 ns to 1e6 s, within a set of restrictions depending on the period. Can be set. """, validator=strict_range, values=[16e-9, 1e6], ) pulse_dutycycle = Instrument.control( "FUNC:PULS:DCYC?", "FUNC:PULS:DCYC %f", """ A floating point property that controls the duty cycle of a pulse waveform function in percent, from 0% to 100%. Can be set. """, validator=strict_range, values=[0, 100], ) pulse_transition = Instrument.control( "FUNC:PULS:TRAN?", "FUNC:PULS:TRAN:BOTH %e", """ A floating point property that controls the edge time in seconds for both the rising and falling edges. It is defined as the time between the 10% and 90% thresholds of the edge. Valid values are between 8.4 ns to 1 µs. Can be set. """, validator=strict_range, values=[8.4e-9, 1e-6], ) output = Instrument.control( "OUTP?", "OUTP %d", """ A boolean property that turns on (True, 'on') or off (False, 'off') the output of the function generator. Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, 'on': 1, 'ON': 1, False: 0, 'off': 0, 'OFF': 0 }, ) output_load = Instrument.control( "OUTP:LOAD?", "OUTP:LOAD %s", """ Sets the expected load resistance (should be the load impedance connected to the output. The output impedance is always 50 Ohm, this setting can be used to correct the displayed voltage for loads unmatched to 50 Ohm. Valid values are between 1 and 10 kOhm or INF for high impedance. No validator is used since both numeric and string inputs are accepted, thus a value outside the range will not return an error. Can be set. """, ) burst_state = Instrument.control( "BURS:STAT?", "BURS:STAT %d", """ A boolean property that controls whether the burst mode is on (True) or off (False). Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, False: 0 }, ) burst_mode = Instrument.control( "BURS:MODE?", "BURS:MODE %s", """ A string property that controls the burst mode. Valid values are: TRIG<GERED>, GAT<ED>. This setting can be set. """, validator=strict_discrete_set, values=["TRIG", "TRIGGERED", "GAT", "GATED"], ) burst_period = Instrument.control( "BURS:INT:PER?", "BURS:INT:PER %e", """ A floating point property that controls the period of subsequent bursts. Has to follow the equation burst_period > (burst_ncycles / frequency) + 1 µs. Valid values are 1 µs to 8000 s. Can be set. """, validator=strict_range, values=[1e-6, 8000], ) burst_ncycles = Instrument.control( "BURS:NCYC?", "BURS:NCYC %d", """ An integer property that sets the number of cycles to be output when a burst is triggered. Valid values are 1 to 100000. This can be set. """, validator=strict_range, values=range(1, 100000), ) arb_file = Instrument.control( "FUNC:ARB?", "FUNC:ARB %s", """ A string property that selects the arbitrary signal from the volatile memory of the device. String has to match an existing arb signal in volatile memore (set by data_arb()). Can be set. """) arb_advance = Instrument.control( "FUNC:ARB:ADV?", "FUNC:ARB:ADV %s", """ A string property that selects how the device advances from data point to data point. Can be set to 'TRIG<GER>' or 'SRAT<E>' (default). """, validator=strict_discrete_set, values=["TRIG", "TRIGGER", "SRAT", "SRATE"], ) arb_filter = Instrument.control( "FUNC:ARB:FILT?", "FUNC:ARB:FILT %s", """ A string property that selects the filter setting for arbitrary signals. Can be set to 'NORM<AL>', 'STEP' and 'OFF'. """, validator=strict_discrete_set, values=["NORM", "NORMAL", "STEP", "OFF"], ) # TODO: This implementation is currently not working. Do not know why. # arb_period = Instrument.control( # "FUNC:ARB:PER?", "FUNC:ARB:PER %e", # """ A floating point property that controls the period of the arbitrary signal. # Limited by number of signal points. Check for instrument errors when setting # this property. Can be set. """, # validator=strict_range, # values=[33e-9, 1e6], # ) # # arb_frequency = Instrument.control( # "FUNC:ARB:FREQ?", "FUNC:ARB:FREQ %f", # """ A floating point property that controls the frequency of the arbitrary signal. # Limited by number of signal points. Check for instrument # errors when setting this property. Can be set. """, # validator=strict_range, # values=[1e-6, 30e+6], # ) # # arb_npoints = Instrument.measurement( # "FUNC:ARB:POIN?", # """ Returns the number of points in the currently selected arbitrary trace. """ # ) # # arb_voltage = Instrument.control( # "FUNC:ARB:PTP?", "FUNC:ARB:PTP %f", # """ An floating point property that sets the peak-to-peak voltage for the # currently selected arbitrary signal. Valid values are 1 mV to 10 V. This can be # set. """, # validator=strict_range, # values=[0.001, 10], # ) arb_srate = Instrument.control( "FUNC:ARB:SRAT?", "FUNC:ARB:SRAT %f", """ An floating point property that sets the sample rate of the currently selected arbitrary signal. Valid values are 1 µSa/s to 250 MSa/s (maximum range, can be lower depending on your device). This can be set. """, validator=strict_range, values=[1e-6, 250e6], ) def data_volatile_clear(self): """ Clear all arbitrary signals from the volatile memory. This should be done if the same name is used continuously to load different arbitrary signals into the memory, since an error will occur if a trace is loaded which already exists in the memory. """ self.write("DATA:VOL:CLE") def data_arb(self, arb_name, data_points, data_format='DAC'): """ Uploads an arbitrary trace into the volatile memory of the device. The data_points can be given as comma separated 16 bit DAC values (ranging from -32767 to +32767), as comma separated floating point values (ranging from -1.0 to +1.0) or as a binary data stream. Check the manual for more information. The storage depends on the device type and ranges from 8 Sa to 16 MSa (maximum). TODO: *Binary is not yet implemented* :param arb_name: The name of the trace in the volatile memory. This is used to access the trace. :param data_points: Individual points of the trace. The format depends on the format parameter. format = 'DAC' (default): Accepts list of integer values ranging from -32767 to +32767. Minimum of 8 a maximum of 65536 points. format = 'float': Accepts list of floating point values ranging from -1.0 to +1.0. Minimum of 8 a maximum of 65536 points. format = 'binary': Accepts a binary stream of 8 bit data. :param data_format: Defines the format of data_points. Can be 'DAC' (default), 'float' or 'binary'. See documentation on parameter data_points above. """ if data_format == 'DAC': separator = ', ' data_points_str = [str(item) for item in data_points ] # Turn list entries into strings data_string = separator.join( data_points_str) # Join strings with separator print(f"DATA:ARB:DAC {arb_name}, {data_string}") self.write(f"DATA:ARB:DAC {arb_name}, {data_string}") return elif data_format == 'float': separator = ', ' data_points_str = [str(item) for item in data_points ] # Turn list entries into strings data_string = separator.join( data_points_str) # Join strings with separator print(f"DATA:ARB {arb_name}, {data_string}") self.write(f"DATA:ARB {arb_name}, {data_string}") return elif data_format == 'binary': raise NotImplementedError( 'The binary format has not yet been implemented. Use "DAC" or "float" instead.' ) else: raise ValueError( 'Undefined format keyword was used. Valid entries are "DAC", "float" and "binary"' ) display = Instrument.setting( "DISP:TEXT '%s'", """ A string property which is displayed on the front panel of the device. Can be set. """, ) def clear_display(self): """ Removes a text message from the display. """ self.write("DISP:TEXT:CLE") def trigger(self): """ Send a trigger signal to the function generator. """ self.write("*TRG;*WAI") def wait_for_trigger(self, timeout=3600, should_stop=lambda: False): """ Wait until the triggering has finished or timeout is reached. :param timeout: The maximum time the waiting is allowed to take. If timeout is exceeded, a TimeoutError is raised. If timeout is set to zero, no timeout will be used. :param should_stop: Optional function (returning a bool) to allow the waiting to be stopped before its end. """ self.write("*OPC?") t0 = time() while True: try: ready = bool(self.read()) except VisaIOError: ready = False if ready: return if timeout != 0 and time() - t0 > timeout: raise TimeoutError( "Timeout expired while waiting for the Agilent 33220A" + " to finish the triggering.") if should_stop: return trigger_source = Instrument.control( "TRIG:SOUR?", "TRIG:SOUR %s", """ A string property that controls the trigger source. Valid values are: IMM<EDIATE> (internal), EXT<ERNAL> (rear input), BUS (via trigger command). This setting can be set. """, validator=strict_discrete_set, values=["IMM", "IMMEDIATE", "EXT", "EXTERNAL", "BUS"], ) ext_trig_out = Instrument.control( "OUTP:TRIG?", "OUTP:TRIG %d", """ A boolean property that controls whether the trigger out signal is active (True) or not (False). This signal is output from the Ext Trig connector on the rear panel in Burst and Wobbel mode. Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, False: 0 }, )
class DSP7265(Instrument): """This is the class for the DSP 7265 lockin amplifier""" SENSITIVITIES = [ np.nan, 2.0e-9, 5.0e-9, 10.0e-9, 20.0e-9, 50.0e-9, 100.0e-9, 200.0e-9, 500.0e-9, 1.0e-6, 2.0e-6, 5.0e-6, 10.0e-6, 20.0e-6, 50.0e-6, 100.0e-6, 200.0e-6, 500.0e-6, 1.0e-3, 2.0e-3, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3, 200.0e-3, 500.0e-3, 1.0 ] SEN_MULTIPLIER = [1, 1e-6, 1e-8] TIME_CONSTANTS = [ 10.0e-6, 20.0e-6, 40.0e-6, 80.0e-6, 160.0e-6, 320.0e-6, 640.0e-6, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3, 200.0e-3, 500.0e-3, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1.0e3, 2.0e3, 5.0e3, 10.0e3, 20.0e3, 50.0e3 ] REFERENCES = ['internal', 'external rear', 'external front'] IMODES = ['voltage mode', 'current mode', 'low noise current mode'] CURVE_BITS = ['x', 'y', 'magnitude', 'phase', 'sensitivity', 'adc1', 'adc2', 'adc3', 'dac1', 'dac2', 'noise', 'ratio', 'log ratio', 'event', 'frequency part 1', 'frequency part 2', # Dual modes 'x2', 'y2', 'magnitude2', 'phase2', 'sensitivity2'] def __init__(self, resourceName, **kwargs): super().__init__( resourceName, "Signal Recovery DSP 7265", includeSCPI=False, # Remove extra unicode character preprocess_reply=lambda r: r.replace('\x00', ''), **kwargs ) voltage = Instrument.control( "OA.", "OA. %g", """ A floating point property that represents the voltage in Volts. This property can be set. """, validator=truncated_range, values=[0, 5] ) frequency = Instrument.control( "OF.", "OF. %g", """ A floating point property that represents the lock-in frequency in Hz. This property can be set. """, validator=truncated_range, values=[0, 2.5e5] ) dac1 = Instrument.control( "DAC. 1", "DAC. 1 %g", """ A floating point property that represents the output value on DAC1 in Volts. This property can be set. """, validator=truncated_range, values=[-12, 12] ) dac2 = Instrument.control( "DAC. 2", "DAC. 2 %g", """ A floating point property that represents the output value on DAC2 in Volts. This property can be set. """, validator=truncated_range, values=[-12, 12] ) dac3 = Instrument.control( "DAC. 3", "DAC. 3 %g", """ A floating point property that represents the output value on DAC3 in Volts. This property can be set. """, validator=truncated_range, values=[-12, 12] ) dac4 = Instrument.control( "DAC. 4", "DAC. 4 %g", """ A floating point property that represents the output value on DAC4 in Volts. This property can be set. """, validator=truncated_range, values=[-12, 12] ) harmonic = Instrument.control( "REFN", "REFN %d", """ An integer property that represents the reference harmonic mode control, taking values from 1 to 65535. This property can be set. """, validator=truncated_discrete_set, values=list(range(65535)) ) reference_phase = Instrument.control( "REFP.", "REFP. %g", """ A floating point property that represents the reference harmonic phase in degrees. This property can be set. """, validator=modular_range_bidirectional, values=[0, 360] ) x = Instrument.measurement("X.", """ Reads the X value in Volts """ ) y = Instrument.measurement("Y.", """ Reads the Y value in Volts """ ) xy = Instrument.measurement("XY.", """ Reads both the X and Y values in Volts """ ) mag = Instrument.measurement("MAG.", """ Reads the magnitude in Volts """ ) phase = Instrument.measurement("PHA.", """ Reads the phase in degrees """ ) adc1 = Instrument.measurement("ADC. 1", """ Reads the input value of ADC1 in Volts """ ) adc2 = Instrument.measurement("ADC. 2", """ Reads the input value of ADC2 in Volts """ ) id = Instrument.measurement("ID", """ Reads the instrument identification """ ) ratio = Instrument.measurement("RT.", """ Reads the ratio output, defined as X/ADC1 """ ) log_ratio = Instrument.measurement("LR.", """ Reads the log ratio output, defined as log(X/ADC1) """ ) reference = Instrument.control( "IE", "IE %d", """Controls the oscillator reference. Can be "internal", "external rear" or "external front" """, validator=strict_discrete_set, values=REFERENCES, map_values=True ) @property def sensitivity(self): """ A floating point property that controls the sensitivity range in Volts (for voltage mode) or Amps (for current modes). When in Volts it takes discrete values from 2 nV to 1 V. When in Amps it takes discrete values from 2 fA to 1 µA (for normal current mode) or up to 10 nA (for low noise current mode). This property can be set. """ return self.values("SEN.")[0] @sensitivity.setter def sensitivity(self, value): # get the voltage/current mode: imode = self.IMODES.index(self.imode) # Scale the sensitivities to the correct range for voltage/current mode sensitivities = [s * self.SEN_MULTIPLIER[imode] for s in self.SENSITIVITIES] if imode == 2: sensitivities[0:7] = [np.nan] * 7 # Check and map the value value = truncated_discrete_set(value, sensitivities) print(value) value = sensitivities.index(value) # Set sensitivity self.write("SEN %d" % value) imode = Instrument.control( "IMODE", "IMODE %d", """ Property that controls the voltage/current mode. can be 'voltage mode', 'current mode', or 'low noise current mode' """, validator=strict_discrete_set, values=IMODES, map_values=True ) slope = Instrument.control( "SLOPE", "SLOPE %d", """ A integer property that controls the filter slope in dB/octave, which can take the values 6, 12, 18, or 24 dB/octave. This property can be set. """, validator=truncated_discrete_set, values=[6, 12, 18, 24], map_values=True ) time_constant = Instrument.control( "TC", "TC %d", """ A floating point property that controls the time constant in seconds, which takes values from 10 microseconds to 50,000 seconds. This property can be set. """, validator=truncated_discrete_set, values=TIME_CONSTANTS, map_values=True ) def set_voltage_mode(self): self.write("IMODE 0") def setDifferentialMode(self, lineFiltering=True): """Sets lockin to differential mode, measuring A-B""" self.write("VMODE 3") self.write("LF %d 0" % 3 if lineFiltering else 0) def setChannelAMode(self): self.write("VMODE 1") @property def adc3(self): # 50,000 for 1V signal over 1 s integral = self.values("ADC 3")[0] return integral / (50000.0 * self.adc3_time) @property def adc3_time(self): # Returns time in seconds return self.values("ADC3TIME")[0] / 1000.0 @adc3_time.setter def adc3_time(self, value): # Takes time in seconds self.write("ADC3TIME %g" % int(1000 * value)) sleep(value * 1.2) @property def auto_gain(self): return (int(self.values("AUTOMATIC")) == 1) @auto_gain.setter def auto_gain(self, value): if value: self.write("AUTOMATIC 1") else: self.write("AUTOMATIC 0") def auto_sensitivity(self): self.write("AS") def auto_phase(self): self.write("AQN") @property def gain(self): return self.values("ACGAIN") @gain.setter def gain(self, value): self.write("ACGAIN %d" % int(value / 10.0)) curve_buffer_bits = Instrument.control( "CBD", "CBD %d", """ An integer property that controls which data outputs are stored in the curve buffer. Valid values are values between 1 and 65,535 (or 2,097,151 in dual reference mode). """, values=[1, 2097151], validator=truncated_range, cast=int, ) curve_buffer_length = Instrument.control( "LEN", "LEN %d", """ An integer property that controls the length of the curve buffer. Valid values are values between 1 and 32,768, but the actual maximum amount of points is determined by the amount of curves that are stored, as set via the curve_buffer_bits property (32,768 / n) """, values=[1, 32768], validator=truncated_range, cast=int, ) curve_buffer_interval = Instrument.control( "STR", "STR %d", """ An integer property that controls Sets the time interval between successive points being acquired in the curve buffer. The time interval is specified in ms with a resolution of 5 ms; input values are rounded up to a multiple of 5. Valid values are values between 0 and 1,000,000,000 (corresponding to 12 days). The interval may be set to 0, which sets the rate of data storage to the curve buffer to 1.25 ms/point (800 Hz). However this only allows storage of the X and Y channel outputs. There is no need to issue a CBD 3 command to set this up since it happens automatically when acquisition starts. """, values=[1, 1000000000], validator=truncated_range, cast=int, ) curve_buffer_status = Instrument.measurement( "M", """ A property that represents the status of the curve buffer acquisition with four values: the first value represents the status with 5 possibilities (0: no activity, 1: acquisition via TD command running, 2: acquisition bya TDC command running, 5: acquisition via TD command halted, 6: acquisition bia TDC command halted); the second value is the number of sweeps that is acquired; the third value is the decimal representation of the status byte (the same response as the ST command; the fourth value is the number of points acquired in the curve buffer. """, cast=int, ) def init_curve_buffer(self): """ Initializes the curve storage memory and status variables. All record of previously taken curves is removed. """ self.write("NC") def set_buffer(self, points, quantities=None, interval=10.0e-3): """ Method that prepares the curve buffer for a measurement. :param int points: Number of points to be recorded in the curve buffer :param list quantities: List containing the quantities (strings) that are to be recorded in the curve buffer, can be any of: 'x', 'y', 'magnitude', 'phase', 'sensitivity', 'adc1', 'adc2', 'adc3', 'dac1', 'dac2', 'noise', 'ratio', 'log ratio', 'event', 'frequency' (or 'frequency part 1' and 'frequency part 2'); for both dual modes, additional options are: 'x2', 'y2', 'magnitude2', 'phase2', 'sensitivity2'. Default is 'x' and 'y'. :param float interval: The interval between two subsequent points stored in the curve buffer in s. Default is 10 ms. """ if quantities is None: quantities = ["x", "y"] if "frequency" in quantities: quantities.remove("frequency") quantities.extend([ "frequency part 1", "frequency part 2" ]) # remove all possible duplicates quantities = list({q.lower() for q in quantities}) bits = 0 for q in quantities: bits += 2**self.CURVE_BITS.index(q) self.curve_buffer_bits = bits self.curve_buffer_length = points self.curve_buffer_interval = int(interval * 1000) self.init_curve_buffer() def start_buffer(self): """ Initiates data acquisition. Acquisition starts at the current position in the curve buffer and continues at the rate set by the STR command until the buffer is full. """ self.write("TD") def wait_for_buffer(self, timeout=None, delay=0.1): """ Method that waits until the curve buffer is filled """ start = time() while self.curve_buffer_status[0] == 1: sleep(delay) if timeout is not None and time() < start + timeout: break def get_buffer(self, quantity=None, convert_to_float=True, wait_for_buffer=True): """ Method that retrieves the buffer after it has been filled. The data retrieved from the lock-in is in a fixed-point format, which requires translation before it can be interpreted as meaningful data. When `convert_to_float` is True the conversion is performed (if possible) before returning the data. :param str quantity: If provided, names the quantity that is to be retrieved from the curve buffer; can be any of: 'x', 'y', 'magnitude', 'phase', 'sensitivity', 'adc1', 'adc2', 'adc3', 'dac1', 'dac2', 'noise', 'ratio', 'log ratio', 'event', 'frequency part 1' and 'frequency part 2'; for both dual modes, additional options are: 'x2', 'y2', 'magnitude2', 'phase2', 'sensitivity2'. If no quantity is provided, all available data is retrieved. :param bool convert_to_float: Bool that determines whether to convert the fixed-point buffer-data to meaningful floating point values via the `buffer_to_float` method. If True, this method tries to convert all the available data to meaningful values; if this is not possible, an exception will be raised. If False, this conversion is not performed and the raw buffer-data is returned. :param bool wait_for_buffer: Bool that determines whether to wait for the data acquisition to finished if this method is called before the acquisition is finished. If True, the method waits until the buffer is filled before continuing; if False, the method raises an exception if the acquisition is not finished when the method is called. """ # Check if buffer is finished if self.curve_buffer_status[0] != 0: if wait_for_buffer: self.wait_for_buffer() else: raise RuntimeError("Buffer acquisition is not yet finished.") # Check which quantities are recorded in the buffer bits = format(self.curve_buffer_bits, '021b')[::-1] quantity_enums = [e for e, b in enumerate(bits) if b == "1"] # Check if the provided quantity (if any) is indeed recorded if quantity is not None: if self.CURVE_BITS.index(quantity) in quantity_enums: quantity_enums = [self.CURVE_BITS.index(quantity)] else: raise KeyError("The selected quantity '%s' is not recorded;" "quantity should be one of: %s" % ( quantity, ", ".join( [self.CURVE_BITS[q] for q in quantity_enums] ))) # Retrieve the data data = {} for enum in quantity_enums: self.write("DC %d" % enum) q_data = [] while True: stb = format(self.adapter.connection.read_stb(), '08b')[::-1] if bool(int(stb[2])): raise ValueError("Status byte reports command parameter error.") if bool(int(stb[0])): break if bool(int(stb[7])): q_data.append(int(self.read().strip())) data[self.CURVE_BITS[enum]] = np.array(q_data) if convert_to_float: data = self.buffer_to_float(data) if quantity is not None: data = data[quantity] return data def buffer_to_float(self, buffer_data, sensitivity=None, sensitivity2=None, raise_error=True): """Method that converts fixed-point buffer data to floating point data. The provided data is converted as much as possible, but there are some requirements to the data if all provided columns are to be converted; if a key in the provided data cannot be converted it will be omitted in the returned data or an exception will be raised, depending on the value of raise_error. The requirements for converting the data are as follows: - Converting X, Y, magnitude and noise requires sensitivity data, which can either be part of the provided data or can be provided via the sensitivity argument - The same holds for X2, Y2 and magnitude2 with sensitivity2. - Converting the frequency requires both 'frequency part 1' and 'frequency part 2'. :param dict buffer_data: The data to be converted. Must be in the format as returned by the `get_buffer` method: a dict of numpy arrays. :param sensitivity: If provided, the sensitivity used to convert X, Y, magnitude and noise. Can be provided as a float or as an array that matches the length of elements in `buffer_data`. If both a sensitivity is provided and present in the buffer_data, the provided value is used for the conversion, but the sensitivity in the buffer_data is stored in the returned dict. :param sensitivity2: Same as the first sensitivity argument, but for X2, Y2, magnitude2 and noise2. :param bool raise_error: Determines whether an exception is raised in case not all keys provided in buffer_data can be converted. If False, the columns that cannot be converted are omitted in the returned dict. :return: Floating-point buffer data :rtype: dict """ data = {} def maybe_raise(message): if raise_error: raise ValueError(message) def convert_if_present(keys, multiply_by=1): """Copy any available entries from buffer_data to data, scale with multiply_by. """ for key in keys: if key in buffer_data: data[key] = buffer_data[key] * multiply_by # Sensitivity (for both single and dual modes) for key in ["sensitivity", "sensitivity2"]: if key in buffer_data: data[key] = np.array([ self.SENSITIVITIES[v % 32] * self.SEN_MULTIPLIER[v // 32] for v in buffer_data[key] ]) # Try to set sensitivity values from arg or data sensitivity = sensitivity or data.get('sensitivity', None) sensitivity2 = sensitivity2 or data.get('sensitivity2', None) if any(["x" in buffer_data, "y" in buffer_data, "magnitude" in buffer_data, "noise" in buffer_data, ]): if sensitivity is None: maybe_raise("X, Y, magnitude and noise cannot be converted as no " "sensitivity is provided, neither as argument nor as " "part of the buffer_data. ") else: convert_if_present(["x", "y", "magnitude", "noise"], sensitivity / 10000) # phase data (for both single and dual modes) convert_if_present(["phase", "phase2"], 1 / 100) # frequency data from frequency part 1 and 2 if "frequency part 1" in buffer_data or "frequency part 2" in buffer_data: if "frequency part 1" in buffer_data and "frequency part 2" in buffer_data: data["frequency"] = np.array([ int(format(v2, "016b") + format(v1, "016b"), 2) / 1000 for v1, v2 in zip(buffer_data["frequency part 1"], buffer_data["frequency part 2"]) ]) else: maybe_raise("Can calculate the frequency only when both" "frequency part 1 and 2 are provided.") # conversion for, adc1, adc2, dac1, dac2, ratio, and log ratio convert_if_present(["adc1", "adc2", "dac1", "dac2", "ratio", "log ratio"], 1 / 1000) # adc3 (integrating converter); requires a call to adc3_time if "adc3" in buffer_data: data["adc3"] = buffer_data["adc3"] / (50000 * self.adc3_time) # event does not require a conversion convert_if_present(["event"]) # X, Y, and magnitude data for both dual modes if any(["x2" in buffer_data, "y2" in buffer_data, "magnitude2" in buffer_data, ]): if sensitivity2 is None: maybe_raise("X2, Y2 and magnitude2 cannot be converted as no " "sensitivity2 is provided, neither as argument nor as " "part of the buffer_data. ") else: convert_if_present(["x2", "y2", "magnitude2"], sensitivity2 / 10000) return data def shutdown(self): log.info("Shutting down %s." % self.name) self.voltage = 0. self.isShutdown = True
class Keithley6517B(Instrument, KeithleyBuffer): """ Represents the Keithely 6517B ElectroMeter and provides a high-level interface for interacting with the instrument. .. code-block:: python keithley = Keithley6517B("GPIB::1") keithley.apply_voltage() # Sets up to source current keithley.source_voltage_range = 200 # Sets the source voltage # range to 200 V keithley.source_voltage = 20 # Sets the source voltage to 20 V keithley.enable_source() # Enables the source output keithley.measure_resistance() # Sets up to measure resistance keithley.ramp_to_voltage(50) # Ramps the voltage to 50 V print(keithley.resistance) # Prints the resistance in Ohms keithley.shutdown() # Ramps the voltage to 0 V # and disables output """ source_enabled = Instrument.measurement( "OUTPUT?", """ Reads a boolean value that is True if the source is enabled. """, cast=bool) @staticmethod def extract_value(result): """ extracts the physical value from a result object returned by the instrument """ m = re.fullmatch(r'([+\-0-9E.]+)[A-Z]{4}', result[0]) if m: return float(m.group(1)) return None ############### # Current (A) # ############### current = Instrument.measurement( ":MEAS?", """ Reads the current in Amps, if configured for this reading. """, get_process=extract_value) current_range = Instrument.control( ":SENS:CURR:RANG?", ":SENS:CURR:RANG:AUTO 0;:SENS:CURR:RANG %g", """ A floating point property that controls the measurement current range in Amps, which can take values between -20 and +20 mA. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[-20e-3, 20e-3]) current_nplc = Instrument.control( ":SENS:CURR:NPLC?", ":SENS:CURR:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the DC current measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """, values=[0.01, 10]) source_current_resistance_limit = Instrument.control( ":SOUR:CURR:RLIM?", ":SOUR:CURR:RLIM %g", """ Boolean property which enables or disables resistance current limit """, cast=bool) ############### # Voltage (V) # ############### voltage = Instrument.measurement( ":MEAS:VOLT?", """ Reads the voltage in Volts, if configured for this reading. """, get_process=extract_value) voltage_range = Instrument.control( ":SENS:VOLT:RANG?", ":SENS:VOLT:RANG:AUTO 0;:SENS:VOLT:RANG %g", """ A floating point property that controls the measurement voltage range in Volts, which can take values from -1000 to 1000 V. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[-1000, 1000]) voltage_nplc = Instrument.control( ":SENS:VOLT:NPLC?", ":SENS:VOLT:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the DC voltage measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """) source_voltage = Instrument.control( ":SOUR:VOLT?", ":SOUR:VOLT:LEV %g", """ A floating point property that controls the source voltage in Volts. """) source_voltage_range = Instrument.control( ":SOUR:VOLT:RANG?", ":SOUR:VOLT:RANG:AUTO 0;:SOUR:VOLT:RANG %g", """ A floating point property that controls the source voltage range in Volts, which can take values from -1000 to 1000 V. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[-1000, 1000]) #################### # Resistance (Ohm) # #################### resistance = Instrument.measurement( ":READ?", """ Reads the resistance in Ohms, if configured for this reading. """, get_process=extract_value) resistance_range = Instrument.control( ":SENS:RES:RANG?", ":SENS:RES:RANG:AUTO 0;:SENS:RES:RANG %g", """ A floating point property that controls the resistance range in Ohms, which can take values from 0 to 100e18 Ohms. Auto-range is disabled when this property is set. """, validator=truncated_range, values=[0, 100e18]) resistance_nplc = Instrument.control( ":SENS:RES:NPLC?", ":SENS:RES:NPLC %g", """ A floating point property that controls the number of power line cycles (NPLC) for the 2-wire resistance measurements, which sets the integration period and measurement speed. Takes values from 0.01 to 10, where 0.1, 1, and 10 are Fast, Medium, and Slow respectively. """) buffer_points = Instrument.control( ":TRAC:POIN?", ":TRAC:POIN %d", """ An integer property that controls the number of buffer points. This does not represent actual points in the buffer, but the configuration value instead. """, validator=truncated_range, values=[1, 6875000], cast=int) #################### # Methods # #################### def __init__(self, adapter, **kwargs): super(Keithley6517B, self).__init__( adapter, "Keithley 6517B Electrometer/High Resistance Meter", **kwargs) def enable_source(self): """ Enables the source of current or voltage depending on the configuration of the instrument. """ self.write("OUTPUT ON") def disable_source(self): """ Disables the source of current or voltage depending on the configuration of the instrument. """ self.write("OUTPUT OFF") def measure_resistance(self, nplc=1, resistance=2.1e5, auto_range=True): """ Configures the measurement of resistance. :param nplc: Number of power line cycles (NPLC) from 0.01 to 10 :param resistance: Upper limit of resistance in Ohms, from -210 POhms to 210 POhms :param auto_range: Enables auto_range if True, else uses the resistance_range attribut """ log.info("%s is measuring resistance.", self.name) self.write(":SENS:FUNC 'RES';" ":SENS:RES:NPLC %f;" % nplc) if auto_range: self.write(":SENS:RES:RANG:AUTO 1;") else: self.resistance_range = resistance self.check_errors() def measure_voltage(self, nplc=1, voltage=21.0, auto_range=True): """ Configures the measurement of voltage. :param nplc: Number of power line cycles (NPLC) from 0.01 to 10 :param voltage: Upper limit of voltage in Volts, from -1000 V to 1000 V :param auto_range: Enables auto_range if True, else uses the voltage_range attribut """ log.info("%s is measuring voltage.", self.name) self.write(":SENS:FUNC 'VOLT';" ":SENS:VOLT:NPLC %f;" % nplc) if auto_range: self.write(":SENS:VOLT:RANG:AUTO 1;") else: self.voltage_range = voltage self.check_errors() def measure_current(self, nplc=1, current=1.05e-4, auto_range=True): """ Configures the measurement of current. :param nplc: Number of power line cycles (NPLC) from 0.01 to 10 :param current: Upper limit of current in Amps, from -21 mA to 21 mA :param auto_range: Enables auto_range if True, else uses the current_range attribut """ log.info("%s is measuring current.", self.name) self.write(":SENS:FUNC 'CURR';" ":SENS:CURR:NPLC %f;" % nplc) if auto_range: self.write(":SENS:CURR:RANG:AUTO 1;") else: self.current_range = current self.check_errors() def auto_range_source(self): """ Configures the source to use an automatic range. """ self.write(":SOUR:VOLT:RANG:AUTO 1") def apply_voltage(self, voltage_range=None): """ Configures the instrument to apply a source voltage, and uses an auto range unless a voltage range is specified. :param voltage_range: A :attr:`~.Keithley6517B.voltage_range` value or None (activates auto range) """ log.info("%s is sourcing voltage.", self.name) if voltage_range is None: self.auto_range_source() else: self.source_voltage_range = voltage_range self.check_errors() @property def error(self): """ Returns a tuple of an error code and message from a single error. """ err = self.values(":system:error?") if len(err) < 2: err = self.read() # Try reading again code = err[0] message = err[1].replace('"', '') return (code, message) def check_errors(self): """ Logs any system errors reported by the instrument. """ code, message = self.error while code != 0: t = time.time() log.info("Keithley 6517B reported error: %d, %s", code, message) code, message = self.error if (time.time() - t) > 10: log.warning("Timed out for Keithley 6517B error retrieval.") def reset(self): """ Resets the instrument and clears the queue. """ self.write("*RST;:stat:pres;:*CLS;") def ramp_to_voltage(self, target_voltage, steps=30, pause=20e-3): """ Ramps to a target voltage from the set voltage value over a certain number of linear steps, each separated by a pause duration. :param target_voltage: A voltage in Volts :param steps: An integer number of steps :param pause: A pause duration in seconds to wait between steps """ voltages = np.linspace(self.source_voltage, target_voltage, steps) for voltage in voltages: self.source_voltage = voltage time.sleep(pause) def trigger(self): """ Executes a bus trigger, which can be used when :meth:`~.trigger_on_bus` is configured. """ return self.write("*TRG") def trigger_immediately(self): """ Configures measurements to be taken with the internal trigger at the maximum sampling rate. """ self.write(":TRIG:SOUR IMM;") def trigger_on_bus(self): """ Configures the trigger to detect events based on the bus trigger, which can be activated by :meth:`~.trigger`. """ self.write(":TRIG:SOUR BUS;") def shutdown(self): """ Ensures that the current or voltage is turned to zero and disables the output. """ log.info("Shutting down %s.", self.name) self.ramp_to_voltage(0.0) self.stop_buffer() self.disable_source()