Пример #1
0
class FieldSwitchingLockinExperiment(Experiment):
    """ Field Switching Experimen: measure resistance on Keithley while sweeping AMI430 field
    """
    field = FloatParameter(default=0.0, unit="T")
    # measure_current = FloatParameter(default=3e-6, unit="A")
    resistance = OutputConnector(unit="Ohm")

    res_reference = 1e3
    vsource = 10e-3
    mag = AMI430("192.168.5.109")
    # keith = Keithley2400("GPIB0::25::INSTR")
    lock = SR865("USB0::0xB506::0x2000::002638::INSTR")

    def init_instruments(self):
        # Initialize lockin
        self.lock.amp = self.vsource
        self.lock.tc = 3
        self.mag.ramp()
        self.delay = self.lock.measure_delay()
        self.field.assign_method(self.mag.set_field)
        self.field.add_post_push_hook(
            lambda: time.sleep(0.1))  # Field set delay
        time.sleep(self.delay)

    async def run(self):
        """This is run for each step in a sweep."""

        await asyncio.sleep(self.delay)
        await self.resistance.push(self.res_reference /
                                   ((self.lock.amp / self.lock.mag) - 1.0))

    def shutdown_instruments(self):
        self.mag.zero()
        self.lock.amp = 0
Пример #2
0
class FieldSwitchingExperiment(Experiment):
    """ Field Switching Experimen: measure resistance on Keithley while sweeping AMI430 field
    """
    field = FloatParameter(default=0.0, unit="T")
    measure_current = FloatParameter(default=3e-6, unit="A")
    resistance = OutputConnector(unit="Ohm")

    mag = AMI430("192.168.5.109")
    keith = Keithley2400("GPIB0::25::INSTR")

    def init_instruments(self):
        self.keith.triad()
        self.keith.conf_meas_res(NPLC=10, res_range=1e5)
        self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-5)
        self.keith.current = self.measure_current.value

        self.mag.ramp()

        self.measure_current.assign_method(self.keith.set_current)
        self.field.assign_method(self.mag.set_field)
        self.field.add_post_push_hook(
            lambda: time.sleep(0.1))  # Field set delay

    async def run(self):
        """This is run for each step in a sweep."""
        await self.resistance.push(self.keith.resistance)
        logger.debug("Stream has filled {} of {} points".format(
            self.resistance.points_taken, self.resistance.num_points()))
        await asyncio.sleep(0.02)  # Give the filters some time to catch up?

    def shutdown_instruments(self):
        self.keith.current = 0.0e-5
        self.mag.zero()
Пример #3
0
class FieldSwitchingLockinExperiment(Experiment):
    """ Field Switching Experimen: measure resistance on Keithley while sweeping AMI430 field
    """
    field           = FloatParameter(default=0.0, unit="T")
    resistance      = OutputConnector(unit="Ohm")

    # Default values for lockin measurement. These will need to be changed in a notebook to match the MR and switching current of the sample being measured
    res_reference = 1e3
    measure_current = 10e-6
    fdB = 18
    tc = 100e-3
    mag   = AMI430("192.168.5.109")
    lock  = SR865("USB0::0xB506::0x2000::002638::INSTR")

    def init_instruments(self):
        # Initialize lockin
        self.lock.amp = self.res_reference*self.measure_current
        self.lock.tc = self.tc
        self.lock.filter_slope = self.fdB
        self.mag.ramp()
        self.delay = self.lock.measure_delay()
        self.field.assign_method(self.mag.set_field)
        self.field.add_post_push_hook(lambda: time.sleep(0.1)) # Field set delay
        time.sleep(self.delay)

    async def run(self):
        """This is run for each step in a sweep."""

        await asyncio.sleep(self.delay)
        await self.resistance.push(self.res_reference/((self.lock.amp/self.lock.mag)-1.0))

    def shutdown_instruments(self):
        self.mag.zero()
        self.lock.amp = 0
Пример #4
0
class SwitchingExperiment(Experiment):

    # Parameters and outputs
    field = FloatParameter(default=0.0, unit="T")
    pulse_duration = FloatParameter(default=5.0e-9, unit="s")
    pulse_voltage = FloatParameter(default=0.1, unit="V")
    voltage = OutputConnector()

    # Constants (set with attribute access if you want to change these!)
    attempts = 1 << 9
    settle_delay = 100e-6
    measure_current = 3.0e-6
    samps_per_trig = 15
    polarity = 1
    pspl_atten = 4
    min_daq_voltage = -1
    max_daq_voltage = 1
    reset_amplitude = 0.2
    reset_duration = 5.0e-9
    circuit_attenuation = 20.0
    tc = 300e-6

    # Instrument Resources
    mag = AMI430("192.168.5.109")
    lock = SR865("USB0::0xB506::0x2000::002638::INSTR")
    pspl = Picosecond10070A("GPIB0::24::INSTR")
    atten = Attenuator("calibration/RFSA2113SB_HPD_20160901.csv", lock.set_ao2,
                       lock.set_ao3)
    arb = KeysightM8190A("192.168.5.108")

    # keith = Keithley2400("GPIB0::25::INSTR")

    def init_streams(self):
        descrip = DataStreamDescriptor()
        descrip.data_name = 'voltage'
        descrip.add_axis(DataAxis("sample", range(self.samps_per_trig)))
        descrip.add_axis(DataAxis("state", range(2)))
        descrip.add_axis(DataAxis("attempt", range(self.attempts)))
        self.voltage.set_descriptor(descrip)

    def init_instruments(self):
        self.lock.tc = self.tc

        # Setup the Keithley
        # self.keith.triad()
        # self.keith.conf_meas_res(res_range=1e5)
        # self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-5)
        # self.keith.current = self.measure_current
        self.mag.ramp()

        # Setup the AWG
        self.arb.set_output(True, channel=1)
        self.arb.set_output(False, channel=2)
        self.arb.sample_freq = 12.0e9
        self.arb.waveform_output_mode = "WSPEED"
        self.arb.abort()
        self.arb.delete_all_waveforms()
        self.arb.reset_sequence_table()
        self.arb.set_output_route("DC", channel=1)
        self.arb.voltage_amplitude = 1.0
        self.arb.set_marker_level_low(0.0, channel=1, marker_type="sync")
        self.arb.set_marker_level_high(1.5, channel=1, marker_type="sync")
        self.arb.continuous_mode = False
        self.arb.gate_mode = False

        def arb_pulse(amplitude, duration, sample_rate=12e9):
            arb_voltage = arb_voltage_lookup()
            pulse_points = int(duration * sample_rate)
            if pulse_points < 320:
                wf = np.zeros(320)
            else:
                wf = np.zeros(64 * np.ceil(pulse_points / 64.0))
            wf[:pulse_points] = np.sign(amplitude) * arb_voltage(
                abs(amplitude))
            return wf

        reset_wf = arb_pulse(
            -self.polarity * self.reset_amplitude *
            np.power(10.0, self.circuit_attenuation / 20.0),
            self.reset_duration)
        wf_data = KeysightM8190A.create_binary_wf_data(reset_wf)
        rst_segment_id = self.arb.define_waveform(len(wf_data))
        self.arb.upload_waveform(wf_data, rst_segment_id)

        # no_reset_wf = arb_pulse(0.0, 3.0/12e9)
        # wf_data     = KeysightM8190A.create_binary_wf_data(no_reset_wf)
        # no_rst_segment_id  = self.arb.define_waveform(len(wf_data))
        # self.arb.upload_waveform(wf_data, no_rst_segment_id)

        # Picosecond trigger waveform
        pspl_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200),
                                                            samp_mkr=1)
        pspl_trig_segment_id = self.arb.define_waveform(len(pspl_trig_wf))
        self.arb.upload_waveform(pspl_trig_wf, pspl_trig_segment_id)

        # NIDAQ trigger waveform
        nidaq_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200),
                                                             sync_mkr=1)
        nidaq_trig_segment_id = self.arb.define_waveform(len(nidaq_trig_wf))
        self.arb.upload_waveform(nidaq_trig_wf, nidaq_trig_segment_id)

        settle_pts = int(640 * np.ceil(self.settle_delay * 12e9 / 640))

        scenario = Scenario()
        seq = Sequence(sequence_loop_ct=int(self.attempts))
        #First try with reset flipping pulse
        seq.add_waveform(rst_segment_id)
        seq.add_idle(settle_pts, 0.0)
        seq.add_waveform(nidaq_trig_segment_id)
        seq.add_idle(1 << 16, 0.0)  # bonus non-contiguous memory delay
        seq.add_waveform(pspl_trig_segment_id)
        seq.add_idle(settle_pts, 0.0)
        seq.add_waveform(nidaq_trig_segment_id)
        seq.add_idle(1 << 16, 0.0)  # bonus non-contiguous memory delay
        scenario.sequences.append(seq)
        self.arb.upload_scenario(scenario, start_idx=0)
        self.arb.sequence_mode = "SCENARIO"
        self.arb.scenario_advance_mode = "REPEAT"
        self.arb.scenario_start_index = 0
        self.arb.run()

        # Setup the NIDAQ
        self.analog_input = Task()
        self.read = int32()
        self.buf_points = 2 * self.samps_per_trig * self.attempts
        self.analog_input.CreateAIVoltageChan("Dev1/ai0", "", DAQmx_Val_Diff,
                                              self.min_daq_voltage,
                                              self.max_daq_voltage,
                                              DAQmx_Val_Volts, None)
        self.analog_input.CfgSampClkTiming("", 1e6, DAQmx_Val_Rising,
                                           DAQmx_Val_FiniteSamps,
                                           self.samps_per_trig)
        self.analog_input.CfgInputBuffer(self.buf_points)
        self.analog_input.CfgDigEdgeStartTrig("/Dev1/PFI0", DAQmx_Val_Rising)
        self.analog_input.SetStartTrigRetriggerable(1)
        self.analog_input.StartTask()

        # Setup the PSPL
        self.pspl.amplitude = self.polarity * 7.5 * np.power(
            10, (-self.pspl_atten) / 20.0)
        self.pspl.trigger_source = "EXT"
        self.pspl.trigger_level = 0.1
        self.pspl.output = True

        def set_voltage(voltage):
            # Calculate the voltage controller attenuator setting
            vc_atten = abs(20.0 * np.log10(abs(voltage) / 7.5)
                           ) - self.pspl_atten - self.circuit_attenuation
            if vc_atten <= 6.0:
                raise ValueError(
                    "Voltage controlled attenuation under range (6dB).")
            self.atten.set_attenuation(vc_atten)
            time.sleep(0.02)

        # Assign methods
        self.field.assign_method(self.mag.set_field)
        self.pulse_duration.assign_method(self.pspl.set_duration)
        self.pulse_voltage.assign_method(set_voltage)

        # Create hooks for relevant delays
        self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.1))

    async def run(self):
        """This is run for each step in a sweep."""
        self.arb.advance()
        self.arb.trigger()
        buf = np.empty(self.buf_points)
        self.analog_input.ReadAnalogF64(self.buf_points, -1,
                                        DAQmx_Val_GroupByChannel, buf,
                                        self.buf_points, byref(self.read),
                                        None)
        await self.voltage.push(buf)
        # Seemingly we need to give the filters some time to catch up here...
        await asyncio.sleep(0.02)
        logger.debug("Stream has filled {} of {} points".format(
            self.voltage.points_taken, self.voltage.num_points()))

    def shutdown_instruments(self):
        # self.keith.current = 0.0e-5
        # self.mag.zero()
        self.arb.stop()
        self.pspl.output = False
        try:
            self.analog_input.StopTask()
        except Exception as e:
            print(
                "Warning: failed to stop task (this normally happens with no consequences when taking multiple samples per trigger)."
            )
            pass
Пример #5
0
class ResetSearchExperiment(Experiment):

    voltage = OutputConnector()
    field = FloatParameter(default=0, unit="T")
    duration = FloatParameter(default=5e-9, unit="s")

    repeats = 200
    amplitudes = np.arange(-0.01, 0.011, 0.01)  # Reset amplitudes
    samps_per_trig = 5
    settle_delay = 50e-6
    measure_current = 3e-6

    # Instruments
    arb = KeysightM8190A("192.168.5.108")
    mag = AMI430("192.168.5.109")
    keith = Keithley2400("GPIB0::25::INSTR")
    lock = SR865("USB0::0xB506::0x2000::002638::INSTR")

    polarity = -1

    def init_streams(self):
        # Baked in data axes
        descrip = DataStreamDescriptor()
        descrip.data_name = 'voltage'
        descrip.add_axis(DataAxis("sample", range(self.samps_per_trig)))
        descrip.add_axis(DataAxis("amplitude", self.amplitudes))
        descrip.add_axis(DataAxis("repeat", range(self.repeats)))
        self.voltage.set_descriptor(descrip)

    def init_instruments(self):
        # Set up Keithley
        self.keith.triad()
        self.keith.conf_meas_res(res_range=1e6)
        self.keith.conf_src_curr(comp_voltage=0.6, curr_range=1.0e-5)
        self.keith.current = self.measure_current
        self.mag.ramp()

        self.arb.set_output(True, channel=1)
        self.arb.set_output(False, channel=2)
        self.arb.sample_freq = 12.0e9
        self.arb.waveform_output_mode = "WSPEED"
        self.setup_AWG()

        self.analog_input = Task()
        self.read = int32()
        self.buf_points = len(
            self.amplitudes) * self.samps_per_trig * self.repeats
        self.analog_input.CreateAIVoltageChan("Dev1/ai1", "", DAQmx_Val_Diff,
                                              0.0, 0.5, DAQmx_Val_Volts, None)
        self.analog_input.CfgSampClkTiming("", 1e6, DAQmx_Val_Rising,
                                           DAQmx_Val_FiniteSamps,
                                           self.samps_per_trig)
        self.analog_input.CfgInputBuffer(self.buf_points)
        self.analog_input.CfgDigEdgeStartTrig("/Dev1/PFI0", DAQmx_Val_Rising)
        self.analog_input.SetStartTrigRetriggerable(1)
        self.analog_input.StartTask()

        # Assign methods
        self.field.assign_method(self.mag.set_field)
        self.duration.assign_method(self.setup_AWG)

    def setup_AWG(self, *args):
        self.arb.abort()
        self.arb.delete_all_waveforms()
        self.arb.reset_sequence_table()

        self.arb.set_output_route("DC", channel=1)
        self.arb.voltage_amplitude = 1.0

        self.arb.set_marker_level_low(0.0, channel=1, marker_type="sync")
        self.arb.set_marker_level_high(1.5, channel=1, marker_type="sync")

        self.arb.continuous_mode = False
        self.arb.gate_mode = False

        def arb_pulse(amplitude, sample_rate=12e9):
            pulse_points = int(self.duration.value * sample_rate)

            if pulse_points < 320:
                wf = np.zeros(320)
            else:
                wf = np.zeros(64 * int(np.ceil(pulse_points / 64.0)))
            wf[:pulse_points] = amplitude
            return wf

        segment_ids = []
        arb_voltage = arb_voltage_lookup()
        for amp in self.amplitudes:
            waveform = arb_pulse(np.sign(amp) * arb_voltage(abs(amp)))
            wf_data = KeysightM8190A.create_binary_wf_data(waveform)
            segment_id = self.arb.define_waveform(len(wf_data))
            segment_ids.append(segment_id)
            self.arb.upload_waveform(wf_data, segment_id)

        # NIDAQ trigger waveform
        nidaq_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200),
                                                             sync_mkr=1)
        nidaq_trig_segment_id = self.arb.define_waveform(len(nidaq_trig_wf))
        self.arb.upload_waveform(nidaq_trig_wf, nidaq_trig_segment_id)

        settle_pts = int(640 * np.ceil(self.settle_delay * 12e9 / 640))
        start_idxs = [0]

        scenario = Scenario()
        seq = Sequence(sequence_loop_ct=int(self.repeats))
        for si in segment_ids:
            # seq = Sequence(sequence_loop_ct=int(1))
            seq.add_waveform(si)  # Apply switching pulse to the sample
            seq.add_idle(settle_pts, 0.0)  # Wait for the measurement to settle
            seq.add_waveform(
                nidaq_trig_segment_id)  # Trigger the NIDAQ measurement
            seq.add_idle(1 << 14, 0.0)  # bonus non-contiguous memory delay
        scenario.sequences.append(seq)

        self.arb.upload_scenario(scenario, start_idx=start_idxs[-1])
        start_idxs.append(start_idxs[-1] + len(scenario.scpi_strings()))
        # The last entry is eroneous
        start_idxs = start_idxs[:-1]

        self.arb.sequence_mode = "SCENARIO"
        self.arb.scenario_advance_mode = "REPEAT"
        self.arb.stop()
        self.arb.scenario_start_index = 0
        self.arb.run()

    async def run(self):
        # Establish buffers
        buffers = np.empty(self.buf_points)
        self.arb.advance()
        self.arb.trigger()
        self.analog_input.ReadAnalogF64(self.buf_points, -1,
                                        DAQmx_Val_GroupByChannel,
                                        buffers, self.buf_points,
                                        byref(self.read), None)
        logger.debug("Read a buffer of {} points".format(buffers.size))
        await self.voltage.push(buffers)
        # Seemingly we need to give the filters some time to catch up here...
        await asyncio.sleep(0.02)
        logger.debug("Stream has filled {} of {} points".format(
            self.voltage.points_taken, self.voltage.num_points()))

    def shutdown_instruments(self):
        try:
            self.analog_input.StopTask()
        except Exception as e:
            logger.warning("Warning failed to stop task. This is typical.")
            pass
        self.arb.stop()
        self.keith.current = 0.0
        self.mag.disconnect()
Пример #6
0
class SwitchSearchLockinExperiment(Experiment):
    voltage = OutputConnector()

    sample = "CSHE2"
    comment = "Search PSPL Switch Voltage"
    # PARAMETERS: Confirm these before running
    field = FloatParameter(default=0.0, unit="T")
    pulse_voltage  = FloatParameter(default=0, unit="V")
    pulse_duration = FloatParameter(default=5.0e-9, unit="s")
    measure_current = 3e-6

    circuit_attenuation = 20.0
    pspl_base_attenuation = 30.0
    settle_delay = 100e-6

    attempts = 1 << 8 # Number of attemps
    samps_per_trig = 10 # Samples per trigger

    # Instruments
    arb   = KeysightM8190A("192.168.5.108")
    pspl  = Picosecond10070A("GPIB0::24::INSTR")
    mag   = AMI430("192.168.5.109")
    # keith = Keithley2400("GPIB0::25::INSTR")
    lock  = SR865("USB0::0xB506::0x2000::002638::INSTR")
    atten = Attenuator("calibration/RFSA2113SB_HPD_20160901.csv", lock.set_ao2, lock.set_ao3)

    min_daq_voltage = -10
    max_daq_voltage = 10

    def init_instruments(self):
        # ===================
        #    Setup the Lockin
        # ===================
        self.lock.tc = 30e-6
        time.sleep(0.5)

        # self.keith.triad()
        # self.keith.conf_meas_res(res_range=1e5)
        # self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-5)
        # self.keith.current = self.measure_current
        self.mag.ramp()

        # ===================
        #    Setup the AWG
        # ===================

        self.arb.set_output(True, channel=1)
        self.arb.set_output(False, channel=2)
        self.arb.sample_freq = 12.0e9
        self.arb.waveform_output_mode = "WSPEED"
        self.arb.set_output_route("DC", channel=1)
        self.arb.voltage_amplitude = 1.0
        self.arb.set_marker_level_low(0.0, channel=1, marker_type="sync")
        self.arb.set_marker_level_high(1.5, channel=1, marker_type="sync")
        self.arb.continuous_mode = False
        self.arb.gate_mode = False

        # ===================
        #   Setup the PSPL
        # ===================

        self.pspl.amplitude = 7.5*np.power(10, -self.pspl_base_attenuation/20.0)
        self.pspl.trigger_source = "EXT"
        self.pspl.trigger_level = 0.1
        self.pspl.output = True

        self.setup_daq()

        def set_voltage(voltage):
            # Calculate the voltage controller attenuator setting
            self.pspl.amplitude = np.sign(voltage)*7.5*np.power(10, -self.pspl_base_attenuation/20.0)
            vc_atten = abs(20.0 * np.log10(abs(voltage)/7.5)) - self.pspl_base_attenuation - self.circuit_attenuation
            if vc_atten <= 6.0:
                logger.error("Voltage controlled attenuation under range (6dB).")
                raise ValueError("Voltage controlled attenuation under range (6dB).")
            self.atten.set_attenuation(vc_atten)
            time.sleep(0.02)

        # Assign methods
        self.field.assign_method(self.mag.set_field)
        self.pulse_duration.assign_method(self.pspl.set_duration)
        self.pulse_voltage.assign_method(set_voltage)

        # Create hooks for relevant delays
        self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.1))

    def setup_daq(self):
        self.arb.abort()
        self.arb.delete_all_waveforms()
        self.arb.reset_sequence_table()

        # Picosecond trigger waveform
        pspl_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200), samp_mkr=1)
        pspl_trig_segment_id = self.arb.define_waveform(len(pspl_trig_wf))
        self.arb.upload_waveform(pspl_trig_wf, pspl_trig_segment_id)

        # NIDAQ trigger waveform
        nidaq_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200), sync_mkr=1)
        nidaq_trig_segment_id = self.arb.define_waveform(len(nidaq_trig_wf))
        self.arb.upload_waveform(nidaq_trig_wf, nidaq_trig_segment_id)

        settle_pts = int(640*np.ceil(self.settle_delay * 12e9 / 640))
        scenario = Scenario()
        seq = Sequence(sequence_loop_ct=int(self.attempts))
        seq.add_waveform(pspl_trig_segment_id)
        seq.add_idle(settle_pts, 0.0)
        seq.add_waveform(nidaq_trig_segment_id)
        seq.add_idle(1 << 16, 0.0) # bonus non-contiguous memory delay
        scenario.sequences.append(seq)
        self.arb.upload_scenario(scenario, start_idx=0)
        self.arb.sequence_mode = "SCENARIO"
        self.arb.scenario_advance_mode = "REPEAT"
        self.arb.scenario_start_index = 0
        self.arb.run()

        # ===================
        #   Setup the NIDAQ
        # ===================
        self.analog_input = Task()
        self.read = int32()
        self.buf_points = self.samps_per_trig*self.attempts
        self.analog_input.CreateAIVoltageChan("Dev1/ai0", "", DAQmx_Val_Diff,
            self.min_daq_voltage, self.max_daq_voltage, DAQmx_Val_Volts, None)
        self.analog_input.CfgSampClkTiming("", 1e6, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps , self.samps_per_trig)
        self.analog_input.CfgInputBuffer(self.buf_points)
        self.analog_input.CfgDigEdgeStartTrig("/Dev1/PFI0", DAQmx_Val_Rising)
        self.analog_input.SetStartTrigRetriggerable(1)
        self.analog_input.StartTask()

    def init_streams(self):
        # Baked in data axes
        descrip = DataStreamDescriptor()
        descrip.add_axis(DataAxis("sample", range(self.samps_per_trig)))
        descrip.add_axis(DataAxis("attempts", range(self.attempts)))
        self.voltage.set_descriptor(descrip)

    async def run(self):
        self.arb.advance()
        self.arb.trigger()
        buf = np.empty(self.buf_points)
        self.analog_input.ReadAnalogF64(self.buf_points, -1, DAQmx_Val_GroupByChannel,
                                        buf, self.buf_points, byref(self.read), None)
        logger.debug("Read a buffer of {} points".format(buf.size))
        await self.voltage.push(buf)
        # Seemingly we need to give the filters some time to catch up here...
        await asyncio.sleep(0.02)
        logger.debug("Stream has filled {} of {} points".format(self.voltage.points_taken, self.voltage.num_points()))

    def shutdown_instruments(self):
        try:
            self.analog_input.StopTask()
        except Exception as e:
            logger.warning("Warning failed to stop task, which is quite typical (!)")

        self.arb.stop()
        # self.keith.current = 0.0
        # self.mag.zero()
        self.pspl.output = False
class SWERExperiment(Experiment):
    """ Experiment class for Switching probability measurment
    Determine switching probability for V << V0
    with varying V (and durations?)
    """

    field          = FloatParameter(default=0.0, unit="T")
    pulse_duration = FloatParameter(default=1.0e-9, unit="s")
    pulse_voltage  = FloatParameter(default=0.1, unit="V")
    repeats        = IntParameter(default = 1) # Dummy parameter for repeating
    voltage     = OutputConnector()

    attempts        = 1 << 12
    settle_delay    = 100e-6
    measure_current = 3.0e-6
    samps_per_trig  = 5

    polarity        = 1

    min_daq_voltage = 0.0
    max_daq_voltage = 0.4

    reset_amplitude = 0.1
    reset_duration  = 5.0e-9

    mag   = AMI430("192.168.5.109")
    lock  = SR865("USB0::0xB506::0x2000::002638::INSTR")
    # pspl  = Picosecond10070A("GPIB0::24::INSTR")
    arb   = KeysightM8190A("192.168.5.108")
    keith = Keithley2400("GPIB0::25::INSTR")

    def init_streams(self):
        # Baked in data axes
        descrip = DataStreamDescriptor()
        descrip.add_axis(DataAxis("sample", range(self.samps_per_trig)))
        descrip.add_axis(DataAxis("state", range(2)))
        descrip.add_axis(DataAxis("attempt", range(self.attempts)))
        self.voltage.set_descriptor(descrip)

    def init_instruments(self):

        # ===================
        #    Setup the Keithley
        # ===================

        self.keith.triad()
        self.keith.conf_meas_res(res_range=1e5)
        self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-5)
        self.keith.current = self.measure_current
        self.mag.ramp()

        # ===================
        #    Setup the AWG
        # ===================

        self.arb.set_output(True, channel=1)
        self.arb.set_output(False, channel=2)
        self.arb.sample_freq = 12.0e9
        self.arb.waveform_output_mode = "WSPEED"
        self.arb.set_output_route("DC", channel=1)
        self.arb.voltage_amplitude = 1.0
        self.arb.set_marker_level_low(0.0, channel=1, marker_type="sync")
        self.arb.set_marker_level_high(1.5, channel=1, marker_type="sync")
        self.arb.continuous_mode = False
        self.arb.gate_mode = False
        self.setup_arb(self.pulse_voltage.value)

        # ===================
        #   Setup the NIDAQ
        # ===================

        self.analog_input = Task()
        self.read = int32()
        self.buf_points = 2*self.samps_per_trig*self.attempts
        self.analog_input.CreateAIVoltageChan("Dev1/ai1", "", DAQmx_Val_Diff,
            self.min_daq_voltage, self.max_daq_voltage, DAQmx_Val_Volts, None)
        self.analog_input.CfgSampClkTiming("", 1e6, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps , self.samps_per_trig)
        self.analog_input.CfgInputBuffer(self.buf_points)
        self.analog_input.CfgDigEdgeStartTrig("/Dev1/PFI0", DAQmx_Val_Rising)
        self.analog_input.SetStartTrigRetriggerable(1)
        self.analog_input.StartTask()

        # Assign methods
        self.field.assign_method(self.mag.set_field)
        self.pulse_voltage.assign_method(self.setup_arb)

    def setup_arb(self,volt):
        def arb_pulse(amplitude, duration, sample_rate=12e9):
            arb_voltage = arb_voltage_lookup()
            pulse_points = int(duration*sample_rate)
            if pulse_points < 320:
                wf = np.zeros(320)
            else:
                wf = np.zeros(64*np.ceil(pulse_points/64.0))
            wf[:pulse_points] = np.sign(amplitude)*arb_voltage(abs(amplitude))
            return wf

        self.arb.abort()
        self.arb.delete_all_waveforms()
        self.arb.reset_sequence_table()

        # Reset waveform
        reset_wf    = arb_pulse(-self.polarity*self.reset_amplitude, self.reset_duration)
        wf_data     = KeysightM8190A.create_binary_wf_data(reset_wf)
        rst_segment_id  = self.arb.define_waveform(len(wf_data))
        self.arb.upload_waveform(wf_data, rst_segment_id)

        # Switching waveform
        switch_wf    = arb_pulse(self.polarity*volt, self.pulse_duration.value)
        wf_data     = KeysightM8190A.create_binary_wf_data(switch_wf)
        sw_segment_id  = self.arb.define_waveform(len(wf_data))
        self.arb.upload_waveform(wf_data, sw_segment_id)

        # NIDAQ trigger waveform
        nidaq_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200), sync_mkr=1)
        nidaq_trig_segment_id = self.arb.define_waveform(len(nidaq_trig_wf))
        self.arb.upload_waveform(nidaq_trig_wf, nidaq_trig_segment_id)

        settle_pts = int(640*np.ceil(self.settle_delay * 12e9 / 640))

        scenario = Scenario()
        seq = Sequence(sequence_loop_ct=int(self.attempts))
        #First try with reset flipping pulse
        seq.add_waveform(rst_segment_id)
        seq.add_idle(settle_pts, 0.0)
        seq.add_waveform(nidaq_trig_segment_id)
        seq.add_idle(1 << 16, 0.0) # bonus non-contiguous memory delay
        seq.add_waveform(sw_segment_id)
        seq.add_idle(settle_pts, 0.0)
        seq.add_waveform(nidaq_trig_segment_id)
        seq.add_idle(1 << 16, 0.0) # bonus non-contiguous memory delay
        scenario.sequences.append(seq)
        self.arb.upload_scenario(scenario, start_idx=0)
        self.arb.sequence_mode = "SCENARIO"
        self.arb.scenario_advance_mode = "REPEAT"
        self.arb.scenario_start_index = 0
        self.arb.run()

    async def run(self):
        # Keep track of the previous values
        logger.debug("Waiting for filters.")
        await asyncio.sleep(1.0)
        self.arb.advance()
        self.arb.trigger()
        buf = np.empty(self.buf_points)
        self.analog_input.ReadAnalogF64(self.buf_points, -1, DAQmx_Val_GroupByChannel,
                                        buf, self.buf_points, byref(self.read), None)
        await self.voltage.push(buf)
        # Seemingly we need to give the filters some time to catch up here...
        await asyncio.sleep(0.002)
        logger.debug("Stream has filled {} of {} points".format(self.voltage.points_taken, self.voltage.num_points() ))

    def shutdown_instruments(self):
        self.keith.current = 0.0e-5
        # self.mag.zero()
        self.arb.stop()
        try:
            self.analog_input.StopTask()
        except Exception as e:
            print("Warning: failed to stop task (this normally happens with no consequences when taking multiple samples per trigger).")
            pass
class nTronBERExperiment(Experiment):

    # Sample information
    sample = "CSHE"
    comment = "Bit Error Rate with nTron pulses"
    # Parameters
    field = FloatParameter(default=0.0, unit="T")
    nTron_voltage = FloatParameter(default=0.2, unit="V")
    nTron_duration = FloatParameter(default=1e-9, unit="s")
    attempts = IntParameter(default=1 << 10)

    # Constants (set with attribute access if you want to change these!)
    settle_delay = 200e-6
    measure_current = 3.0e-6
    samps_per_trig = 5

    polarity = 1

    min_daq_voltage = 0.0
    max_daq_voltage = 0.4

    reset_amplitude = 0.2
    reset_duration = 5.0e-9

    # Things coming back
    daq_buffer = OutputConnector()

    # Instrument resources
    mag = AMI430("192.168.5.109")
    # lock  = SR865("USB0::0xB506::0x2000::002638::INSTR")
    # pspl  = Picosecond10070A("GPIB0::24::INSTR")
    # atten = Attenuator("calibration/RFSA2113SB_HPD_20160706.csv", lock.set_ao2, lock.set_ao3)
    arb = KeysightM8190A("192.168.5.108")
    keith = Keithley2400("GPIB0::25::INSTR")

    def init_instruments(self):

        # ===================
        #    Setup the Keithley
        # ===================

        self.keith.triad()
        self.keith.conf_meas_res(res_range=1e5)
        self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-5)
        self.keith.current = self.measure_current
        self.mag.ramp()

        # ===================
        #    Setup the AWG
        # ===================

        self.arb.set_output(True, channel=1)
        self.arb.set_output(False, channel=2)
        self.arb.sample_freq = 12.0e9
        self.arb.waveform_output_mode = "WSPEED"
        self.arb.set_output_route("DC", channel=1)
        self.arb.voltage_amplitude = 1.0
        self.arb.set_marker_level_low(0.0, channel=1, marker_type="sync")
        self.arb.set_marker_level_high(1.5, channel=1, marker_type="sync")
        self.arb.continuous_mode = False
        self.arb.gate_mode = False

        self.nTron_control_voltage = nTron_voltage_lookup()
        self.setup_arb(self.nTron_voltage.value)

        # Assign methods
        self.field.assign_method(self.mag.set_field)
        self.nTron_voltage.assign_method(self.setup_arb)

    def setup_arb(self, vpeak):
        self.arb.abort()
        self.arb.delete_all_waveforms()
        self.arb.reset_sequence_table()

        reset_wf = arb_pulse(-self.polarity * self.reset_amplitude,
                             self.reset_duration)
        wf_data = KeysightM8190A.create_binary_wf_data(reset_wf)
        rst_segment_id = self.arb.define_waveform(len(wf_data))
        self.arb.upload_waveform(wf_data, rst_segment_id)

        no_reset_wf = arb_pulse(0.0, 3.0 / 12e9)
        wf_data = KeysightM8190A.create_binary_wf_data(no_reset_wf)
        no_rst_segment_id = self.arb.define_waveform(len(wf_data))
        self.arb.upload_waveform(wf_data, no_rst_segment_id)

        # nTron waveforms
        volt = self.polarity * self.nTron_control_voltage(vpeak)
        logger.debug("Set nTron pulse: {}V -> AWG {}V, {}s".format(
            vpeak, volt, self.nTron_duration.value))
        ntron_wf = ntron_pulse(amplitude=volt,
                               fall_time=self.nTron_duration.value)
        wf_data = KeysightM8190A.create_binary_wf_data(ntron_wf)
        ntron_segment_id = self.arb.define_waveform(len(wf_data))
        self.arb.upload_waveform(wf_data, ntron_segment_id)

        # NIDAQ trigger waveform
        nidaq_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200),
                                                             sync_mkr=1)
        nidaq_trig_segment_id = self.arb.define_waveform(len(nidaq_trig_wf))
        self.arb.upload_waveform(nidaq_trig_wf, nidaq_trig_segment_id)

        settle_pts = int(640 * np.ceil(self.settle_delay * 12e9 / 640))

        scenario = Scenario()
        seq = Sequence(sequence_loop_ct=int(self.attempts.value))
        seq.add_waveform(rst_segment_id)
        seq.add_idle(settle_pts, 0.0)
        seq.add_waveform(nidaq_trig_segment_id)
        seq.add_idle(1 << 16, 0.0)  # bonus non-contiguous memory delay
        # seq.add_waveform(pspl_trig_segment_id)
        seq.add_waveform(ntron_segment_id)
        seq.add_idle(settle_pts, 0.0)
        seq.add_waveform(nidaq_trig_segment_id)
        seq.add_idle(1 << 16, 0.0)  # bonus non-contiguous memory delay
        scenario.sequences.append(seq)
        self.arb.upload_scenario(scenario, start_idx=0)

        self.arb.sequence_mode = "SCENARIO"
        self.arb.scenario_advance_mode = "REPEAT"
        self.arb.scenario_start_index = 0
        self.arb.run()

        # ===================
        #   Setup the NIDAQ
        # ===================

        self.analog_input = Task()
        self.read = int32()
        self.buf_points = 2 * self.samps_per_trig * self.attempts.value
        self.analog_input.CreateAIVoltageChan("Dev1/ai1", "", DAQmx_Val_Diff,
                                              self.min_daq_voltage,
                                              self.max_daq_voltage,
                                              DAQmx_Val_Volts, None)
        self.analog_input.CfgSampClkTiming("", 1e6, DAQmx_Val_Rising,
                                           DAQmx_Val_FiniteSamps,
                                           self.samps_per_trig)
        self.analog_input.CfgInputBuffer(self.buf_points)
        self.analog_input.CfgDigEdgeStartTrig("/Dev1/PFI0", DAQmx_Val_Rising)
        self.analog_input.SetStartTrigRetriggerable(1)
        self.analog_input.StartTask()

    def init_streams(self):
        # Baked in data axes
        descrip = DataStreamDescriptor()
        descrip.add_axis(DataAxis("samples", range(self.samps_per_trig)))
        descrip.add_axis(DataAxis("state", range(2)))
        descrip.add_axis(DataAxis("attempts", range(self.attempts.value)))
        self.daq_buffer.set_descriptor(descrip)

    async def run(self):
        """This is run for each step in a sweep."""
        self.arb.advance()
        self.arb.trigger()
        buf = np.empty(self.buf_points)
        self.analog_input.ReadAnalogF64(self.buf_points, -1,
                                        DAQmx_Val_GroupByChannel, buf,
                                        self.buf_points, byref(self.read),
                                        None)
        await self.daq_buffer.push(buf)
        # Seemingly we need to give the filters some time to catch up here...
        await asyncio.sleep(0.02)
        logger.debug("Stream has filled {} of {} points".format(
            self.daq_buffer.points_taken, self.daq_buffer.num_points()))

    def shutdown_instruments(self):
        self.keith.current = 0.0e-5
        # self.mag.zero()
        self.arb.stop()
        try:
            self.analog_input.StopTask()
        except Exception as e:
            print(
                "Warning: failed to stop task (this normally happens with no consequences when taking multiple samples per trigger)."
            )
            pass
Пример #9
0
class ShortProcessSwitchingExperiment(Experiment):

    # Parameters and outputs
    pulse_duration = FloatParameter(default=5.0e-9, unit="s")
    pulse_voltage  = FloatParameter(default=1, unit="V")
    bias_current   = FloatParameter(default=60e-6, unit="A")
    field          = FloatParameter(default=0.0, unit="T")
    voltage_chan   = OutputConnector()
    #voltage_MTJ    = OutputConnector()
    resistance_MTJ = OutputConnector()

    # Constants (set with attribute access if you want to change these!)
    attempts           = 1 << 4

    # MTJ measurements
    measure_current = 3.0e-6

    # Reference resistances and attenuations
    matching_ref_res = 50
    chan_bias_ref_res = 1e4
    pspl_base_attenuation = 10
    circuit_attenuation = 0

    # Min/Max NIDAQ AI Voltage
    min_daq_voltage = -1
    max_daq_voltage = 1

    # Measurement Sequence timing, clocks in Hz times in seconds
    ai_clock = 0.25e6
    do_clock = 1e5
    run_time = 2e-4
    settle_time = 0.5*run_time
    integration_time = 0.1*run_time
    pspl_delay = 0.25*0.5*run_time

    # Instrument resources, NIDAQ called via PyDAQmx
    arb_cb = Agilent33220A("192.168.5.199") # Channel Bias
    pspl  = Picosecond10070A("GPIB0::24::INSTR") # Gate Pulse
    atten = RFMDAttenuator("calibration/RFSA2113SB_HPD_20160901.csv") # Gate Amplitude control
    lock  = SR865("USB0::0xB506::0x2000::002638::INSTR") # Gate Amplitude control
    keith = Keithley2400("GPIB0::25::INSTR")
    mag   = AMI430("192.168.5.109")

    def init_instruments(self):
        # Channel bias arb
        self.arb_cb.output          = False
        self.arb_cb.load_resistance = (self.chan_bias_ref_res+self.matching_ref_res)
        self.arb_cb.function        = 'Pulse'
        self.arb_cb.pulse_period    = 0.99*self.run_time
        self.arb_cb.pulse_width     = self.run_time - self.settle_time
        self.arb_cb.pulse_edge      = 100e-9
        self.arb_cb.low_voltage     = 0.0
        self.arb_cb.high_voltage    = self.bias_current.value*(self.chan_bias_ref_res+self.matching_ref_res)
        self.arb_cb.polarity        = 1
        self.arb_cb.burst_state     = True
        self.arb_cb.burst_cycles    = 1
        self.arb_cb.trigger_source  = "External"
        self.arb_cb.burst_mode      = "Triggered"
        self.arb_cb.output          = True

        # Setup the Keithley
        self.keith.triad()
        self.keith.conf_meas_res(res_range=1e5)
        self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-5)
        self.keith.current = self.measure_current

        # Setup the magnet
        self.mag.ramp()

        # Setup the NIDAQ tasks
        DAQmxResetDevice("Dev1")
        self.nidaq = CallbackTask(["Dev1/ai0"], asyncio.get_event_loop(), int(self.integration_time*self.ai_clock), self.attempts, [self.voltage_chan],
                                 min_voltage=self.min_daq_voltage, max_voltage=self.max_daq_voltage, ai_clock=self.ai_clock)
        self.nidaq.StartTask()


        # Setup DO Triggers, P1:0 triggers AWG, P1:1 triggers PSPL and DAQ analog input
        self.digital_output = Task()
        data_p0 = np.zeros(int(self.do_clock*self.run_time),dtype=np.uint8)
        data_p0[0]=1

        data_p1 = np.zeros(int(self.do_clock*self.run_time),dtype=np.uint8)
        data_p1[int(self.do_clock*(self.pspl_delay))]=1 # PSPL delay

        data = np.hstack([data_p0,data_p1])

        self.digital_output.CreateDOChan("/Dev1/port0/line0:1","",DAQmx_Val_ChanPerLine)
        self.digital_output.CfgSampClkTiming("",self.do_clock, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, int(data.size/2)*self.attempts)
        self.digital_output.WriteDigitalLines(int(self.do_clock*self.run_time),0,1,DAQmx_Val_GroupByChannel,data,None,None)

        # Setup the PSPL
        self.pspl.amplitude = 7.5*np.power(10, (-self.pspl_base_attenuation)/20.0)
        self.pspl.trigger_source = "EXT"
        self.pspl.trigger_level = 0.5
        self.pspl.output = True

        # Setup PSPL Attenuator Control
        self.atten.set_supply_method(self.lock.set_ao2)
        self.atten.set_control_method(self.lock.set_ao3)

        def set_pulse_voltage(voltage, base_atten=self.pspl_base_attenuation):
            # Calculate the voltage controller attenuator setting
            amplitude = 7.5*np.power(10, -base_atten/20.0)
            vc_atten = abs(20.0 * np.log10(abs(voltage)/7.5)) - base_atten - self.circuit_attenuation

            if vc_atten <= self.atten.minimum_atten():
                base_atten -= 1
                if base_atten < 0:
                    logger.error("Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation.".format(vc_atten))
                    raise ValueError("Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation.".format(vc_atten))
                set_pulse_voltage(voltage, base_atten=base_atten)
                return

            if self.atten.maximum_atten() < vc_atten:
                base_atten += 1
                if base_atten > 80:
                    logger.error("Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation.".format(vc_atten))
                    raise ValueError("Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation.".format(vc_atten))
                set_pulse_voltage(voltage, base_atten=base_atten)
                return

            #print("PSPL Amplitude: {}, Attenuator: {}".format(amplitude,vc_atten))
            self.atten.set_attenuation(vc_atten)
            self.pspl.amplitude = self.arb_cb.polarity*abs(amplitude)
            time.sleep(0.04)

        def set_awg_current(current):

            if 0 <= current:
                self.arb_cb.polarity = 1
                self.arb_cb.low_voltage = 0
                self.arb_cb.high_voltage = current*self.chan_bias_ref_res
            else :
                self.arb_cb.polarity = -1
                self.arb_cb.low_voltage = current*self.chan_bias_ref_res
                self.arb_cb.high_voltage = 0

            self.pspl.amplitude = self.arb_cb.polarity*abs(self.pspl.amplitude)

            time.sleep(0.04)

        # Assign Methods
        self.bias_current.assign_method(set_awg_current)
        self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.1))
        self.pulse_duration.assign_method(self.pspl.set_duration)
        self.pulse_voltage.assign_method(set_pulse_voltage)
        self.field.assign_method(self.mag.set_field)

    def init_streams(self):
        # Baked in data axes
        descrip = DataStreamDescriptor()
        descrip.data_name='voltage'
        descrip.add_axis(DataAxis("sample", range(int(self.integration_time*self.ai_clock))))
        descrip.add_axis(DataAxis("attempt", range(self.attempts)))
        self.voltage_chan.set_descriptor(descrip)

        # descrip = DataStreamDescriptor()
        # descrip.data_name='voltage'
        # descrip.add_axis(DataAxis("sample", range(int(self.integration_time*self.ai_clock))))
        # descrip.add_axis(DataAxis("attempt", range(self.attempts)))
        # self.voltage_MTJ.set_descriptor(descrip)

        descrip = DataStreamDescriptor()
        descrip.data_name='resistance'
        self.resistance_MTJ.set_descriptor(descrip)

    async def run(self):
        self.digital_output.StartTask()
        self.digital_output.WaitUntilTaskDone(2*self.attempts*self.run_time)
        self.digital_output.StopTask()
        await self.resistance_MTJ.push(self.keith.resistance)
        await asyncio.sleep(0.05)
        # print("\t now ", self.nidaq.points_read)
        # logger.debug("Stream has filled {} of {} points".format(self.voltage.points_taken, self.voltage.num_points() ))

    def shutdown_instruments(self):
        self.keith.current = 0.0e-5
        self.mag.zero()
        try:
            for ch in [self.nidaq, self.nidaq_MTJ]:
                ch.StopTask()
                ch.ClearTask()
                self.digital_output.StopTask()
                del ch
        except Exception as e:
            print("Warning: failed to stop task (this normally happens with no consequences when taking multiple samples per trigger).")
            pass
        self.arb_cb.output = False
        self.pspl.output = False
        for name, instr in self._instruments.items():
            instr.disconnect()
Пример #10
0
class ShortProcessSwitchingExperimentReset(Experiment):

    # Parameters and outputs
    pulse_duration = FloatParameter(default=5.0e-9, unit="s")
    pulse_voltage  = FloatParameter(default=1, unit="V")
    bias_current   = FloatParameter(default=60e-6, unit="A")
    field          = FloatParameter(default=0.0, unit="T")
    voltage        = OutputConnector()

    polarity        = 1
    attempts        = 1 << 4 # How many times to try switching per set of conditions
    measure_current = 10.0e-6 # MTJ sense current
    reset_current   = 0.5e-3 # AWG reset current (remember division)
    MTJ_res         = 100e3 # MTJ Resistance

    # Reference resistances and attenuations
    matching_ref_res      = 50
    chan_bias_ref_res     = 1e4
    reset_bias_ref_res    = 5e3
    pspl_base_attenuation = 10
    circuit_attenuation   = 0

    # Reset Pulse          Switching Pulse
    #                         ___/\____
    # \_______/             /         \
    # A          B         C     D
    #            |------------------------------------->
    #             MEAS Begins (ignore middle) MEAS Ends
    #            | Integ. time|              |integ time|

    # Measurement Sequence timing, clocks in Hz times in seconds
    ai_clock          = 0.25e6
    do_clock          = 0.5e6

    trig_interval     = 200e-6  # The repetition rate for switching attempts
    bias_pulse_width  = 40e-6   # This is how long the bias pulse is
    reset_pulse_width = 40e-6   # This is how long the reset pulse is
    meas_duration     = 140e-6  # This is how long the meas is

    reset_trig_time   = 0e-6    # (A) When we trigger the reset pulse
    meas_trig_time    = 50e-6   # (B) When the measurement trigger begins
    switch_trig_time  = 4e-6    # (C) When we trigger the reset pulse
    pspl_trig_time    = 4e-6    # (D) When we trigger the gate pulse
    integration_time  = 20e-6   # How long to measure for at the beginning and end

    # Instrument resources, NIDAQ called via PyDAQmx
    arb_cb    = Agilent33220A("192.168.5.199") # Channel Bias
    arb_reset = Agilent33220A("192.168.5.198") # Channel reset pulses
    pspl      = Picosecond10070A("GPIB0::24::INSTR") # Gate Pulse
    atten     = RFMDAttenuator("calibration/RFSA2113SB_HPD_20160901.csv") # Gate Amplitude control
    lock      = SR865("USB0::0xB506::0x2000::002638::INSTR") # Gate Amplitude control
    keith     = Keithley2400("GPIB0::25::INSTR")
    mag       = AMI430("192.168.5.109")

    def init_instruments(self):
        # Channel bias arb
        self.arb_cb.output          = False
        self.arb_cb.load_resistance = (self.chan_bias_ref_res+self.matching_ref_res)
        self.arb_cb.function        = 'Pulse'
        self.arb_cb.pulse_period    = 0.99*self.trig_interval
        self.arb_cb.pulse_width     = self.bias_pulse_width
        self.arb_cb.pulse_edge      = 100e-9
        self.arb_cb.low_voltage     = 0.0
        self.arb_cb.high_voltage    = self.bias_current.value*(self.chan_bias_ref_res+self.matching_ref_res)
        self.arb_cb.polarity        = self.polarity
        self.arb_cb.burst_state     = True
        self.arb_cb.burst_cycles    = 1
        self.arb_cb.trigger_source  = "External"
        self.arb_cb.burst_mode      = "Triggered"
        self.arb_cb.output          = True

        # MTJ reset arb
        self.arb_reset.output          = False
        self.arb_reset.load_resistance = (self.reset_bias_ref_res+self.matching_ref_res)
        self.arb_reset.function        = 'Pulse'
        self.arb_reset.pulse_period    = 0.99*self.trig_interval
        self.arb_reset.pulse_width     = self.reset_pulse_width
        self.arb_reset.pulse_edge      = 100e-9
        self.arb_reset.low_voltage     = 0.0
        self.arb_reset.high_voltage    = self.reset_current*(self.reset_bias_ref_res+self.matching_ref_res)
        self.arb_reset.polarity        = -self.polarity
        self.arb_reset.burst_state     = True
        self.arb_reset.burst_cycles    = 1
        self.arb_reset.trigger_source  = "External"
        self.arb_reset.burst_mode      = "Triggered"
        self.arb_reset.output          = True

        # Setup the Keithley
        self.keith.triad()
        self.keith.conf_meas_res(res_range=1e5)
        self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-4)
        self.keith.current = self.measure_current

        # Setup the magnet
        self.mag.ramp()

        # Setup the NIDAQ tasks
        DAQmxResetDevice("Dev1")
        self.nidaq = CallbackTask(["Dev1/ai2"], asyncio.get_event_loop(), int(self.meas_duration*self.ai_clock), self.attempts, [self.voltage],
                                 trigger="/Dev1/PFI12", min_voltage=-self.MTJ_res*self.measure_current, max_voltage=self.MTJ_res*self.measure_current,
                                 ai_clock=self.ai_clock)
        self.nidaq.StartTask()

        self.digital_output = Task()
        do_reset = np.zeros(int(self.do_clock*self.trig_interval),dtype=np.uint8)
        do_reset[int(self.do_clock*self.reset_trig_time)]=1
        do_bias = np.zeros(int(self.do_clock*self.trig_interval),dtype=np.uint8)
        do_bias[int(self.do_clock*self.switch_trig_time)]=1
        do_pspl = np.zeros(int(self.do_clock*self.trig_interval),dtype=np.uint8)
        do_pspl[int(self.do_clock*self.pspl_trig_time)]=1
        do_meas = np.zeros(int(self.do_clock*self.trig_interval),dtype=np.uint8)
        do_meas[int(self.do_clock*self.meas_trig_time)]=1
        data = np.hstack([do_reset, do_bias, do_pspl, do_meas])

        self.digital_output.CreateDOChan("/Dev1/port0/line0:3","",DAQmx_Val_ChanPerLine)
        self.digital_output.CfgSampClkTiming("",self.do_clock, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, int(data.size/4)*self.attempts)
        self.digital_output.WriteDigitalLines(int(self.do_clock*self.trig_interval),0,1,DAQmx_Val_GroupByChannel,data,None,None)

        # Setup the PSPL
        self.pspl.amplitude = 7.5*np.power(10, (-self.pspl_base_attenuation)/20.0)
        self.pspl.trigger_source = "EXT"
        self.pspl.trigger_level = 0.5
        self.pspl.output = True

        # Setup PSPL Attenuator Control
        self.atten.set_supply_method(self.lock.set_ao2)
        self.atten.set_control_method(self.lock.set_ao3)

        def set_pulse_voltage(voltage, base_atten=self.pspl_base_attenuation):
            # Calculate the voltage controller attenuator setting
            amplitude = 7.5*np.power(10, -base_atten/20.0)
            vc_atten = abs(20.0 * np.log10(abs(voltage)/7.5)) - base_atten - self.circuit_attenuation

            if vc_atten <= self.atten.minimum_atten():
                base_atten -= 1
                if base_atten < 0:
                    logger.error("Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation.".format(vc_atten))
                    raise ValueError("Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation.".format(vc_atten))
                set_pulse_voltage(voltage, base_atten=base_atten)
                return

            if self.atten.maximum_atten() < vc_atten:
                base_atten += 1
                if base_atten > 80:
                    logger.error("Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation.".format(vc_atten))
                    raise ValueError("Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation.".format(vc_atten))
                set_pulse_voltage(voltage, base_atten=base_atten)
                return

            #print("PSPL Amplitude: {}, Attenuator: {}".format(amplitude,vc_atten))
            self.atten.set_attenuation(vc_atten)
            self.pspl.amplitude = self.arb_cb.polarity*abs(amplitude)
            time.sleep(0.04)

        def set_awg_current(current):

            if 0 <= current:
                self.arb_cb.polarity = 1
                self.arb_cb.low_voltage = 0
                self.arb_cb.high_voltage = current*self.chan_bias_ref_res
            else :
                self.arb_cb.polarity = -1
                self.arb_cb.low_voltage = current*self.chan_bias_ref_res
                self.arb_cb.high_voltage = 0

            self.pspl.amplitude = self.arb_cb.polarity*abs(self.pspl.amplitude)

            time.sleep(0.04)

        # Assign Methods
        self.bias_current.assign_method(set_awg_current)
        self.pulse_duration.assign_method(self.pspl.set_duration)
        self.pulse_voltage.assign_method(set_pulse_voltage)
        self.field.assign_method(self.mag.set_field)

        self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.05))
        self.pulse_voltage.add_post_push_hook(lambda: time.sleep(0.05))
        self.bias_current.add_post_push_hook(lambda: time.sleep(0.05))

    def init_streams(self):
        descrip = DataStreamDescriptor()
        descrip.data_name='voltage'
        descrip.add_axis(DataAxis("sample", range(int(self.meas_duration*self.ai_clock))))
        descrip.add_axis(DataAxis("attempt", range(self.attempts)))
        self.voltage.set_descriptor(descrip)

    async def run(self):
        self.digital_output.StartTask()
        self.digital_output.WaitUntilTaskDone(2*self.attempts*self.trig_interval)
        self.digital_output.StopTask()
        await asyncio.sleep(0.05)

    def shutdown_instruments(self):
        self.keith.current = 0.0e-5
        self.keith.triad(down=True)
        self.mag.zero()
        try:
            for ch in [self.nidaq, self.nidaq_MTJ]:
                ch.StopTask()
                ch.ClearTask()
                self.digital_output.StopTask()
                del ch
        except Exception as e:
            print("Warning: failed to stop task (this normally happens with no consequences when taking multiple samples per trigger).")
            pass
        self.arb_cb.output = False
        self.pspl.output = False
        for name, instr in self._instruments.items():
            instr.disconnect()
class SwitchSearchLockinExperiment(Experiment):
    voltage = OutputConnector()

    sample = "CSHE2"
    comment = "Search PSPL Switch Voltage"

    field = FloatParameter(default=0.0, unit="T")
    pulse_voltage = FloatParameter(default=0, unit="V")
    pulse_duration = FloatParameter(default=5.0e-9, unit="s")

    # Default values for lockin measurement. These will need to be changed in a notebook to match the MR and switching current of the sample being measured
    res_reference = 1e3
    sample_resistance = 50
    measure_current = 10e-6
    fdB = 18
    tc = 100e-3

    circuit_attenuation = 20.0
    pspl_base_attenuation = 30.0

    attempts = 1 << 8  # Number of attemps
    samps_per_trig = 10  # Samples per trigger

    # Instruments
    arb = KeysightM8190A("192.168.5.108")
    pspl = Picosecond10070A("GPIB0::24::INSTR")
    mag = AMI430("192.168.5.109")
    lock = SR865("USB0::0xB506::0x2000::002638::INSTR")
    atten = RFMDAttenuator("calibration/RFSA2113SB_HPD_20160901.csv")

    min_daq_voltage = 0
    max_daq_voltage = 10

    def init_instruments(self):
        # ===================
        #    Setup the Lockin
        # ===================
        self.lock.tc = self.tc
        self.lock.filter_slope = self.fdB
        self.lock.amp = (self.res_reference +
                         self.sample_resistance) * self.measure_current
        sense_vals = np.array(self.lock.SENSITIVITY_VALUES)
        self.lock.sensitivity = sense_vals[np.argmin(
            np.absolute(sense_vals - 2 * self.sample_resistance *
                        self.measure_current * np.ones(sense_vals.size)))]
        time.sleep(20 * self.lock.measure_delay())

        # Rescale lockin analogue output for NIDAQ
        self.lock.r_offset_enable = True
        self.lock.r_expand = 100
        self.lock.auto_offset("R")
        self.lock.r_offset = 0.99 * self.lock.r_offset
        #self.lock.r_offset = 100 * ((self.sample_resistance*self.measure_current/self.lock.sensitivity) - (0.05/self.lock.r_expand))
        time.sleep(20 * self.lock.measure_delay())

        # Ramp magnet to set point
        self.mag.ramp()

        #Set attenuator control methods
        self.atten.set_supply_method(self.lock.set_ao2)
        self.atten.set_control_method(self.lock.set_ao3)

        # ===================
        #    Setup the AWG
        # ===================

        self.arb.set_output(True, channel=1)
        self.arb.set_output(False, channel=2)
        self.arb.sample_freq = 12.0e9
        self.arb.waveform_output_mode = "WSPEED"
        self.arb.set_output_route("DC", channel=1)
        self.arb.voltage_amplitude = 1.0
        self.arb.set_marker_level_low(0.0, channel=1, marker_type="sync")
        self.arb.set_marker_level_high(1.5, channel=1, marker_type="sync")
        self.arb.continuous_mode = False
        self.arb.gate_mode = False

        # ===================
        #   Setup the PSPL
        # ===================
        # PSPL Max amplitude is 7.5 V
        self.pspl.amplitude = 7.5 * np.power(
            10, -self.pspl_base_attenuation / 20.0)
        self.pspl.trigger_source = "EXT"
        self.pspl.trigger_level = 0.1
        self.pspl.output = True

        self.setup_daq()

        def set_voltage(voltage):
            # Calculate the voltage controller attenuator setting
            self.pspl.amplitude = np.sign(voltage) * 7.5 * np.power(
                10, -self.pspl_base_attenuation / 20.0)
            vc_atten = abs(
                20.0 * np.log10(abs(voltage) / 7.5)
            ) - self.pspl_base_attenuation - self.circuit_attenuation

            if vc_atten <= self.atten.minimum_atten():
                logger.error(
                    "Voltage controlled attenuation {} under range.".format(
                        vc_atten))
                raise ValueError(
                    "Voltage controlled attenuation {} under range.".format(
                        vc_atten))

            if self.atten.maximum_atten() < vc_atten:
                logger.error(
                    "Voltage controlled attenuation {} over range.".format(
                        vc_atten))
                raise ValueError(
                    "Voltage controlled attenuation {} over range.".format(
                        vc_atten))

            self.atten.set_attenuation(vc_atten)
            time.sleep(0.02)

        # Assign methods
        self.field.assign_method(self.mag.set_field)
        self.pulse_duration.assign_method(self.pspl.set_duration)
        self.pulse_voltage.assign_method(set_voltage)

        # Create hooks for relevant delays
        self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.1))

    def setup_daq(self):
        self.arb.abort()
        self.arb.delete_all_waveforms()
        self.arb.reset_sequence_table()

        # Picosecond trigger waveform
        pspl_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200),
                                                            samp_mkr=1)
        pspl_trig_segment_id = self.arb.define_waveform(len(pspl_trig_wf))
        self.arb.upload_waveform(pspl_trig_wf, pspl_trig_segment_id)

        # NIDAQ trigger waveform
        nidaq_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200),
                                                             sync_mkr=1)
        nidaq_trig_segment_id = self.arb.define_waveform(len(nidaq_trig_wf))
        self.arb.upload_waveform(nidaq_trig_wf, nidaq_trig_segment_id)

        settle_pts = int(640 * np.ceil(self.lock.measure_delay() * 12e9 / 640))
        scenario = Scenario()
        seq = Sequence(sequence_loop_ct=int(self.attempts))
        seq.add_waveform(pspl_trig_segment_id)
        seq.add_idle(settle_pts, 0.0)
        seq.add_waveform(nidaq_trig_segment_id)
        seq.add_idle(1 << 16, 0.0)  # bonus non-contiguous memory delay
        scenario.sequences.append(seq)
        self.arb.upload_scenario(scenario, start_idx=0)
        self.arb.sequence_mode = "SCENARIO"
        self.arb.scenario_advance_mode = "REPEAT"
        self.arb.scenario_start_index = 0
        self.arb.run()

        # ===================
        #   Setup the NIDAQ
        # ===================
        self.analog_input = Task()
        self.read = int32()
        self.buf_points = self.samps_per_trig * self.attempts
        self.analog_input.CreateAIVoltageChan("Dev1/ai0", "", DAQmx_Val_Diff,
                                              self.min_daq_voltage,
                                              self.max_daq_voltage,
                                              DAQmx_Val_Volts, None)
        self.analog_input.CfgSampClkTiming("", 1e6, DAQmx_Val_Rising,
                                           DAQmx_Val_FiniteSamps,
                                           self.samps_per_trig)
        self.analog_input.CfgInputBuffer(self.buf_points)
        self.analog_input.CfgDigEdgeStartTrig("/Dev1/PFI0", DAQmx_Val_Rising)
        self.analog_input.SetStartTrigRetriggerable(1)
        self.analog_input.StartTask()

    def init_streams(self):
        # Baked in data axes
        descrip = DataStreamDescriptor()
        descrip.add_axis(DataAxis("sample", range(self.samps_per_trig)))
        descrip.add_axis(DataAxis("attempts", range(self.attempts)))
        self.voltage.set_descriptor(descrip)

    async def run(self):
        self.arb.advance()
        self.arb.trigger()
        buf = np.empty(self.buf_points)
        self.analog_input.ReadAnalogF64(self.buf_points, -1,
                                        DAQmx_Val_GroupByChannel, buf,
                                        self.buf_points, byref(self.read),
                                        None)
        logger.debug("Read a buffer of {} points".format(buf.size))
        await self.voltage.push(buf)
        # Seemingly we need to give the filters some time to catch up here...
        await asyncio.sleep(0.02)
        logger.debug("Stream has filled {} of {} points".format(
            self.voltage.points_taken, self.voltage.num_points()))

    def shutdown_instruments(self):
        try:
            self.analog_input.StopTask()
        except Exception as e:
            logger.warning(
                "Warning failed to stop task, which is quite typical (!)")

        self.arb.stop()
        self.pspl.output = False
        self.lock.amp = 0