diff --git a/qcodes/instrument_drivers/keysight/M3201A.py b/qcodes/instrument_drivers/keysight/M3201A.py new file mode 100644 index 000000000000..ee6ba1804117 --- /dev/null +++ b/qcodes/instrument_drivers/keysight/M3201A.py @@ -0,0 +1,19 @@ +# from qcodes import validators as validator +# from functools import partial +# +# from .SD_common.SD_Module import * +from .SD_common.SD_AWG import * + + +class Keysight_M3201A(SD_AWG): + """ + This is the qcodes driver for the Keysight M3201A AWG PXIe card + + Args: + name (str): name for this instrument, passed to the base instrument + chassis (int): chassis number where the device is located + slot (int): slot number where the device is plugged in + """ + + def __init__(self, name, chassis=1, slot=7, **kwargs): + super().__init__(name, chassis, slot, channels=4, triggers=8, **kwargs) diff --git a/qcodes/instrument_drivers/keysight/M3300A.py b/qcodes/instrument_drivers/keysight/M3300A.py new file mode 100644 index 000000000000..81ab3f946a5a --- /dev/null +++ b/qcodes/instrument_drivers/keysight/M3300A.py @@ -0,0 +1,43 @@ +################################################################################### +# # +# Driver file for M3300A # +# # +################################################################################### +# # +# Written by: Mark Johnson # +# Also see: http://www.keysight.com/en/pd-2747490-pn-M3300A # +# # +################################################################################### + +try: + from .SD_common.SD_AWG import SD_AWG + from .SD_common.SD_DIG import SD_DIG +except ImportError: + raise ImportError('To use the M3300A driver, install the keysight module') + +class M3300A_AWG(SD_AWG): + """ Driver for the AWG of the Keysight M3300A card. + + Args: + name (str) : name for this instrument, passed to the base instrument + chassis (int) : chassis number where the device is located + slot (int) : slot number where the device is plugged in + Example: + AWG = AWG('M3300A') + """ + def __init__(self, name, chassis=1, slot=8, **kwargs): + super().__init__(name, chassis=1, slot=8, channels=4, triggers=8, **kwargs) + +class M3300A_DIG(SD_DIG): + """ Driver for the digitizer of the keysight M3300A card. + + Args: + name (str) : name for this instrument, passed to the base instrument + chassis (int) : chassis number where the device is located + slot (int) : slot number where the device is plugged in + + Example: + DIG = DIG('M3300A') + """ + def __init__(self, name, chassis=1, slot=8, **kwargs): + super().__init__(name, chassis, slot, channels=8, triggers=8, **kwargs) diff --git a/qcodes/instrument_drivers/keysight/SD_common/SD_AWG.py b/qcodes/instrument_drivers/keysight/SD_common/SD_AWG.py new file mode 100644 index 000000000000..47e50ccc2c1c --- /dev/null +++ b/qcodes/instrument_drivers/keysight/SD_common/SD_AWG.py @@ -0,0 +1,722 @@ +from qcodes import validators as validator +from functools import partial + +from .SD_Module import * + + +class SD_AWG(SD_Module): + """ + This is the general SD_AWG driver class that implements shared parameters and functionality among all PXIe-based + AWG cards by Keysight. (series M32xxA and M33xxA) + + This driver was written to be inherited from by a specific AWG card driver (e.g. M3201A). + + This driver was written with the M3201A card in mind. + + This driver makes use of the Python library provided by Keysight as part of the SD1 Software package (v.2.01.00). + """ + + def __init__(self, name, chassis, slot, channels, triggers, **kwargs): + super().__init__(name, chassis, slot, **kwargs) + + # Create instance of keysight SD_AOU class + self.awg = keysightSD1.SD_AOU() + + # Create an instance of keysight SD_Wave class + self.wave = keysightSD1.SD_Wave() + + # store card-specifics + self.channels = channels + self.triggers = triggers + + # Open the device, using the specified chassis and slot number + awg_name = self.awg.getProductNameBySlot(chassis, slot) + if isinstance(awg_name, str): + result_code = self.awg.openWithSlot(awg_name, chassis, slot) + if result_code <= 0: + raise Exception('Could not open SD_AWG ' + 'error code {}'.format(result_code)) + else: + raise Exception('No SD_AWG found at ' + 'chassis {}, slot {}'.format(chassis, slot)) + + self.add_parameter('trigger_io', + label='trigger io', + get_cmd=self.get_trigger_io, + set_cmd=self.set_trigger_io, + docstring='The trigger input value, 0 (OFF) or 1 (ON)', + vals=validator.Enum(0, 1)) + self.add_parameter('clock_frequency', + label='clock frequency', + unit='Hz', + get_cmd=self.get_clock_frequency, + set_cmd=self.set_clock_frequency, + docstring='The real hardware clock frequency in Hz', + vals=validator.Numbers(100e6, 500e6)) + self.add_parameter('clock_sync_frequency', + label='clock sync frequency', + unit='Hz', + get_cmd=self.get_clock_sync_frequency, + docstring='The frequency of the internal CLKsync in Hz') + + for i in range(triggers): + self.add_parameter('pxi_trigger_number_{}'.format(i), + label='pxi trigger number {}'.format(i), + get_cmd=partial(self.get_pxi_trigger, pxi_trigger=(4000 + i)), + set_cmd=partial(self.set_pxi_trigger, pxi_trigger=(4000 + i)), + docstring='The digital value of pxi trigger no. {}, 0 (ON) of 1 (OFF)'.format(i), + vals=validator.Enum(0, 1)) + + for i in range(channels): + self.add_parameter('frequency_channel_{}'.format(i), + label='frequency channel {}'.format(i), + unit='Hz', + set_cmd=partial(self.set_channel_frequency, channel_number=i), + docstring='The frequency of channel {}'.format(i), + vals=validator.Numbers(0, 200e6)) + self.add_parameter('phase_channel_{}'.format(i), + label='phase channel {}'.format(i), + unit='deg', + set_cmd=partial(self.set_channel_phase, channel_number=i), + docstring='The phase of channel {}'.format(i), + vals=validator.Numbers(0, 360)) + # TODO: validate the setting of amplitude and offset at the same time (-1.5 self.n_channels): + raise ValueError("The specified channel {ch} exceeds the number of channels ({n})".format(ch=channel, n=self.n_channels)) + self.__trigger_mode[channel] = mode + value = self.SD_AIN.channelTriggerConfig(channel, self.__analog_trigger_mask[channel], + self.__trigger_threshold[channel]) + value_name = 'set_trigger_mode {}'.format(mode) + return result_parser(value, value_name, verbose) + + def get_trigger_mode(self, channel, verbose=False): + """ Returns the current trigger mode + + Args: + channel (int) : the input channel you are observing + """ + return self.__trigger_mode[channel] + + def set_trigger_threshold(self, threshold, channel, verbose=False): + """ Sets the current trigger threshold, in the range of -3V and 3V + + Args: + channel (int) : the input channel you are configuring + threshold (float) : the value in volts for the trigger threshold + """ + if (channel > self.n_channels): + raise ValueError("The specified channel {ch} exceeds the number of channels ({n})".format(ch=channel, n=self.n_channels)) + if (threshold > 3 or threshold < -3): + raise ValueError("The specified threshold {thresh} V does not exist.".format(thresh=threshold)) + self.__trigger_threshold[channel] = threshold + value = self.SD_AIN.channelTriggerConfig(channel, self.__analog_trigger_mask[channel], + self.__trigger_threshold[channel]) + value_name = 'set_trigger_threshold {}'.format(threshold) + return result_parser(value, value_name, verbose) + + def get_trigger_threshold(channel, verbose=False): + """ Returns the current trigger threshold + + Args: + channel (int) : the input channel you are observing + """ + return self.__trigger_threshold[channel] + + # DAQConfig + def set_points_per_cycle(self, n_points, channel, verbose=False): + """ Sets the number of points to be collected per trigger + + Args: + channel (int) : the input channel you are configuring + """ + self.__points_per_cycle[channel] = n_points + value = self.SD_AIN.DAQconfig(channel, self.__points_per_cycle[channel], + self.__n_cycles[channel], + self.__trigger_delay[channel], + self.__trigger_mode[channel]) + value_name = 'set_points_per_cycle'.format(n_points) + return result_parser(value, value_name, verbose) + + def set_n_cycles(self, n_cycles, channel, verbose=False): + """ Sets the number of trigger cycles to collect data for + + Args: + channel (int) : the input channel you are configuring + n_cycles (int) : the number of triggers to collect data from + + """ + self.__n_cycles[channel] = n_cycles + value = self.SD_AIN.DAQconfig(channel, self.__points_per_cycle[channel], + self.__n_cycles[channel], + self.__trigger_delay[channel], + self.__trigger_mode[channel]) + value_name = 'set_n_cycles {}'.format(n_cycles) + return result_parser(value, value_name, verbose) + + def set_DAQ_trigger_delay(self, delay, channel, verbose=False): + """ Sets the trigger delay for the specified trigger source + + Args: + channel (int) : the input channel you are configuring + delay (int) : the delay in unknown units + """ + self.__trigger_delay[channel] = delay + value = self.SD_AIN.DAQconfig(channel, self.__points_per_cycle[channel], + self.__n_cycles[channel], + self.__trigger_delay[channel], + self.__trigger_mode[channel]) + value_name = 'set_DAQ_trigger_delay {}'.format(delay) + return result_parser(value, value_name, verbose) + + def set_DAQ_trigger_mode(self, mode, channel, verbose=False): + """ Sets the trigger mode when using an external trigger + + Args: + channel (int) : the input channel you are configuring + mode (int) : the trigger mode you are using + """ + self.__trigger_mode[channel] = mode + value = self.SD_AIN.DAQconfig(channel, self.__points_per_cycle[channel], + self.__n_cycles[channel], + self.__trigger_delay[channel], + self.__trigger_mode[channel]) + value_name = 'set_DAQ_trigger_mode {}'.format(mode) + return result_parser(value, value_name, verbose) + + # DAQtriggerConfig + def set_digital_trigger_mode(self, mode, channel, verbose=False): + """ + + Args: + channel (int) : the input channel you are configuring + mode (int) : the trigger mode you are using + """ + self.__digital_trigger_mode[channel] = mode + value = self.SD_AIN.DAQtriggerConfig(channel, self.__digital_trigger_mode[channel], + self.__digital_trigger_source[channel], + self.__analog_trigger_mask[channel]) + value_name = 'set_digital_trigger_mode {}'.format(mode) + return result_parser(value, value_name, verbose) + + def set_digital_trigger_source(self, source, channel, verbose=False): + """ + + Args: + channel (int) : the input channel you are configuring + source (int) : the trigger source you are using + """ + self.__digital_trigger_source[channel] = source + value = self.SD_AIN.DAQtriggerConfig(channel, self.__digital_trigger_mode[channel], + self.__digital_trigger_source[channel], + self.__analog_trigger_mask[channel]) + value_name = 'set_digital_trigger_source {}'.format(source) + return result_parser(value, value_name, verbose) + + def set_analog_trigger_mask(self, mask, channel, verbose=False): + """ + + Args: + channel (int) : the input channel you are configuring + mask (int) : the trigger mask you are using + """ + self.__analog_trigger_mask[channel] = mask + value = self.SD_AIN.DAQtriggerConfig(channel, self.__digital_trigger_mode[channel], + self.__digital_trigger_source[channel], + self.__analog_trigger_mask[channel]) + value_name = 'set_analog_trigger_mask {}'.format(mask) + return result_parser(value, value_name, verbose) + + # DAQtriggerExternalConfig + def set_ext_trigger_source(self, source, channel, verbose=False): + """ Sets the trigger source + + Args: + channel (int) : the input channel you are configuring + source (int) : the trigger source you are using + """ + self.__external_source[channel] = source + value = self.SD_AIN.DAQtriggerExternalConfig(channel, self.__external_source[channel], + self.__trigger_behaviour[channel]) + value_name = 'set_ext_trigger_source {}'.format(source) + return result_parser(value, value_name, verbose) + + def set_ext_trigger_behaviour(self, behaviour, channel, verbose=False): + """ Sets the trigger source + + Args: + channel (int) : the input channel you are configuring + behaviour (int) : the trigger behaviour you are using + """ + self.__external_behaviour[channel] = behaviour + value = self.SD_AIN.DAQtriggerExternalConfig(channel, self.__external_source[channel], + self.__trigger_behaviour[channel]) + value_name = 'set_ext_trigger_behaviour {}'.format(behaviour) + return result_parser(value, value_name, verbose) + + # DAQread + def set_n_points(self, n_points, channel): + """ Sets the trigger source + + Args: + channel (int) : the input channel you are configuring + n_points (int) : the number of points to be read from specified DAQ + """ + self.__n_points[channel] = n_points + + def set_timeout(self, timeout, channel): + """ Sets the trigger source + + Args: + channel (int) : the input channel you are configuring + timeout (int) : the read timeout in ms for the specified DAQ + """ + self.__timeout[channel] = timeout + diff --git a/qcodes/instrument_drivers/keysight/SD_common/SD_DIG_test.py b/qcodes/instrument_drivers/keysight/SD_common/SD_DIG_test.py new file mode 100644 index 000000000000..453e978cd083 --- /dev/null +++ b/qcodes/instrument_drivers/keysight/SD_common/SD_DIG_test.py @@ -0,0 +1,22 @@ +from qcodes.instrument_drivers.test import DriverTestCase +import signadyne_common.SD_DIG + +class SD_DIG_test(DriverTestCase): + """ + This is the qcodes driver test suite for a generic Signadyne Digitizer of the M32/33XX series. + + Status: pre-alpha + + """ + driver = SD_DIG + + @classMethod + def setUpClass(cls): + super().setUpClass() + + def test_device_name(): + """ Test that the device can be accessed and returns a name + """ + name = self.instrument.SD_AIN.getProductNameBySlot(1,8) + self.assertEqual(name, 'M3300A') + diff --git a/qcodes/instrument_drivers/keysight/SD_common/SD_Module.py b/qcodes/instrument_drivers/keysight/SD_common/SD_Module.py new file mode 100644 index 000000000000..ec2cf0b4341d --- /dev/null +++ b/qcodes/instrument_drivers/keysight/SD_common/SD_Module.py @@ -0,0 +1,329 @@ +import warnings + +from qcodes.instrument.base import Instrument +from numpy import ndarray + +try: + import keysightSD1 +except ImportError: + raise ImportError('to use the Keysight SD drivers install the keysightSD1 module ' + '(http://www.keysight.com/main/software.jspx?ckey=2784055)') + + +def result_parser(value, name='result', verbose=False): + """ + This method is used for parsing the result in the get-methods. + For values that are non-negative, the value is simply returned. + Negative values indicate an error, so an error is raised + with a reference to the error code. + + The parser also can print to the result to the shell if verbose is 1. + + Args: + value: the value to be parsed + name (str): name of the value to be parsed + verbose (bool): boolean indicating verbose mode + + Returns: + value: parsed value, which is the same as value if non-negative + or not a number + """ + if isinstance(value, ndarray) or isinstance(value, str) or isinstance(value, bool) or (int(value) >= 0): + if verbose: + print('{}: {}'.format(name, value)) + return value + else: + raise Exception('Error in call to SD_Module ' + 'error code {}'.format(value)) + + +class SD_Module(Instrument): + """ + This is the general SD_Module driver class that implements shared parameters and functionality among all PXIe-based + digitizer/awg/combo cards by Keysight. + + This driver was written to be inherited from by either the SD_AWG, SD_DIG or SD_Combo class, depending on the + functionality of the card. + + Specifically, this driver was written with the M3201A and M3300A cards in mind. + + This driver makes use of the Python library provided by Keysight as part of the SD1 Software package (v.2.01.00). + """ + + def __init__(self, name, chassis, slot, **kwargs): + super().__init__(name, **kwargs) + + # Create instance of keysight SD_Module class + self.SD_module = keysightSD1.SD_Module() + + # Open the device, using the specified chassis and slot number + module_name = self.SD_module.getProductNameBySlot(chassis, slot) + if isinstance(module_name, str): + result_code = self.SD_module.openWithSlot(module_name, chassis, slot) + if result_code <= 0: + raise Exception('Could not open SD_Module ' + 'error code {}'.format(result_code)) + else: + raise Exception('No SD Module found at ' + 'chassis {}, slot {}'.format(chassis, slot)) + + self.add_parameter('module_count', + label='module count', + get_cmd=self.get_module_count, + docstring='The number of Signadyne modules installed in the system') + self.add_parameter('product_name', + label='product name', + get_cmd=self.get_product_name, + docstring='The product name of the device') + self.add_parameter('serial_number', + label='serial number', + get_cmd=self.get_serial_number, + docstring='The serial number of the device') + self.add_parameter('chassis_number', + label='chassis number', + get_cmd=self.get_chassis, + docstring='The chassis number where the device is located') + self.add_parameter('slot_number', + label='slot number', + get_cmd=self.get_slot, + docstring='The slot number where the device is located') + self.add_parameter('status', + label='status', + get_cmd=self.get_status, + docstring='The status of the device') + self.add_parameter('firmware_version', + label='firmware version', + get_cmd=self.get_firmware_version, + docstring='The firmware version of the device') + self.add_parameter('hardware_version', + label='hardware version', + get_cmd=self.get_hardware_version, + docstring='The hardware version of the device') + self.add_parameter('instrument_type', + label='type', + get_cmd=self.get_type, + docstring='The type of the device') + self.add_parameter('open', + label='open', + get_cmd=self.get_open, + docstring='Indicating if device is open, True (open) or False (closed)') + + # + # Get-commands + # + + def get_module_count(self, verbose=False): + """Returns the number of SD modules installed in the system""" + value = self.SD_module.moduleCount() + value_name = 'module_count' + return result_parser(value, value_name, verbose) + + def get_product_name(self, verbose=False): + """Returns the product name of the device""" + value = self.SD_module.getProductName() + value_name = 'product_name' + return result_parser(value, value_name, verbose) + + def get_serial_number(self, verbose=False): + """Returns the serial number of the device""" + value = self.SD_module.getSerialNumber() + value_name = 'serial_number' + return result_parser(value, value_name, verbose) + + def get_chassis(self, verbose=False): + """Returns the chassis number where the device is located""" + value = self.SD_module.getChassis() + value_name = 'chassis_number' + return result_parser(value, value_name, verbose) + + def get_slot(self, verbose=False): + """Returns the slot number where the device is located""" + value = self.SD_module.getSlot() + value_name = 'slot_number' + return result_parser(value, value_name, verbose) + + def get_status(self, verbose=False): + """Returns the status of the device""" + value = self.SD_module.getStatus() + value_name = 'status' + return result_parser(value, value_name, verbose) + + def get_firmware_version(self, verbose=False): + """Returns the firmware version of the device""" + value = self.SD_module.getFirmwareVersion() + value_name = 'firmware_version' + return result_parser(value, value_name, verbose) + + def get_hardware_version(self, verbose=False): + """Returns the hardware version of the device""" + value = self.SD_module.getHardwareVersion() + value_name = 'hardware_version' + return result_parser(value, value_name, verbose) + + def get_type(self, verbose=False): + """Returns the type of the device""" + value = self.SD_module.getType() + value_name = 'type' + return result_parser(value, value_name, verbose) + + def get_open(self, verbose=False): + """Returns whether the device is open (True) or not (False)""" + value = self.SD_module.isOpen() + value_name = 'open' + return result_parser(value, value_name, verbose) + + def get_pxi_trigger(self, pxi_trigger, verbose=False): + """ + Returns the digital value of the specified PXI trigger + + Args: + pxi_trigger (int): PXI trigger number (4000 + Trigger No.) + verbose (bool): boolean indicating verbose mode + + Returns: + value (int): Digital value with negated logic, 0 (ON) or 1 (OFF), + or negative numbers for errors + """ + value = self.SD_module.PXItriggerRead(pxi_trigger) + value_name = 'pxi_trigger number {}'.format(pxi_trigger) + return result_parser(value, value_name, verbose) + + # + # Set-commands + # + + def set_pxi_trigger(self, value, pxi_trigger): + """ + Sets the digital value of the specified PXI trigger + + Args: + pxi_trigger (int): PXI trigger number (4000 + Trigger No.) + value (int): Digital value with negated logic, 0 (ON) or 1 (OFF) + """ + self.SD_module.PXItriggerWrite(pxi_trigger, value) + + # + # FPGA related functions + # + + def get_fpga_pc_port(self, port, data_size, address, address_mode, access_mode, verbose=False): + """ + Reads data at the PCport FPGA Block + + Args: + port (int): PCport number + data_size (int): number of 32-bit words to read (maximum is 128 words) + address (int): address that wil appear at the PCport interface + address_mode (int): ?? not in the docs + access_mode (int): ?? not in the docs + """ + data = self.SD_module.FPGAreadPCport(port, data_size, address, address_mode, access_mode) + value_name = 'data at PCport {}'.format(port) + return result_parser(data, value_name, verbose) + + def set_fpga_pc_port(self, port, data, address, address_mode, access_mode, verbose=False): + """ + Writes data at the PCport FPGA Block + + Args: + port (int): PCport number + data (array): array of integers containing the data + address (int): address that wil appear at the PCport interface + address_mode (int): ?? not in the docs + access_mode (int): ?? not in the docs + """ + self.SD_module.FPGAwritePCport(port, data, address, address_mode, access_mode, verbose) + + # + # The methods below are not used for setting or getting parameters, but can be used in the test functions of the + # test suite e.g. The main reason they are defined is to make this driver more complete + # + + def get_product_name_by_slot(self, chassis, slot, verbose=False): + value = self.SD_module.getProductNameBySlot(chassis, slot) + value_name = 'product_name' + return result_parser(value, value_name, verbose) + + def get_product_name_by_index(self, index, verbose=False): + value = self.SD_module.getProductNameByIndex(index) + value_name = 'product_name' + return result_parser(value, value_name, verbose) + + def get_serial_number_by_slot(self, chassis, slot, verbose=False): + warnings.warn('Returns faulty serial number due to error in Keysight lib v.2.01.00', UserWarning) + value = self.SD_module.getSerialNumberBySlot(chassis, slot) + value_name = 'serial_number' + return result_parser(value, value_name, verbose) + + def get_serial_number_by_index(self, index, verbose=False): + warnings.warn('Returns faulty serial number due to error in Keysight lib v.2.01.00', UserWarning) + value = self.SD_module.getSerialNumberByIndex(index) + value_name = 'serial_number' + return result_parser(value, value_name, verbose) + + def get_type_by_slot(self, chassis, slot, verbose=False): + value = self.awg.getTypeBySlot(chassis, slot) + value_name = 'type' + return result_parser(value, value_name, verbose) + + def get_type_by_index(self, index, verbose=False): + value = self.awg.getTypeByIndex(index) + value_name = 'type' + return result_parser(value, value_name, verbose) + + # + # The methods below are useful for controlling the device, but are not used for setting or getting parameters + # + + # closes the hardware device and also throws away the current instrument object + # if you want to open the instrument again, you have to initialize a new instrument object + def close(self): + self.SD_module.close() + super().close() + + # only closes the hardware device, does not delete the current instrument object + def close_soft(self): + self.SD_module.close() + + def open_with_serial_number(self, name, serial_number): + self.SD_module.openWithSerialNumber(name, serial_number) + + def open_with_slot(self, name, chassis, slot): + self.SD_module.openWithSlot(name, chassis, slot) + + def run_self_test(self): + value = self.SD_module.runSelfTest() + print('Did self test and got result: {}'.format(value)) + + # method below is commented out because it is missing from the dll provided by Keysight + # def get_awg_running(self, verbose=0, awg_number): + # """ + # Returns whether the AWG is running or stopped + # + # Args: + # awg_number (int): AWG number + # + # Returns: + # value (int): 1 if the AWG is running, 0 if it is stopped + # """ + # value = + # if verbose: + # print('slot_number: %s' % value) + # return value + + # method below is commented out because it is missing from the dll provided by Keysight + # def get_awg_waveform_number_playing(self, verbose=0, awg_number=0): + # """ + # Returns the waveformNumber of the waveform which is currently being generated. + # + # Args: + # awg_number (int): AWG number + # + # Returns: + # value (int): Waveform identifier, + # or negative numbers for errors + # """ + # value = self.awg.AWG + # if verbose: + # print('pxi_trigger number %s: %s' % (pxi_trigger, value)) + # return value diff --git a/qcodes/instrument_drivers/keysight/__init__.py b/qcodes/instrument_drivers/keysight/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/qcodes/instrument_drivers/keysight/test_suite.py b/qcodes/instrument_drivers/keysight/test_suite.py new file mode 100644 index 000000000000..d1edc5924a5d --- /dev/null +++ b/qcodes/instrument_drivers/keysight/test_suite.py @@ -0,0 +1,510 @@ +from qcodes.instrument_drivers.test import DriverTestCase +from .M3201A import Keysight_M3201A +from .M3300A import M3300A_AWG +from .SD_common.SD_Module import SD_Module + + +class TestSD_Module(DriverTestCase): + """ + Tis is a test suite for testing the general Keysight SD_Module driver. + + This test suit is only used during the development of the general SD_Module driver. In a real-life scenario, + no direct instances will be made from this class, but rather instances of either SD_AWG or SD_DIG. + """ + + driver = SD_Module + + @classmethod + def setUpClass(cls): + super().setUpClass() + + def test_chassis_and_slot(self): + chassis_number = self.instrument.chassis_number() + slot_number = self.instrument.slot_number() + product_name = self.instrument.product_name() + serial_number = self.instrument.serial_number() + + product_name_test = self.instrument.get_product_name_by_slot(chassis_number, slot_number) + self.assertEqual(product_name_test, product_name) + + serial_number_test = self.instrument.get_serial_number_by_slot(chassis_number, slot_number) + # hack to make this test pass even with the current faulty Keysight lib (v.2.01.00) + serial_number = serial_number[:-1] + self.assertEqual(serial_number_test, serial_number) + + +class TestKeysight_M3201A(DriverTestCase): + """ + This is a test suite for testing the Signadyne M3201A AWG card driver. + It provides test functions for most of the functions and parameters as defined in the driver, + as well as test functions for general things like connecting to the device. + + Status: beta + + Most of the current test functions are not super useful yet because the driver doesn't support set-able parameters + at the moment. Normally a more useful test function would do something like: + + self.instrument.clock_frequency(100e6) + self.assertAlmostEqual(self.instrument.clock_frequency(), 100e6, places=4) + + Unfortunately, the Signadyne libraries don't support many variables that are both get-able and set-able. + + We can however test for ValueErrors which is a useful safety test. + """ + + driver = Keysight_M3201A + + @classmethod + def setUpClass(cls): + super().setUpClass() + print('Did set up Class. Should have printed `found ..., testing`.') + cls.instrument.off() # Not a test but a safety measure + + def test_chassis_number(self): + chassis_number = self.instrument.chassis_number() + self.assertEqual(chassis_number, 1) + + def test_slot_number(self): + slot_number = self.instrument.slot_number() + self.assertEqual(slot_number, 7) + + def test_serial_number(self): + serial_number = self.instrument.serial_number() + self.assertEqual(serial_number, 'ES56600108') + + def test_chassis_and_slot(self): + chassis_number = self.instrument.chassis_number() + slot_number = self.instrument.slot_number() + product_name = self.instrument.product_name() + serial_number = self.instrument.serial_number() + + product_name_test = self.instrument.get_product_name_by_slot(chassis_number, slot_number) + self.assertEqual(product_name_test, product_name) + + serial_number_test = self.instrument.get_serial_number_by_slot(chassis_number, slot_number) + # hack to make this test pass even with the current faulty Keysight lib (v.2.01.00) + serial_number = serial_number[:-1] + self.assertEqual(serial_number_test, serial_number) + + def test_open_close(self): + chassis_number = self.instrument.chassis_number() + slot_number = self.instrument.slot_number() + product_name = self.instrument.product_name() + serial_number = self.instrument.serial_number() + + self.instrument.close_soft() + open_status = self.instrument.open() + self.assertEqual(open_status, False) + + self.instrument.open_with_serial_number(product_name, serial_number) + open_status = self.instrument.open() + self.assertEqual(open_status, True) + + self.instrument.close_soft() + self.instrument.open_with_slot(product_name, chassis_number, slot_number) + open_status = self.instrument.open() + self.assertEqual(open_status, True) + + def test_clock_frequency(self): + with self.assertRaises(ValueError): + self.instrument.clock_frequency(600e6) + with self.assertRaises(ValueError): + self.instrument.clock_frequency(32) + + cur_f = self.instrument.clock_frequency() + test_f = 300e6 + self.instrument.clock_frequency(test_f) + self.assertAlmostEqual(self.instrument.clock_frequency(), test_f, delta=1) + + test_f = 453.152e6 + self.instrument.clock_frequency(test_f) + self.assertAlmostEqual(self.instrument.clock_frequency(), test_f, delta=1) + + # leave the setup in the initial state + self.instrument.clock_frequency(cur_f) + + def test_channel_frequency(self): + cur_f = self.instrument.frequency_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.frequency_channel_0.set(600e6) + with self.assertRaises(ValueError): + self.instrument.frequency_channel_0.set(-32) + + # turn off the signal for safety + self.instrument.off() + + self.instrument.frequency_channel_0.set(0.1e6) + self.instrument.frequency_channel_0.set(10e6) + self.instrument.frequency_channel_0.set(132) + + # leave the setup in the initial state or default values if no initial state was found + if cur_f: + self.instrument.frequency_channel_0.set(cur_f) + else: + self.instrument.frequency_channel_0.set(0) + + def test_channel_phase(self): + cur_p = self.instrument.phase_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.phase_channel_0.set(400) + with self.assertRaises(ValueError): + self.instrument.phase_channel_0.set(-32) + + # turn off the signal for safety + self.instrument.off() + + self.instrument.phase_channel_0.set(0) + self.instrument.phase_channel_0.set(351.89) + self.instrument.phase_channel_0.set(6.123) + + # leave the setup in the initial state or default values if no initial state was found + if cur_p: + self.instrument.phase_channel_0.set(cur_p) + else: + self.instrument.phase_channel_0.set(0) + + def test_channel_amplitude(self): + cur_a = self.instrument.amplitude_channel_0.get_latest() + cur_o = self.instrument.offset_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.amplitude_channel_0.set(2) + with self.assertRaises(ValueError): + self.instrument.amplitude_channel_0.set(-3) + + # turn off the signal for safety + self.instrument.off() + # set offset to zero (so we don't go out of range) + self.instrument.offset_channel_0.set(0) + + self.instrument.amplitude_channel_0.set(0) + self.instrument.amplitude_channel_0.set(1.35) + self.instrument.amplitude_channel_0.set(-1.112) + + # leave the setup in the initial state or default values if no initial state was found + if cur_a: + self.instrument.amplitude_channel_0.set(cur_a) + else: + self.instrument.amplitude_channel_0.set(0) + if cur_o: + self.instrument.offset_channel_0.set(cur_o) + else: + self.instrument.offset_channel_0.set(0) + + def test_channel_offset(self): + cur_o = self.instrument.offset_channel_0.get_latest() + cur_a = self.instrument.amplitude_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.offset_channel_0.set(2) + with self.assertRaises(ValueError): + self.instrument.offset_channel_0.set(-3) + + # turn off the signal for safety + self.instrument.off() + # set amplitude to zero (so we don't go out of range) + self.instrument.amplitude_channel_0.set(0) + + self.instrument.offset_channel_0.set(0) + self.instrument.offset_channel_0.set(1.35) + self.instrument.offset_channel_0.set(-1.112) + + # leave the setup in the initial state or default values if no initial state was found + if cur_o: + self.instrument.offset_channel_0.set(cur_o) + else: + self.instrument.offset_channel_0.set(0) + if cur_a: + self.instrument.amplitude_channel_0.set(cur_a) + else: + self.instrument.amplitude_channel_0.set(0) + + def test_channel_wave_shape(self): + cur_w = self.instrument.wave_shape_channel_0.get_latest() + cur_o = self.instrument.offset_channel_0.get_latest() + cur_a = self.instrument.amplitude_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.wave_shape_channel_0.set(1.5) + with self.assertRaises(ValueError): + self.instrument.wave_shape_channel_0.set(-3) + + # turn off the signal for safety + self.instrument.off() + # set amplitude and offset to zero for safety + self.instrument.amplitude_channel_0.set(0) + self.instrument.offset_channel_0.set(0) + + self.instrument.wave_shape_channel_0.set(0) + self.instrument.wave_shape_channel_0.set(1) + self.instrument.wave_shape_channel_0.set(6) + self.instrument.wave_shape_channel_0.set(5) + + # leave the setup in the initial state or default values if no initial state was found + if cur_w: + self.instrument.wave_shape_channel_0.set(cur_w) + else: + self.instrument.wave_shape_channel_0.set(0) + if cur_o: + self.instrument.offset_channel_0.set(cur_o) + else: + self.instrument.offset_channel_0.set(0) + if cur_a: + self.instrument.amplitude_channel_0.set(cur_a) + else: + self.instrument.amplitude_channel_0.set(0) + + def test_PXI_trigger(self): + with self.assertRaises(ValueError): + self.instrument.pxi_trigger_number_0.set(1.5) + with self.assertRaises(ValueError): + self.instrument.clock_frequency(32) + + cur_pxi = self.instrument.pxi_trigger_number_0.get() + + test_pxi = 0 + self.instrument.pxi_trigger_number_0.set(test_pxi) + self.assertEqual(self.instrument.pxi_trigger_number_0.get(), test_pxi) + + test_pxi = 1 + self.instrument.pxi_trigger_number_0.set(test_pxi) + self.assertEqual(self.instrument.pxi_trigger_number_0.get(), test_pxi) + + # leave the setup in the initial state + self.instrument.pxi_trigger_number_0.set(cur_pxi) + + +class TestKeysight_M3300A(DriverTestCase): + """ + This is a test suite for testing the Signadyne M3201A AWG card driver. + It provides test functions for most of the functions and parameters as defined in the driver, + as well as test functions for general things like connecting to the device. + + Status: beta + + Most of the current test functions are not super useful yet because the driver doesn't support set-able parameters + at the moment. Normally a more useful test function would do something like: + + self.instrument.clock_frequency(100e6) + self.assertAlmostEqual(self.instrument.clock_frequency(), 100e6, places=4) + + Unfortunately, the Signadyne libraries don't support many variables that are both get-able and set-able. + + We can however test for ValueErrors which is a useful safety test. + """ + + driver = M3300A_AWG + + @classmethod + def setUpClass(cls): + super().setUpClass() + print('Did set up Class. Should have printed `found ..., testing`.') + cls.instrument.off() # Not a test but a safety measure + + def test_chassis_number(self): + chassis_number = self.instrument.chassis_number() + self.assertEqual(chassis_number, 1) + + def test_slot_number(self): + slot_number = self.instrument.slot_number() + self.assertEqual(slot_number, 8) + + def test_serial_number(self): + serial_number = self.instrument.serial_number() + self.assertEqual(serial_number, 'ES56600101') + + def test_chassis_and_slot(self): + chassis_number = self.instrument.chassis_number() + slot_number = self.instrument.slot_number() + product_name = self.instrument.product_name() + serial_number = self.instrument.serial_number() + + product_name_test = self.instrument.get_product_name_by_slot(chassis_number, slot_number) + self.assertEqual(product_name_test, product_name) + + serial_number_test = self.instrument.get_serial_number_by_slot(chassis_number, slot_number) + # hack to make this test pass even with the current faulty Keysight lib (v.2.01.00) + serial_number = serial_number[:-1] + self.assertEqual(serial_number_test, serial_number) + + def test_open_close(self): + chassis_number = self.instrument.chassis_number() + slot_number = self.instrument.slot_number() + product_name = self.instrument.product_name() + serial_number = self.instrument.serial_number() + + self.instrument.close_soft() + open_status = self.instrument.open() + self.assertEqual(open_status, False) + + self.instrument.open_with_serial_number(product_name, serial_number) + open_status = self.instrument.open() + self.assertEqual(open_status, True) + + self.instrument.close_soft() + self.instrument.open_with_slot(product_name, chassis_number, slot_number) + open_status = self.instrument.open() + self.assertEqual(open_status, True) + + def test_clock_frequency(self): + # TODO: This does not work, figure out why + with self.assertRaises(ValueError): + self.instrument.clock_frequency(600e6) + with self.assertRaises(ValueError): + self.instrument.clock_frequency(32) + + cur_f = self.instrument.clock_frequency() + # test_f = 200e6 + # self.instrument.clock_frequency(test_f) + # self.assertAlmostEqual(self.instrument.clock_frequency(), test_f, delta=1) + # + # test_f = 453.152e6 + # self.instrument.clock_frequency(test_f) + # self.assertAlmostEqual(self.instrument.clock_frequency(), test_f, delta=1) + print(cur_f) + # leave the setup in the initial state + # self.instrument.clock_frequency(cur_f) + + def test_channel_frequency(self): + cur_f = self.instrument.frequency_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.frequency_channel_0.set(600e6) + with self.assertRaises(ValueError): + self.instrument.frequency_channel_0.set(-32) + + # turn off the signal for safety + self.instrument.off() + + self.instrument.frequency_channel_0.set(0.1e6) + self.instrument.frequency_channel_0.set(10e6) + self.instrument.frequency_channel_0.set(132) + + # leave the setup in the initial state or default values if no initial state was found + if cur_f: + self.instrument.frequency_channel_0.set(cur_f) + else: + self.instrument.frequency_channel_0.set(0) + + def test_channel_phase(self): + cur_p = self.instrument.phase_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.phase_channel_0.set(400) + with self.assertRaises(ValueError): + self.instrument.phase_channel_0.set(-32) + + # turn off the signal for safety + self.instrument.off() + + self.instrument.phase_channel_0.set(0) + self.instrument.phase_channel_0.set(351.89) + self.instrument.phase_channel_0.set(6.123) + + # leave the setup in the initial state or default values if no initial state was found + if cur_p: + self.instrument.phase_channel_0.set(cur_p) + else: + self.instrument.phase_channel_0.set(0) + + def test_channel_amplitude(self): + cur_a = self.instrument.amplitude_channel_0.get_latest() + cur_o = self.instrument.offset_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.amplitude_channel_0.set(2) + with self.assertRaises(ValueError): + self.instrument.amplitude_channel_0.set(-3) + + # turn off the signal for safety + self.instrument.off() + # set offset to zero (so we don't go out of range) + self.instrument.offset_channel_0.set(0) + + self.instrument.amplitude_channel_0.set(0) + self.instrument.amplitude_channel_0.set(1.35) + self.instrument.amplitude_channel_0.set(-1.112) + + # leave the setup in the initial state or default values if no initial state was found + if cur_a: + self.instrument.amplitude_channel_0.set(cur_a) + else: + self.instrument.amplitude_channel_0.set(0) + if cur_o: + self.instrument.offset_channel_0.set(cur_o) + else: + self.instrument.offset_channel_0.set(0) + + def test_channel_offset(self): + cur_o = self.instrument.offset_channel_0.get_latest() + cur_a = self.instrument.amplitude_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.offset_channel_0.set(2) + with self.assertRaises(ValueError): + self.instrument.offset_channel_0.set(-3) + + # turn off the signal for safety + self.instrument.off() + # set amplitude to zero (so we don't go out of range) + self.instrument.amplitude_channel_0.set(0) + + self.instrument.offset_channel_0.set(0) + self.instrument.offset_channel_0.set(1.35) + self.instrument.offset_channel_0.set(-1.112) + + # leave the setup in the initial state or default values if no initial state was found + if cur_o: + self.instrument.offset_channel_0.set(cur_o) + else: + self.instrument.offset_channel_0.set(0) + if cur_a: + self.instrument.amplitude_channel_0.set(cur_a) + else: + self.instrument.amplitude_channel_0.set(0) + + def test_channel_wave_shape(self): + cur_w = self.instrument.wave_shape_channel_0.get_latest() + cur_o = self.instrument.offset_channel_0.get_latest() + cur_a = self.instrument.amplitude_channel_0.get_latest() + with self.assertRaises(ValueError): + self.instrument.wave_shape_channel_0.set(1.5) + with self.assertRaises(ValueError): + self.instrument.wave_shape_channel_0.set(-3) + + # turn off the signal for safety + self.instrument.off() + # set amplitude and offset to zero for safety + self.instrument.amplitude_channel_0.set(0) + self.instrument.offset_channel_0.set(0) + + self.instrument.wave_shape_channel_0.set(0) + self.instrument.wave_shape_channel_0.set(1) + self.instrument.wave_shape_channel_0.set(6) + self.instrument.wave_shape_channel_0.set(5) + + # leave the setup in the initial state or default values if no initial state was found + if cur_w: + self.instrument.wave_shape_channel_0.set(cur_w) + else: + self.instrument.wave_shape_channel_0.set(0) + if cur_o: + self.instrument.offset_channel_0.set(cur_o) + else: + self.instrument.offset_channel_0.set(0) + if cur_a: + self.instrument.amplitude_channel_0.set(cur_a) + else: + self.instrument.amplitude_channel_0.set(0) + + def test_PXI_trigger(self): + with self.assertRaises(ValueError): + self.instrument.pxi_trigger_number_0.set(1.5) + with self.assertRaises(ValueError): + self.instrument.clock_frequency(32) + + cur_pxi = self.instrument.pxi_trigger_number_0.get() + + test_pxi = 0 + self.instrument.pxi_trigger_number_0.set(test_pxi) + self.assertEqual(self.instrument.pxi_trigger_number_0.get(), test_pxi) + + test_pxi = 1 + self.instrument.pxi_trigger_number_0.set(test_pxi) + self.assertEqual(self.instrument.pxi_trigger_number_0.get(), test_pxi) + + # leave the setup in the initial state + self.instrument.pxi_trigger_number_0.set(cur_pxi) diff --git a/qcodes/instrument_drivers/spincore/PulseBlasterDDS_II_300.py b/qcodes/instrument_drivers/spincore/PulseBlasterDDS_II_300.py new file mode 100644 index 000000000000..8719cebb9da1 --- /dev/null +++ b/qcodes/instrument_drivers/spincore/PulseBlasterDDS_II_300.py @@ -0,0 +1,306 @@ +from qcodes.instrument.base import Instrument +from qcodes.utils.validators import Numbers, Enum, Ints, Strings, Anything +from functools import partial +try: + from .spinapi import * +except: + raise ImportError('To use a SpinCore PulseBlaster, install the Python SpinAPI') + +class PB_KEYWORDS(): + ns = 1.0 + us = 1000.0 + ms = 1000000.0 + + MHz = 1.0 + kHz = 0.001 + Hz = 0.000001 + + # pb_start_programming options + PULSE_PROGRAM = 0 + FREQ_REGS = 1 + TX_PHASE_REGS = 2 + RX_PHASE_REGS = 3 + #COS_PHASE_REGS = 4 # RadioProcessor boards ONLY + #SIN_PHASE_REGS = 5 # RadioProcessor boards ONLY + +class PB_ERRORS(): + pass + + +class PB_DDS(Instrument): + """ + This is the qcodes driver for the SpinCore PulseBlasterDDS-II-300 + + Args: + name (str): name for this instrument, passed to the base instrument + """ + + def __init__(self, name, **kwargs): + if 'Werror' in kwargs: + self.werror = kwargs.pop('Werror') + else: + self.werror = False + + super().__init__(name, **kwargs) + + self.N_CHANNELS = 2 + self.N_FREQ_REGS = 16 + self.N_PHASE_REGS = 8 + self.N_AMPLITUDE_REGS = 4 + + + self.DEFAULT_DDS_INST = (0, 0, 0, 0, 0) + + # Initialise the DDS + # NOTE: Hard coded value for board may want to be part of instrument init + if self.count_boards() > 0: + self.select_board(0) + pb_init() + # Call set defaults as part of init to give board a well defined state + pb_set_defaults() + else: + if self.werror: + raise IOError('PB Error: Can\'t find board') + else: + print('Error: Can\'t find board, will continue anyway') + + # Internal State Variables, used when setting individual registers when others + # may be undefined + self.__frequency = [[0.0]*self.N_FREQ_REGS , [0.0]*self.N_FREQ_REGS ] + self.__phase = [[0.0]*self.N_PHASE_REGS , [0.0]*self.N_PHASE_REGS] + # Create an empty list of lists of instructions [[], [], ...] + self.__chan_instructions = [] + for n in range(self.N_CHANNELS): + self.__chan_instructions.append([]) + + for n in range(self.N_CHANNELS): + # DDS Register Bank + for r in range(self.N_FREQ_REGS): + self.add_parameter( + 'frequency_n{}_r{}'.format(n, r), + label='DDS Frequency for channel {}, register {}'.format(n, r), + set_cmd=partial(self.set_frequency_register, channel=n, register=r), + vals=Numbers(), + docstring='' + ) + + for r in range(self.N_PHASE_REGS): + self.add_parameter( + 'phase_n{}_r{}'.format(n, r), + label='DDS Phase for channel {}, register {}'.format(n, r), + set_cmd=partial(self.set_phase_register, channel=n, register=r), + vals=Numbers(), + docstring='' + ) + + for r in range(self.N_AMPLITUDE_REGS): + self.add_parameter( + 'amplitude_n{}_r{}'.format(n, r), + label='DDS Amplitude for channel {}, register {}'.format(n, r), + set_cmd=partial(self.set_amplitude_register, channel=n, register=r), + vals=Numbers(), + docstring='' + ) + + ########################################################################### + ### DDS Board commands ### + ########################################################################### + # Just wrapped from spinapi.py # + + def get_version(self): + ret = pb_get_version() + if not isinstance(ret, str): + raise IOError(self.get_error()) + return ret + + def get_error(self): + """ Print library error as UTF-8 encoded string.""" + ret = "PB Error: " + pb_get_error() + + def count_boards(self): + """ Print the number of boards detected in the system.""" + ret = pb_count_boards() + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def init(self): + """ Initialize currently selected board.""" + ret = pb_init() + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def set_debug(self, debug): + ret = pb_set_debug(debug) + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def select_board(self, board_number): + """ Select a specific board number""" + ret = pb_select_board(board_number) + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def set_defaults(self): + """ Set board defaults. Must be called before using any other board functions.""" + ret = pb_set_defaults() + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def _core_clock(self, clock): + ret = pb_core_clock(ctypes.c_double(clock)) + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def write_register(self, address, value): + ret = pb_write_register(address, value) + if (ret < 0): + raise IOError(self.get_error()) + return ret + + ########################################################################### + ### DDS Control commands ### + ########################################################################### + + def add_inst(self, inst, channel): + self.__chan_instructions[channel].append(inst) + + def get_inst_list(self, channel): + return self.__chan_instructions[channel] + + def program_pulse_sequence(self, pulse_sequence): + """ An all-in-one function to send a pulse sequence to the board + + Args: + pulse_sequence (list) : a list of instructions to program the board + the instructions should be tuples i.e. + (FREQ0, PHASE0, ...) + """ + pb_start_programming(PB_KEYWORDS.PULSE_PROGRAM) + for p in pulse_sequence: + pb_inst_dds2(*p) + pb_stop_programming() + + + def program_inst(self): + """ Send the current instruction list to the board + + Args: + + """ + raise IOError('Warning: Unimplemented function') + pass + #pb_start_programming() + #n_inst = 0 + ## Find the longest set of instructions defined + #for n in range(self.N_CHANNELS): + # n_inst = max(n_inst, len(self.__chan_instructions[n])) + #for i in range(n_inst): + # # TODO: use lambdas here for ease + # #if (self.__chan_instructions[ + # pass + #pb_stop_programming() + + def set_shape_defaults(self, channel): + """ Resets the shape for the specified channel + + Args: + channel (int) : Either DDS0 (0) or DDS1 (1) + """ + pb_select_dds(channel) + ret = pb_set_shape_defaults() + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def load_waveform(self, data, device, channel): + """ Loads a waveform onto the DDS + + Args: + data (float) : an array of 1024 points representing a single + period of the waveform. Points range between + -1.0 and 1.0 + device (int) : Either DEVICE_DDS or DEVICE_SHAPE + channel (int) : Either DDS0 (0) or DDS1 (1) + """ + pb_select_dds(channel) + ret = pb_dds_load(data, device) + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def set_envelope_freq(self, freq, register, channel): + """ Sets the frequency for an envelope register + + Args: + freq (float) : the frequency in MHz for the envelope register + register (int) : the register number + channel (int) : Either DDS0 (0) or DDS1 (1) + """ + pb_select_dds(channel) + ret = pb_dds_set_envelope_freq(freq, register) + if (ret < 0): + raise IOError(self.get_error()) + return ret + + ########################################################################### + ### Set/Get commands for parameters ### + ########################################################################### + + def set_frequency_register(self, frequency, channel, register): + """ Sets the DDS frequency for the specified channel and register + + Args: + frequency (double) : + channel (int) : + register (int) : + """ + self.__frequency[channel][register] = frequency + pb_select_dds(channel) + pb_start_programming(PB_KEYWORDS.FREQ_REGS) + for r in range(self.N_FREQ_REGS): + ret = pb_set_freq(self.__frequency[channel][r]) + if (ret < 0): + raise IOError(self.get_error()) + ret = pb_stop_programming() + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def set_phase_register(self, phase, channel, register): + """ Sets the DDS phase for the specified channel and register + + Args: + phase (double) : + channel (int) : + register (int) : + """ + self.__phase[channel][register] = phase + pb_select_dds(channel) + pb_start_programming(PB_KEYWORDS.TX_PHASE_REGS) + for r in range(self.N_PHASE_REGS): + pb_set_phase(self.__phase[channel][register]) + ret = pb_stop_programming() + if (ret < 0): + raise IOError(self.get_error()) + return ret + + def set_amplitude_register(self, amplitude, channel, register): + """ Sets the DDS amplitude for the specified channel and register + + Args: + amplitude (double) : + channel (int) : + register (int) : + """ + pb_select_dds(channel) + ret = self.pb_set_amp(amplitude, register) + if (ret < 0): + raise IOError(self.get_error()) + return ret + diff --git a/qcodes/instrument_drivers/spincore/__init__.py b/qcodes/instrument_drivers/spincore/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/qcodes/instrument_drivers/spincore/spinapi.py b/qcodes/instrument_drivers/spincore/spinapi.py new file mode 100644 index 000000000000..c410914a9a1f --- /dev/null +++ b/qcodes/instrument_drivers/spincore/spinapi.py @@ -0,0 +1,229 @@ +# Copyright (c) 2015 SpinCore Technologies, Inc. +# http://www.spincore.com +# +# This software is provided 'as-is', without any express or implied warranty. +# In no event will the authors be held liable for any damages arising from the +# use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software in a +# product, an acknowledgement in the product documentation would be appreciated +# but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +# +# Modified by Mark Johnson + +import ctypes +c_float_p = ctypes.POINTER(ctypes.c_float), # create a float pointer type + +PULSE_PROGRAM = 0 +FREQ_REGS = 1 + +try: + spinapi = ctypes.CDLL("spinapi64") +except: + try: + spinapi = ctypes.CDLL("spinapi") + except: + raise("Failed to load spinapi library.") + +def enum(**enums): + return type('Enum', (), enums) + +ns = 1.0 +us = 1000.0 +ms = 1000000.0 + +MHz = 1.0 +kHz = 0.001 +Hz = 0.000001 + +#Instruction enum +Inst = enum( + CONTINUE = 0, + STOP = 1, + LOOP = 2, + END_LOOP = 3, + JSR = 4, + RTS = 5, + BRANCH = 6, + LONG_DELAY = 7, + WAIT = 8, + RTI = 9 +) +spinapi.pb_get_version.restype = (ctypes.c_char_p) +spinapi.pb_get_error.restype = (ctypes.c_char_p) + +spinapi.pb_count_boards.restype = (ctypes.c_int) + +spinapi.pb_init.restype = (ctypes.c_int) + +spinapi.pb_select_board.argtype = (ctypes.c_int) +spinapi.pb_select_board.restype = (ctypes.c_int) + +spinapi.pb_set_debug.argtype = (ctypes.c_int) +spinapi.pb_set_debug.restype = (ctypes.c_int) + +spinapi.pb_set_defaults.restype = (ctypes.c_int) + +spinapi.pb_core_clock.argtype = (ctypes.c_double) +spinapi.pb_core_clock.restype = (ctypes.c_int) + +spinapi.pb_write_register.argtype = (ctypes.c_int, ctypes.c_int) +spinapi.pb_write_register.restype = (ctypes.c_int) + +spinapi.pb_start_programming.argtype = (ctypes.c_int) +spinapi.pb_start_programming.restype = (ctypes.c_int) + +spinapi.pb_stop_programming.restype = (ctypes.c_int) + +spinapi.pb_start.restype = (ctypes.c_int) +spinapi.pb_stop.restype = (ctypes.c_int) +spinapi.pb_reset.restype = (ctypes.c_int) +spinapi.pb_close.restype = (ctypes.c_int) + +spinapi.pb_inst_dds2.argtype = ( + ctypes.c_int, #Frequency register DDS0 + ctypes.c_int, #Phase register DDS0 + ctypes.c_int, #Amplitude register DDS0 + ctypes.c_int, #Output enable DDS0 + ctypes.c_int, #Phase reset DDS0 + ctypes.c_int, #Frequency register DDS1 + ctypes.c_int, #Phase register DDS1 + ctypes.c_int, #Amplitude register DDS1 + ctypes.c_int, #Output enable DDS1, + ctypes.c_int, #Phase reset DDS1, + ctypes.c_int, #Flags + ctypes.c_int, #inst + ctypes.c_int, #inst data + ctypes.c_double, #timing value (double) +) +spinapi.pb_inst_dds2.restype = (ctypes.c_int) + +### DDS.h ### +spinapi.pb_set_freq.restype = (ctypes.c_int) +spinapi.pb_set_freq.argtype = (ctypes.c_double) + +spinapi.pb_set_phase.restype = (ctypes.c_int) +spinapi.pb_set_phase.argtype = (ctypes.c_double) + +spinapi.pb_set_shape_defaults.restype = (ctypes.c_int) + +spinapi.pb_select_dds.restype = (ctypes.c_int) +spinapi.pb_select_dds.argtype = (ctypes.c_int) + +spinapi.pb_dds_load.restype = (ctypes.c_int) +spinapi.pb_dds_load.argtype = ( + c_float_p, # *data + ctypes.c_int # device number +) + +spinapi.pb_dds_set_envelope_freq.restype = (ctypes.c_int) +spinapi.pb_dds_set_envelope_freq.argtype = ( + ctypes.c_float, # envelope frequency + ctypes.c_int # the frequency register +) + +spinapi.pb_set_amp.restype = (ctypes.c_int) +spinapi.pb_set_amp.argtype = ( + ctypes.c_float, # amplitude + ctypes.c_int, # address of amplitude register +) + + +def pb_get_version(): + """Return library version as UTF-8 encoded string.""" + ret = spinapi.pb_get_version() + return str(ctypes.c_char_p(ret).value.decode("utf-8")) + +def pb_get_error(): + """Return library error as UTF-8 encoded string.""" + ret = spinapi.pb_get_error() + return str(ctypes.c_char_p(ret).value.decode("utf-8")) + +def pb_count_boards(): + """Return the number of boards detected in the system.""" + return spinapi.pb_count_boards() + +def pb_init(): + """Initialize currently selected board.""" + return spinapi.pb_init() + +def pb_set_debug(debug): + return spinapi.pb_set_debug(debug) + +def pb_select_board(board_number): + """Select a specific board number""" + return spinapi.pb_select_board(board_number) + +def pb_set_defaults(): + """Set board defaults. Must be called before using any other board functions.""" + return spinapi.pb_set_defaults() + +def pb_core_clock(clock): + return spinapi.pb_core_clock(ctypes.c_double(clock)) + +def pb_write_register(address, value): + return spinapi.pb_write_register(address, value) + +def pb_start_programming(target): + return spinapi.pb_start_programming(target) + +def pb_stop_programming(): + return spinapi.pb_stop_programming() + +def pb_start(): + return spinapi.pb_start() + +def pb_stop(): + return spinapi.pb_stop() + +def pb_reset(): + return spinapi.pb_reset() + +def pb_close(): + return spinapi.pb_close() + + + +################################################################################ +### DDS.h ### +################################################################################ + +def pb_set_freq(freq): + return spinapi.pb_set_freq(ctypes.c_double(freq)) + +def pb_set_phase(phase): + return spinapi.pb_set_phase(ctypes.c_double(phase)) + +def pb_inst_dds2(*args): + t = list(args) + #Argument 13 must be a double + t[13] = ctypes.c_double(t[13]) + args = tuple(t) + return spinapi.pb_inst_dds2(*args) + + +def pb_inst_dds2_shape(*args): + return spinapi.pb_inst_dds2_shape(*args) + +def pb_set_shape_defaults(): + return spinapi.pb_set_shape_defaults() + +def pb_select_dds(dds_num): + return spinapi.pb_select_dds(dds_num) + +def pb_dds_load(data, device): + return spinapi.pb_dds_load(c_float_p(data), device) + +def pb_dds_set_envelope_freq(freq, n): + return spinapi.pb_dds_set_envelope_freq(ctypes.c_double(freq), n) + +def pb_set_amp(amp, addr): + return spinapi.pb_set_amp(c_types.c_double(amp), addr)