예제 #1
0
    def reset_phase(self, ch):
        """ resets the channels phase accumulator to zero

        :type ch: int; {1,2}
        :param ch: Channel on which the reset is performed

        :raises ValueError: if the channel number is invalid
        """
        valid('set', ch, [1, 2], 'output channel')

        if ch == 1:
            self.phase_rst1 = True
        elif ch == 2:
            self.phase_rst2 = True
예제 #2
0
    def set_waveform_trigger_output(self,
                                    ch,
                                    trig_en=True,
                                    single=False,
                                    duration=0,
                                    hold_last=False):
        """ Enables triggered output mode on the specified channel and
        configures 'how' to output the set waveform on a trigger event.

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

        :type trig_en: bool;
        :param trig_en: Enables triggering mode on the specified output channel

        :type single: bool;
        :param single: Enables single mode. Outputs a single waveform
        (vs continuous) per trigger event.

        :type duration: float; [0.0, 1e11] seconds
        :param duration: Total time that the triggered output should be
        generated (leave 0 for continuous).
            Note the duration resolution is 8ns.

        :type hold_last: bool
        :param hold_last: Hold the last value of the waveform for the duration
        of the triggered output.
        """
        # Convert the input parameter strings to bit-value mappings
        valid('set', ch, [1, 2], 'channel')
        valid('bool', trig_en, 'trigger enable')
        valid('bool', single, 'single trigger enable')
        valid('range', duration, [0, 1e11], 'duration', 'seconds')
        valid('bool', hold_last, 'hold_last')

        sweep_channels = [self._sweep1, self._sweep2]

        sweep_channels[ch - 1].wait_for_trig = trig_en
        sweep_channels[ch - 1].waveform = \
            _ARB_TRIG_TYPE_SINGLE if single else _ARB_TRIG_TYPE_CONT
        sweep_channels[ch - 1].hold_last = hold_last

        if single and not duration:
            # Duration must be set to ~equal the waveform period otherwise we
            # can never retrigger
            sweep_channels[ch - 1].duration = \
                _ARB_SMPL_RATE / 8.0 / self.get_frequency(ch)
        else:
            sweep_channels[ch - 1].duration = int(round(duration * 1.0e9 / 8))
예제 #3
0
    def get_frequency(self, ch):
        """ Returns the frequency of the output waveform on the selected
        channel.

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

        :raises ValueError: if the channel number is invalid
        """
        valid('set', ch, [1, 2], 'output channel')

        if ch == 1:
            return (float(self._sweep1.step) /
                    self._sweep1.stop) * _ARB_SMPL_RATE
        if ch == 2:
            return (float(self._sweep2.step) /
                    self._sweep2.stop) * _ARB_SMPL_RATE
예제 #4
0
    def enable_output(self, ch=None, en=True):
        """ Enable or disable the ArbitraryWaveGen output(s).

        If *ch* is None (the default), both channels will be acted upon,
        otherwise just the one specified by the argument.

        :type ch: int; {1,2} or None
        :param ch: Output channel, or both.

        :type en: bool
        :param en: Enable the specified output channel(s).

        :raises ValueError: Invalid parameters
        """
        valid('set', ch, [1, 2], 'output channel', allow_none=True)
        valid('bool', en, 'output enable')
        if not ch or ch == 1:
            self.enable1 = en
        if not ch or ch == 2:
            self.enable2 = en
예제 #5
0
    def _set_mode(self, ch, mode, length):
        if mode is _ARB_MODE_1000:
            valid('range', length, [1, 2**13], desc='length for lookup table')
        if mode is _ARB_MODE_500:
            valid('range', length, [1, 2**14], desc='length for lookup table')
        if mode is _ARB_MODE_250:
            valid('range', length, [1, 2**15], desc='length for lookup table')
        if mode is _ARB_MODE_125:
            valid('range', length, [1, 2**16], desc='length for lookup table')

        if ch == 1:
            self.mode1 = mode
            self.lut_length1 = length - 1
        elif ch == 2:
            self.mode2 = mode
            self.lut_length2 = length - 1
예제 #6
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
예제 #7
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
예제 #8
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)