Exemple #1
0
        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"))
Exemple #2
0
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,
        )
Exemple #3
0
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
Exemple #4
0
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
Exemple #6
0
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
Exemple #7
0
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")
Exemple #8
0
 class Fake(FakeInstrument):
     x = Instrument.measurement(
         "",
         "",
         values={
             'X': 1,
             'Y': 2,
             'Z': 3
         },
         map_values=True,
         dynamic=dynamic,
     )
Exemple #9
0
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)
Exemple #10
0
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
        )
Exemple #11
0
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()
Exemple #12
0
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")
Exemple #13
0
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()
Exemple #14
0
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)
Exemple #15
0
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
Exemple #16
0
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,
    )
Exemple #17
0
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
Exemple #19
0
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")
Exemple #20
0
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)
Exemple #21
0
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
Exemple #22
0
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))
Exemple #23
0
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()
Exemple #24
0
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'
Exemple #25
0
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")
Exemple #26
0
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")
Exemple #27
0
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])
Exemple #28
0
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
        },
    )
Exemple #29
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
Exemple #30
0
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()