예제 #1
0
    def set_monitor(self, monitor_ch, source):
        """
        Configures the specified monitor channel to view the desired PID
        instrument signal.

        There are two 12-bit monitoring channels available, 'a' and 'b';
        each of these can be assigned to source signals from any of the
        internal PID instrument monitoring points. Signals larger than 12-bits
        must be either truncated or clipped to the allowed size.

        The source is one of:
            - **adc1** : Channel 1 ADC input
            - **in1** : PID Channel 1 input (after mixing, offset and scaling)
            - **out1** : PID Channel 1 output
            - **adc2** : Channel 2 ADC Input
            - **in2** : PID Channel 2 input (after mixing, offset and scaling)
            - **out2** : PID Channel 2 output

        :type monitor_ch: str; {'a','b'}
        :param monitor_ch: Monitor channel

        :type source: str; {'adc1', 'in1', 'out1', 'adc2', 'in2', 'out2'}
        :param source: Signal to connect to the monitor channel

        """
        _utils.check_parameter_valid('string',
                                     monitor_ch,
                                     desc="monitor channel")
        _utils.check_parameter_valid('string', source, desc="monitor signal")

        monitor_sources = {
            'none': _PID_MON_NONE,
            'adc1': _PID_MON_ADC1,
            'in1': _PID_MON_IN1,
            'out1': _PID_MON_OUT1,
            'adc2': _PID_MON_ADC2,
            'in2': _PID_MON_IN2,
            'out2': _PID_MON_OUT2
        }
        monitor_ch = monitor_ch.lower()
        source = source.lower()

        _utils.check_parameter_valid('set',
                                     monitor_ch,
                                     allowed=['a', 'b'],
                                     desc="monitor channel")
        _utils.check_parameter_valid(
            'set',
            source,
            allowed=['none', 'adc1', 'in1', 'out1', 'adc2', 'in2', 'out2'],
            desc="monitor source")

        if monitor_ch == 'a':
            self.monitor_a = source
            self.mon1_source = monitor_sources[source]
        elif monitor_ch == 'b':
            self.monitor_b = source
            self.mon2_source = monitor_sources[source]
        else:
            raise ValueOutOfRangeException("Invalid channel %d", monitor_ch)
예제 #2
0
    def set_monitor(self, monitor_ch, source):
        """
        Select the point inside the lockin amplifier to monitor.

        There are two monitoring channels available, 'A' and 'B'; you can mux
        any of the internal monitoring points to either of these channels.

        The source is one of:
            - **none**: Disable monitor channel
            - **in1**, **in2**: Input Channel 1/2
            - **main**: Lock-in output (Output Channel 1)
            - **aux**: Auxillary output (Output Channel 2)
            - **demod**: Demodulation signal input to mixer
            - **i**, **q**: Mixer I and Q channels respectively.

        :type monitor_ch: string; {'A','B'}
        :param monitor_ch: Monitor channel
        :type source: string; {'none','in1','in2','main','aux','demod','i','q'}
        :param source: Signal to monitor
        """
        _utils.check_parameter_valid('string',
                                     monitor_ch,
                                     desc="monitor channel")
        _utils.check_parameter_valid('string', source, desc="monitor signal")

        monitor_ch = monitor_ch.lower()
        source = source.lower()

        _utils.check_parameter_valid('set',
                                     monitor_ch,
                                     allowed=['a', 'b'],
                                     desc="monitor channel")
        _utils.check_parameter_valid(
            'set',
            source,
            allowed=['none', 'in1', 'in2', 'main', 'aux', 'demod', 'i', 'q'],
            desc="monitor source")

        monitor_sources = {
            'none': _LIA_MON_NONE,
            'in1': _LIA_MON_IN1,
            'in2': _LIA_MON_IN2,
            'main': _LIA_MON_OUT,
            'aux': _LIA_MON_AUX,
            'demod': _LIA_MON_DEMOD,
            'i': _LIA_MON_I,
            'q': _LIA_MON_Q,
        }

        if monitor_ch == 'a':
            self.monitor_a = source
            self.monitor_select0 = monitor_sources[source]
        elif monitor_ch == 'b':
            self.monitor_b = source
            self.monitor_select1 = monitor_sources[source]
        else:
            raise ValueOutOfRangeException("Invalid channel %d", monitor_ch)
예제 #3
0
    def set_filter(self, ch, decimation_factor, filter_coefficients):
        """
        Set FIR filter sample rate and kernel coefficients for the specified
        filter channel. This will enable the specified channel output.
        See class documentation for information on the `filter_coefficients`
        array formatting and how
        `decimation_factor` relates to instrument sample rate.

        :type ch: int; {1,2}
        :param ch: Filter channel.

        :type decimation_factor: int; [3,10]
        :param decimation_factor: the binary exponent *n* specifying the
         filter sample rate: :math:`Fs = 125 MHz / 2^n`.

        :type filter_coefficients: array(float)
        :param filter_coefficients: array of FIR filter coefficients. The
         length of the array must not exceed :math:`N = min(29*2^n, 14819)`.
        """
        _utils.check_parameter_valid('set', ch, [1, 2], 'filter channel')
        _utils.check_parameter_valid('set', decimation_factor, range(3, 11),
                                     'decimation factor')
        _utils.check_parameter_valid(
            'range', len(filter_coefficients),
            [0, _FIR_NUM_BLOCKS * min(2**decimation_factor, 2**9 - 1)],
            'filter coefficient array length')
        # Check that all coefficients are between -1.0 and 1.0
        if not all(map(lambda x: abs(x) <= 1.0, filter_coefficients)):
            raise ValueOutOfRangeException("set_filter filter coefficients "
                                           "must be in the range "
                                           "[-1.0, 1.0].")
        """ IMPORTANT: The decimation filter samplerate must be set before the
                       coefficients are written
                       this is because the mmap access bit appropriately resets
                       the other decimation blocks.
        """
        self._set_samplerate(ch, 2.0**decimation_factor)

        self._write_coeffs(ch, filter_coefficients)

        if ch == 1:
            self.filter_en1 = True
            self.output_en1 = True
            self.input_en1 = True
        else:
            self.filter_en2 = True
            self.output_en2 = True
            self.input_en2 = True

        self._channel_reset(ch)

        # Manually commit all outstanding registers as this function does not
        # use needs_commit
        # This is because the mmap_access register muts be committed when
        # writing coefficients.
        self.commit()
    def set_output(self, ch, amplitude, offset=0):
        """ Set the output sweep amplitude.

        .. note::
            Ensure that the output amplitude is set so as to not saturate the
            inputs.
            Inputs are limited to 1.0Vpp with attenuation turned off.

        :type ch: int; {1, 2}
        :param ch: Output channel

        :type amplitude: float; [0.0, 2.0] Vpp
        :param amplitude: Sweep amplitude

        :type offset: float; [-1.0, 1.0] Volts
        :param offset: Sweep offset
        """
        _utils.check_parameter_valid('set', ch, [1, 2], 'output channel')
        _utils.check_parameter_valid('range', amplitude, [0.001, 2.0],
                                     'sweep amplitude', 'Vpp')
        _utils.check_parameter_valid('range', offset, [-1.0, 1.0],
                                     'sweep offset', 'volts')

        # ensure combination of amplitude and offset doesn't cause output
        # clipping
        if ((amplitude / 2.0) + abs(offset)) > 1.0:
            raise ValueOutOfRangeException("Output sweep waveform must not "
                                           "exceed +/- 1.0 volts. Reduce "
                                           "output amplitude and/or offset.")

        # Set up the output scaling register but also save the voltage value
        # away for use in the state dictionary to scale incoming data
        if ch == 1:
            self.sweep_amplitude_ch1 = amplitude
            self.sweep_amp_volts_ch1 = amplitude
            self.sweep_offset_ch1 = offset
            self.channel1_en = amplitude > 0

        elif ch == 2:
            self.sweep_amplitude_ch2 = amplitude
            self.sweep_amp_volts_ch2 = amplitude
            self.sweep_offset_ch2 = offset
            self.channel2_en = amplitude > 0
예제 #5
0
    def set_filter(self, f_corner, order):
        """
        Set the low-pass filter parameters.

        :type f_corner: float
        :param f_corner: Corner frequency of the low-pass filter (Hz)

        :type order: int; [0, 1, 2]
        :param order: filter order; 0 (bypass), first- or second-order.

        """
        # Ensure the right parts of the filter are enabled
        self.lpf_den = 0

        if order == 0:
            self.filt_bypass1 = True
            self.filt_bypass2 = True
        if order == 1:
            self.filt_bypass1 = False
            self.filt_bypass2 = True
            self.slope = 1
        elif order == 2:
            self.filt_bypass1 = False
            self.filt_bypass2 = False
            self.slope = 2
        else:
            raise ValueOutOfRangeException("Order must be 0 (bypass), 1 or 2;"
                                           " not %d" % order)

        self.input_gain = 1.0

        gain_factor = 2.0**12 * self._adc_gains()[0] / \
            (10.0 if self.get_frontend(1)[1] else 1.0)
        self.lpf_pidgain = gain_factor if order == 1 else \
            math.sqrt(gain_factor)

        ifb = 1.0 - 2.0 * (math.pi * f_corner) / _LIA_CONTROL_FS
        self.lpf_int_ifb_gain = ifb
        self.lpf_int_i_gain = 1.0 - ifb
예제 #6
0
    def set_output_range(self, ch, maximum, minimum):
        """
        Set upper and lower bounds for the signal on each DAC channel.
        The auxilliary waveform is not restricted to these
        bounds when added to either DAC channel.

        :type ch : int; [1, 2]
        :param ch : 1 = Output 1, 2 = Output 2

        :type maximum: float, [-1.0, 1.0] Volts;
        :param maximum: maximum value the output signal can be before
            clipping occurs.

        :type minimum: float, [-1.0, 1.0] Volts;
        :param maximum: maximum value the output signal can be before
            clipping occurs.
        """
        _utils.check_parameter_valid('set', ch, [1, 2], 'output channel')
        _utils.check_parameter_valid('range',
                                     maximum, [-1.0, 1.0],
                                     desc='maximum',
                                     units='Volts')
        _utils.check_parameter_valid('range',
                                     minimum, [-1.0, 1.0],
                                     desc='minimum',
                                     units='Volts')
        if minimum > maximum:
            raise ValueOutOfRangeException(
                "Maximum range value must be greater than minimum.")

        if ch == 1:
            self.cliprange_upper_ch1 = maximum / self._dac_gains()[0] / 2.0**15
            self.cliprange_lower_ch1 = minimum / self._dac_gains()[0] / 2.0**15
        else:
            self.cliprange_upper_ch2 = maximum / self._dac_gains()[1] / 2.0**15
            self.cliprange_lower_ch2 = minimum / self._dac_gains()[1] / 2.0**15
예제 #7
0
    def set_monitor(self, monitor_ch, source):
        """
        Select the point inside the laser lock box to monitor.

        There are two monitoring channels available, 'A' and 'B'; you can mux
        any of the internal
        monitoring points to either of these channels.

        The source is one of:
            - **error_signal**: error signal (after low-pass filter)
            - **pid_fast**: output of the fast pid
            - **pid_slow**: output of the slow pid
            - **offset_fast**: offset on the input to the fast pid
            - **in1**: input channel 1
            - **in2**: input channel 2
            - **out1**: output channel 1
            - **out2**: output channel 2
            - **scan**: scan signal
            - **lo**: local oscillator signal
            - **aux**: auxiliary sinewave signal

        :type monitor_ch: string; {'A','B'}
        :param monitor_ch: Monitor channel
        :type source: string; {'error', 'pid_fast', 'pid_slow',
            'offset_fast', 'offset_slow', 'in1', 'in2', 'out1', 'out2',
            'scan', 'lo', 'aux', 'slow_scan'}
        :param source: Signal to monitor
        """
        _utils.check_parameter_valid('string',
                                     monitor_ch,
                                     desc="monitor channel")
        _utils.check_parameter_valid('string', source, desc="monitor signal")

        monitor_ch = monitor_ch.lower()
        source = source.lower()

        _utils.check_parameter_valid('set',
                                     monitor_ch,
                                     allowed=['a', 'b'],
                                     desc="monitor channel")
        _utils.check_parameter_valid('set',
                                     source,
                                     allowed=[
                                         'error', 'pid_fast', 'pid_slow',
                                         'offset_fast', 'offset_slow', 'in1',
                                         'in2', 'out1', 'out2', 'scan', 'lo',
                                         'aux', 'slow_scan'
                                     ],
                                     desc="monitor source")

        monitor_sources = {
            'error': _LLB_MON_ERROR,
            'pid_fast': _LLB_MON_PID_FAST,
            'pid_slow': _LLB_MON_PID_SLOW,
            'offset_fast': _LLB_MON_OFFSET_FAST,
            'in1': _LLB_MON_IN1,
            'in2': _LLB_MON_IN2,
            'out1': _LLB_MON_OUT1,
            'out2': _LLB_MON_OUT2,
            'scan': _LLB_MON_SCAN,
            'lo': _LLB_MON_LO,
            'aux': _LLB_MON_AUX
        }

        if monitor_ch == 'a':
            self.monitor_a = source
            self.monitor_select0 = monitor_sources[source]
        elif monitor_ch == 'b':
            self.monitor_b = source
            self.monitor_select1 = monitor_sources[source]
        else:
            raise ValueOutOfRangeException("Invalid channel %d", monitor_ch)
예제 #8
0
    def set_waveform_trigger(self,
                             ch,
                             source,
                             edge,
                             level,
                             minwidth=None,
                             maxwidth=None,
                             hysteresis=False):
        """ Specify what constitutes a trigger event for the given output
        channel.
        This takes effect only when the channel has triggered output mode
        enabled (see :any:`set_waveform_trigger_output
        <pymoku.instruments.ArbitraryWaveGen.set_waveform_trigger_output>`).

        :type ch: int; {1,2}
        :param ch: Output channel to set triggering on

        :type source: string, {'in1','in2','ext'}
        :param source: Trigger source. May be either input channel, or the
        external 'Trig' back-panel connector allowing triggering from an
        externally-generated digital [LV]TTL or CMOS signal.

        :type edge: string, {'rising','falling','both'}
        :param edge: Which edge to trigger on. In 'Pulse Width' mode this
        specifies whether the pulse is positive (rising) or negative (falling),
        with the 'both' option being invalid.

        :type level: float, [-5.0, 5.0] volts
        :param level: Trigger level. Ignored in 'ext' mode.

        :type minwidth: float, seconds
        :param minwidth:
            Minimum Pulse Width. 0 <= minwidth < (2^32/samplerate).
            Can't be used with maxwidth.

        :type maxwidth: float, seconds
        :param maxwidth:
            Maximum Pulse Width. 0 <= maxwidth < (2^32/samplerate).
            Can't be used with minwidth.

        :type hysteresis: bool
        :param hysteresis: Enable hysteresis around trigger point.

        """
        valid('set', ch, [1, 2], 'channel')
        valid('set', source, ['in1', 'in2', 'ext'], 'trigger source')
        valid('set', edge, ['rising', 'falling', 'both'])
        valid('range', level, [_ARB_TRIG_LVL_MIN, _ARB_TRIG_LVL_MAX],
              'trigger level', 'Volts')

        if not (maxwidth is None or minwidth is None):
            raise InvalidConfigurationException(
                "Can't set both 'minwidth' and 'maxwidth' for Pulse Width "
                "trigger mode. Choose one.")
        if (maxwidth or minwidth) and (edge == 'both'):
            raise InvalidConfigurationException(
                "Can't set trigger edge type 'both' in Pulse Width trigger "
                "mode. Choose one of {'rising','falling'}.")

        # External trigger source is only available on Moku 20
        if (self._moku.get_hw_version() == 1.0) and source == 'ext':
            raise InvalidConfigurationException(
                "External trigger source is not available on your hardware.")
        if source == 'ext' and level:
            log.warning(
                "Trigger level ignored when triggering from source 'ext'.")

        # TODO: Add timer source
        _str_to_source = {
            'in1': _ARB_TRIG_SRC_CH1,
            'in2': _ARB_TRIG_SRC_CH2,
            'ext': _ARB_TRIG_SRC_EXT
        }
        _str_to_edge = {
            'rising': Trigger.EDGE_RISING,
            'falling': Trigger.EDGE_FALLING,
            'both': Trigger.EDGE_BOTH
        }
        source = str_to_val(_str_to_source, source, 'trigger source')
        edge = str_to_val(_str_to_edge, edge, 'edge type')

        if ch == 1:
            self.trig_source1 = source
            self.trig_level1 = level
        elif ch == 2:
            self.trig_source2 = source
            self.trig_level2 = level
        else:
            raise ValueOutOfRangeException("Incorrect channel number %d", ch)

        trig_channels = [self._trigger1, self._trigger2]

        # AKA: Normal trigger mode only (HG-2598)
        trig_channels[ch - 1].timer = 0.0
        trig_channels[ch - 1].auto_holdoff = 0

        trig_channels[ch - 1].edge = edge
        trig_channels[ch - 1].duration = minwidth or maxwidth or 0.0
        # TODO: Enable setting hysteresis level. For now we use the iPad LSB
        # values for ON/OFF.
        trig_channels[ch - 1].hysteresis = 25 if hysteresis else 0

        if maxwidth:
            trig_channels[ch - 1].trigtype = Trigger.TYPE_PULSE
            trig_channels[ch - 1].pulsetype = Trigger.PULSE_MAX
        elif minwidth:
            trig_channels[ch - 1].trigtype = Trigger.TYPE_PULSE
            trig_channels[ch - 1].pulsetype = Trigger.PULSE_MIN
        else:
            trig_channels[ch - 1].trigtype = Trigger.TYPE_EDGE
예제 #9
0
    def gen_waveform(self,
                     ch,
                     period,
                     amplitude,
                     phase=0,
                     offset=0,
                     interpolation=True,
                     dead_time=0,
                     dead_voltage=0,
                     en=True):
        """ Configure and enable the Arbitrary Waveform on the given output
        channel.

        The look-up table for this channel's output waveform should have been
        loaded beforehand using :any:`write_lut`.

        The Arbitrary Waveform Generator has the ability to insert a deadtime
        between cycles of the look-up table. This time is specified in cycles
        of the waveform. During this time, the output will be held at the given
        *dead_voltage*.  This allows the user to, for example, generate
        infrequent pulses without using space in the LUT to specify the time
        between, keeping the full LUT size to provide a high-resolution pulse
        shape.

        Where the period and look-up table contents are set such that there
        isn't exactly one LUT point per output sample, the AWG instrument can
        optionally provide a linear interpolation between LUT points.

        This function enables the output channel by default. If you wish to
        enable the outputs simultaneously, you should set the `en` parameter to
        False and enable both when desired using :any:`enable_output`.

        :type ch: int; {1,2}
        :param ch: Channel on which to generate the wave

        :type period: float, [4e-9, 1];
        :param period: period of the signal in seconds

        :type amplitude: float, [0.0,2.0] Vpp
        :param amplitude: Waveform peak-to-peak amplitude

        :type phase: float, [0-360] degrees
        :param phase: Phase offset of the wave

        :type offset: float, [-1.0,1.0] Volts
        :param offset: DC offset applied to the waveform

        :type interpolation: bool [True, False]
        :param interpolation: Enable linear interpolation of LUT entries

        :type dead_time: float [0, 2e18] cyc
        :param dead_time: number of cycles which show the dead voltage. Use 0
        for no dead time

        :type dead_voltage: float [-2.0,2.0] V
        :param dead_voltage: signal level during dead time in Volts

        :type en: bool
        :param en: Enable output

        :raises ValueError: if the parameters  is invalid
        :raises ValueOutOfRangeException: if wave parameters are out of range
        :raises InvalidParameterException: if the parameters are the wrong
        types
        """
        valid('set', ch, [1, 2], desc='output channel')
        valid('range', period, [4e-9, 1000], desc='period of the signal')
        valid('range',
              amplitude, [0.0, 2.0],
              desc='peak to peak amplitude',
              units='volts')
        valid('bool', interpolation, desc='linear interpolation')
        valid('range',
              dead_time, [0.0, 2e18],
              desc='signal dead time',
              units='cycles')
        valid('range',
              dead_voltage, [-2.0, 2.0],
              desc='dead value',
              units='volts')
        valid('range', phase, [0, 360], desc='phase offset', units='degrees')
        valid('bool', en, 'output enable')

        upper_voltage = offset + (amplitude / 2.0)
        lower_voltage = offset - (amplitude / 2.0)

        if (upper_voltage > 1.0) or (lower_voltage < -1.0):
            raise ValueOutOfRangeException(
                "Waveform offset limited by amplitude "
                "(max output range 2.0Vpp).")

        # Ensure that dead voltage does not exceed the amplitude of the
        # waveform
        if dead_voltage > upper_voltage or dead_voltage < lower_voltage:
            raise ValueOutOfRangeException(
                "Dead voltage must not exceed custom waveform voltage range "
                "of [%.2f, %.2f] Volts.".format(lower_voltage, upper_voltage))

        if ch == 1:
            freq = 1.0 / period
            self.interpolation1 = interpolation
            phase_modulo = (self.lut_length1 + 1) * \
                _ARB_LUT_INTERPLOATION_LENGTH
            self._sweep1.step = freq / _ARB_SMPL_RATE * phase_modulo
            phase_modulo = phase_modulo * (1 + dead_time)
            self._sweep1.stop = phase_modulo
            self._sweep1.start = (phase / 360.0) * phase_modulo
            self.dead_value1 = 0.0 if not amplitude else 2.0 * \
                (dead_voltage - lower_voltage) / \
                (upper_voltage - lower_voltage) - 1.0
            self.amplitude1 = amplitude
            self.offset1 = offset
            self.enable1 = en

        if ch == 2:
            freq = 1.0 / period
            self.interpolation2 = interpolation
            phase_modulo = (self.lut_length2 + 1) * \
                _ARB_LUT_INTERPLOATION_LENGTH
            self._sweep2.step = freq / _ARB_SMPL_RATE * phase_modulo
            phase_modulo = phase_modulo * (1 + dead_time)
            self._sweep2.stop = phase_modulo
            self._sweep2.start = (phase / 360.0) * phase_modulo
            self.dead_value2 = 0.0 if not amplitude else 2.0 * \
                (dead_voltage - lower_voltage) / \
                (upper_voltage - lower_voltage) - 1.0
            self.amplitude2 = amplitude
            self.offset2 = offset
            self.enable2 = en
예제 #10
0
    def write_lut(self, ch, data, mode=None):
        """Writes the signal lookup table to memory in the Moku:Lab.

        You can also choose the output rate of the AWG, which influences the
        maximum length of the look-up table as follows:

        - 1000MSPS: 8192 points per channel
        - 500MSPS: 16384 points per channel
        - 250MSPS: 32768 points per channel
        - 125MSPS: 65536 points per channel

        If you don't specify a mode, the fastest output rate for the given data
        length will be automatically chosen. This is correct in almost all
        circumstances.

        If you specify a particular mode along with a data array too big for
        that mode, the behaviour is undefined.

        To avoid unexpected output signals during write, disable the outputs
        by using the :any:`enable_output` function.

        :type ch: int; {1,2}
        :param ch: Output channel to load the LUT to

        :type data: float array;
        :param data: Lookup table coefficients normalised to range [-1.0, 1.0].

        :type mode: int; {125, 250, 500, 1000} MSmps
        :param mode: defines the output sample rate of the AWG.

        :raises ValueError: if the channel is invalid
        :raises ValueOutOfRangeException: if wave parameters are out of range
        """
        valid('set', ch, [1, 2], 'output channel')
        valid('set',
              mode, [125, 250, 500, 1000],
              desc='output sample rate',
              units="MSmps",
              allow_none=True)

        # Check that all coefficients are between -1.0 and 1.0
        if not all(map(lambda x: abs(x) <= 1.0, data)):
            raise ValueOutOfRangeException(
                "Lookup table coefficients must be in the range [-1.0, 1.0].")

        n_points = len(data)

        if n_points <= 2**13:
            max_lut_samplerate = 1000
        elif n_points <= 2**14:
            max_lut_samplerate = 500
        elif n_points <= 2**15:
            max_lut_samplerate = 250
        elif n_points <= 2**16:
            max_lut_samplerate = 125
        else:
            raise ValueOutOfRangeException(
                "Maximum data length is 65535 samples")

        if not mode:
            mode = max_lut_samplerate

        if mode > max_lut_samplerate:
            raise InvalidConfigurationException(
                "Maximum samplerate for {} lookup table coefficients "
                "is {}Msmps.".format(n_points, max_lut_samplerate))

        _str_to_mode = {
            '1000': _ARB_MODE_1000,
            '500': _ARB_MODE_500,
            '250': _ARB_MODE_250,
            '125': _ARB_MODE_125
        }

        mode = str_to_val(_str_to_mode, str(mode), "operating mode")

        self._set_mode(ch, mode, len(data))
        self.commit()

        # picks the stepsize and the steps based in the mode
        steps, stepsize = [(8, 8192), (4, 8192 * 2), (2, 8192 * 4),
                           (1, 8192 * 8)][mode]

        byte_data = bytearray()
        for step in range(steps):
            byte_data += bytearray(b''.join([
                struct.pack('<hh', int(math.ceil((2.0**15 - 1) * d)), 0)
                for d in data
            ]))
            byte_data += bytearray(b'\0' * (stepsize * 4 - (len(data) * 4)))

        # Write the data to AWG memory map
        self._set_mmap_access(True)
        self._moku._send_file_bytes('j',
                                    '',
                                    byte_data,
                                    offset=_ARB_LUT_LENGTH * 8 * 4 * (ch - 1))
        self._set_mmap_access(False)

        # Release the memory map "file" to other resources
        self._moku._fs_finalise('j', '', _ARB_LUT_LENGTH * 8 * 4 * 2)
    def set_sweep(self,
                  f_start=100,
                  f_end=120e6,
                  sweep_points=512,
                  sweep_log=False,
                  averaging_time=1e-3,
                  settling_time=1e-3,
                  averaging_cycles=1,
                  settling_cycles=1):
        """ Set the output sweep parameters

        :type f_start: int; 1 <= f_start <= 120e6 Hz
        :param f_start: Sweep start frequency

        :type f_end: int; 1 <= f_end <= 120e6 Hz
        :param f_end: Sweep end frequency

        :type sweep_points: int; 32 <= sweep_points <= 512
        :param sweep_points: Number of points in the sweep (rounded to nearest
            power of 2).

        :type sweep_log: bool
        :param sweep_log: Enable logarithmic frequency sweep scale.

        :type averaging_time: float; sec
        :param averaging_time: Minimum averaging time per sweep point.

        :type settling_time: float; sec
        :param settling_time: Minimum setting time per sweep point.

        :type averaging_cycles: int; cycles
        :param averaging_cycles: Minimum averaging cycles per sweep point.

        :type settling_cycles: int; cycles
        :param settling_cycles: Minimum settling cycles per sweep point.
        """
        _utils.check_parameter_valid('range', f_start, [1, 120e6],
                                     'sweep start frequency', 'Hz')
        _utils.check_parameter_valid('range', f_end, [1, 120e6],
                                     'sweep end frequency', 'Hz')
        _utils.check_parameter_valid('range', sweep_points, [32, 512],
                                     'sweep points')
        _utils.check_parameter_valid('bool',
                                     sweep_log,
                                     desc='sweep log scale enable')
        _utils.check_parameter_valid('range', averaging_time, [1e-6, 10],
                                     'sweep averaging time', 'sec')
        _utils.check_parameter_valid('range', settling_time, [1e-6, 10],
                                     'sweep settling time', 'sec')
        _utils.check_parameter_valid('range', averaging_cycles, [1, 2**20],
                                     'sweep averaging cycles', 'cycles')
        _utils.check_parameter_valid('range', settling_cycles, [1, 2**20],
                                     'sweep settling cycles', 'cycles')

        # Frequency span check
        if (f_end - f_start) == 0:
            raise ValueOutOfRangeException("Sweep frequency span must be "
                                           "non-zero: f_start/f_end/span "
                                           "- %.2f/%.2f/%.2f." %
                                           (f_start, f_end, f_end - f_start))

        self.sweep_freq_min = f_start
        self.sweep_length = sweep_points
        self.log_en = sweep_log

        self.averaging_time = averaging_time
        self.averaging_cycles = averaging_cycles
        self.settling_time = settling_time

        self.sweep_freq_delta = self._calculate_sweep_delta(
            f_start, f_end, sweep_points, sweep_log)
        self.settling_cycles = settling_cycles
예제 #12
0
    def get_stream_data(self, n=0, timeout=None):
        """ Get any new instrument samples that have arrived on the network.

        This returns a tuple containing two arrays (one per channel) of up
        to 'n' samples of instrument data. If a channel is disabled, the
        corresponding array is empty. If there were less than 'n' samples
        remaining for the session, then the arrays will contain this remaining
        amount of samples.

        :type n: int
        :param n: Number of samples to get off the network. Set this to '0' to
            get all currently available samples, or '-1' to wait on all
            samples of the currently running streaming session to be received.
        :type timeout: float
        :param timeout: Timeout in seconds

        :rtype: tuple
        :returns: ([CH1_DATA], [CH2_DATA])

        :raises NoDataException: if the logging session has stopped
        :raises FrameTimeout: if the timeout expired
        :raises InvalidOperationException: if there is no streaming session
                running
        :raises ValueOutOfRangeException: invalid input parameters
        :raises DataIntegrityException: If the network layer detects dropped
                data
        """
        if timeout and timeout <= 0:
            raise ValueOutOfRangeException(
                "Timeout must be positive or 'None'")
        if n < -1:
            raise ValueOutOfRangeException(
                "Invalid number of samples. Expected (n >= -1).")

        # If no network session exists, can't get samples
        if not self._stream_net_is_running():
            raise InvalidOperationException(
                "No network streaming session is running.")
        if self._no_data:
            log.debug("No more samples to get.")
            return ([], [])
        if type(n) is not int:
            raise TypeError("Sample number 'n' must be an integer")

        # Check how many samples are already processed and waiting to be
        # read out
        processed_samples = self._stream_get_processed_samples()
        if n > 0:
            # Actual number of samples processed already
            num_processed_samples = [len(x) for x in processed_samples]
        else:
            # We don't need to track the number of processed samples
            # if n = [0,1]
            num_processed_samples = [-1, -1]

        # Only "get" samples off the network if we haven't already processed
        # enough to return 'n' for all enabled channels.
        while ((n == -1) or (self.ch1 and ((num_processed_samples[0] <= n) or
                                           (num_processed_samples[0] <= 0)))
               or (self.ch2 and ((num_processed_samples[1] <= n) or
                                 (num_processed_samples[1] <= 0)))):
            try:
                self._stream_receive_samples(timeout)
            except NoDataException:
                log.debug("No more data available for current stream.")
                self._no_data = True

            # Update our list of current processed samples
            processed_samples = self._stream_get_processed_samples()
            if n != -1:
                # Update the number of processed samples if we aren't asking
                # for 'all' of them
                num_processed_samples = [len(x) for x in processed_samples]

            # Check if the streaming session has completed
            if self._no_data:
                break

        active_channels = [self.ch1, self.ch2]
        to_return = min(
            [len(p) for c, p in zip(active_channels, processed_samples) if c])

        if n > 0:
            to_return = min(n, to_return)

        dout_ch1 = processed_samples[0][0:to_return] if self.ch1 else []
        dout_ch2 = processed_samples[1][0:to_return] if self.ch2 else []

        self._stream_clear_processed_samples(to_return)

        return (dout_ch1, dout_ch2)
예제 #13
0
    def set_demodulation(self,
                         mode,
                         frequency=1e6,
                         phase=0,
                         output_amplitude=0.5):
        """
        Configure the demodulation stage.

        The mode is one of:
            - **internal** : for an internally set local oscillator
            - **external** : to directly use an external signal for
                             demodulation (Note: Q is not selectable in this
                             mode)
            - **external_pll** : to use an external signal for demodulation
                                 after running it through an internal PLL.

        .. note::
          When 'external' is used (that is, without a PLL), the Lock-in
          Amplifier doesn't know the frequency and therefore can't form the
          quadrature for full I/Q demodulation. This in turn means it can't
          distinguish I from Q, X from Y, or form R/Theta. This limits the
          choices for signals that can be output on the Main and AUX channels
          to ones not formed from the quadrature signal.

          An exception will be raised if you attempt to set the demodulation
          to 'external' while viewing one of these signals.

        :type mode: string; {'internal', 'external', 'external_pll'}
        :param mode: Demodulation mode

        :type frequency: float; [0, 200e6] Hz
        :param frequency: Internal demodulation signal frequency (ignored for
         all 'external' modes)

        :type phase: float; [0, 360] deg
        :param phase: Internal demodulation signal phase (ignored in 'external'
         mode)

        :type output_amplitude: float; [0.0, 2.0] Vpp
        :param output_amplitude: Output amplitude of the demodulation signal
         when auxillary channel set to output `demod`.

        """
        _utils.check_parameter_valid('range',
                                     frequency,
                                     allowed=[0, 200e6],
                                     desc="demodulation frequency",
                                     units="Hz")
        _utils.check_parameter_valid('range',
                                     phase,
                                     allowed=[0, 360],
                                     desc="demodulation phase",
                                     units="degrees")
        _utils.check_parameter_valid(
            'set', mode, allowed=['internal', 'external', 'external_pll'])

        if mode == 'external' and not (
                self.aux_source in _NON_PLL_ALLOWED_SIGS and
            (self.main_source in _NON_PLL_ALLOWED_SIGS)):
            raise InvalidConfigurationException("Can't use external"
                                                " demodulation source "
                                                "without a PLL with "
                                                "quadrature-related outputs."
                                                "Allowed outputs are " +
                                                (str(_NON_PLL_ALLOWED_SIGS)))

        self.autoacquire = 1
        self.bandwidth = 0
        self.lo_PLL_reset = 0
        self.lo_reacquire = 0

        # Store the desired output amplitude in the case that 'set_outputs'
        # is called with 'demod' for the auxillary channel output. We can't
        # set the register here because it is shared with the local oscillator
        # amplitude. It will be updated on commit.
        self._demod_amp = output_amplitude

        if mode == 'internal':
            self.ext_demod = 0
            self.lo_PLL = 0
            self.frequency_demod = frequency
            self.phase_demod = phase
            self.demod_mode = mode
        elif mode == 'external':
            self.ext_demod = 1
            self.lo_PLL = 0
            self.demod_mode = mode
        elif mode == 'external_pll':
            self.ext_demod = 0
            self.lo_PLL = 1
            self.lo_reacquire = 1
            self.phase_demod = phase
            self.demod_mode = mode
        else:
            # Should not happen
            raise ValueOutOfRangeException(
                'Demodulation mode must be one '
                'of "internal", "external" '
                'or "external_pll", not %s', mode)