From b6f0f76aeae09fa32a805d5fe44f6a6f36ddfc39 Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Fri, 2 Apr 2021 12:36:32 +0200 Subject: [PATCH 1/8] Added asynchronous function pair start_triggered() and get_data(). Improved error handling. --- .../drivers/Spectrum/M4i.py | 285 ++++++++++++------ 1 file changed, 192 insertions(+), 93 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py index 03871c236..b2dd76aab 100644 --- a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py +++ b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py @@ -32,7 +32,8 @@ log.info('M4i: adding header_dir %s to sys.path' % header_dir) sys.path.append(header_dir) import pyspcm -except (ImportError, OSError) as ex: + import py_header.spcerr +except (ImportError, OSError): info_str = 'to use the M4i driver install the pyspcm module and the M4i libs' log.exception(info_str) raise ImportError(info_str) @@ -61,6 +62,11 @@ def szTypeToName(lCardType): sName = 'unknown type' return sName +_errormsg_dict = { + getattr(py_header.spcerr, name): name + for name in dir(py_header.spcerr) if name.startswith('ERR_') + } + # %% Main driver class @@ -97,6 +103,8 @@ def __init__(self, name, cardid='spcm0', **kwargs): if self.hCard is None: logging.warning("M4i: no card found\n") + self._last_set_result = 0 + # add parameters for getting self.add_parameter('card_id', label='card id', @@ -130,6 +138,19 @@ def __init__(self, name, cardid='spcm0', **kwargs): get_cmd=partial(self._param32bit, pyspcm.SPC_PCISERIALNO), docstring='The serial number of the board') + self.add_parameter('hardware_version', + label='hardware version', + get_cmd=self.get_hardware_version, + docstring='The hardware version of the board') + self.add_parameter('firmware_version', + label='firmware version', + get_cmd=self.get_firmware_version, + docstring='The firmware version of the board') + self.add_parameter('features', + label='features', + get_cmd=partial(self._param32bit, + pyspcm.SPC_PCIEXTFEATURES), + docstring='Installed options and features') self.add_parameter('channel_count', label='channel count', get_cmd=partial(self._param32bit, @@ -309,71 +330,72 @@ def __init__(self, name, cardid='spcm0', **kwargs): docstring='Select the input path which is used to read out the features') for i in [0, 1, 2, 3]: - self.add_parameter('input_path_{}'.format(i), - label='input path {}'.format(i), - get_cmd=partial(self._param32bit, getattr( - pyspcm, 'SPC_PATH{}'.format(i))), - set_cmd=partial(self._set_param32bit, getattr( - pyspcm, 'SPC_PATH{}'.format(i))), + self.add_parameter(f'input_path_{i}', + label=f'input path {i}', + get_cmd=partial(self._param32bit, + getattr(pyspcm, f'SPC_PATH{i}')), + set_cmd=partial(self._set_param32bit, + getattr(pyspcm, f'SPC_PATH{i}')), vals=Enum(0, 1), - docstring='Set and get analog input path for channel {}'.format(i)) + docstring=f'Set and get analog input path for channel {i}') # channel range functions # TODO: check the input path to set the right validator (either by # directly calling input_path_x() or by storing a variable) - self.add_parameter('range_channel_{}'.format(i), - label='range channel {}'.format(i), - get_cmd=partial(self._param32bit, getattr( - pyspcm, 'SPC_AMP{}'.format(i))), - set_cmd=partial(self._set_param32bit, getattr( - pyspcm, 'SPC_AMP{}'.format(i))), + self.add_parameter(f'range_channel_{i}', + label=f'range channel {i}', + get_cmd=partial(self._param32bit, + getattr(pyspcm, f'SPC_AMP{i}')), + set_cmd=partial(self._set_param32bit, + getattr(pyspcm, f'SPC_AMP{i}')), vals=Enum(200, 500, 1000, 2000, 2500, 5000, 10000), unit='mV', - docstring='Set and get input range of channel {} (in mV)'.format(i)) + docstring=f'Set and get input range of channel {i} (in mV)') # input termination functions - self.add_parameter('termination_{}'.format(i), - label='termination {}'.format(i), - get_cmd=partial(self._param32bit, getattr( - pyspcm, 'SPC_50OHM{}'.format(i))), - set_cmd=partial(self._set_param32bit, getattr( - pyspcm, 'SPC_50OHM{}'.format(i))), + self.add_parameter(f'termination_{i}', + label=f'termination {i}', + get_cmd=partial(self._param32bit, + getattr(pyspcm, f'SPC_50OHM{i}')), + set_cmd=partial(self._set_param32bit, + getattr(pyspcm, f'SPC_50OHM{i}')), vals=Enum(0, 1), - docstring='if 1 sets termination to 50 Ohm, otherwise 1 MOhm for channel {}'.format(i)) + docstring=f'if 1 sets termination to 50 Ohm, otherwise 1 MOhm for channel {i}') # input coupling ACDC_coupling_docstring = f'if 1 sets the AC coupling, otherwise sets the DC coupling for channel {i}' ACDC_coupling_docstring += '\nThe AC coupling only works if the card is in HF mode.' - self.add_parameter('ACDC_coupling_{}'.format(i), - label='ACDC coupling {}'.format(i), - get_cmd=partial(self._param32bit, getattr( - pyspcm, 'SPC_ACDC{}'.format(i))), - set_cmd=partial(self._set_param32bit, getattr( - pyspcm, 'SPC_ACDC{}'.format(i))), + self.add_parameter(f'ACDC_coupling_{i}', + label=f'ACDC coupling {i}', + get_cmd=partial(self._param32bit, + getattr(pyspcm, f'SPC_ACDC{i}')), + set_cmd=partial(self._set_param32bit, + getattr(pyspcm, f'SPC_ACDC{i}')), vals=Enum(0, 1), docstring=ACDC_coupling_docstring) # AC/DC offset compensation - self.add_parameter('ACDC_offs_compensation_{}'.format(i), - label='ACDC offs compensation {}'.format(i), + self.add_parameter(f'ACDC_offs_compensation_{i}', + label=f'ACDC offs compensation {i}', get_cmd=partial(self._get_compensation, i), set_cmd=partial(self._set_compensation, i), vals=Enum(0, 1, M4i._NO_HF_MODE), - docstring=f'if 1 enables compensation, if 0 disables compensation for channel {i}. Value {M4i._NO_HF_MODE} means the card is not in HF mode') + docstring=(f'if 1 enables compensation, if 0 disables compensation for channel {i}. ' + 'Value {M4i._NO_HF_MODE} means the card is not in HF mode')) # anti aliasing filter (Bandwidth limit) - self.add_parameter('anti_aliasing_filter_{}'.format(i), - label='anti aliasing filter {}'.format(i), - get_cmd=partial(self._param32bit, getattr( - pyspcm, 'SPC_FILTER{}'.format(i))), - set_cmd=partial(self._set_param32bit, getattr( - pyspcm, 'SPC_FILTER{}'.format(i))), + self.add_parameter(f'anti_aliasing_filter_{i}', + label=f'anti aliasing filter {i}', + get_cmd=partial(self._param32bit, + getattr(pyspcm, f'SPC_FILTER{i}')), + set_cmd=partial(self._set_param32bit, + getattr(pyspcm, f'SPC_FILTER{i}')), vals=Enum(0, 1), - docstring='if 1 selects bandwidth limit, if 0 sets to full bandwidth for channel {}'.format(i)) + docstring=f'if 1 selects bandwidth limit, if 0 sets to full bandwidth for channel {i}') - self.add_parameter('channel_{}'.format(i), - label='channel {}'.format(i), + self.add_parameter(f'channel_{i}', + label=f'channel {i}', unit='a.u.', get_cmd=partial(self._read_channel, i)) @@ -550,32 +572,31 @@ def __init__(self, name, cardid='spcm0', **kwargs): docstring='A 1 sets the AC coupling for the external trigger, a 0 sets DC') for l in [0, 1]: - self.add_parameter('external_trigger_level_{}'.format(l), - label='external trigger level {}'.format(l), - get_cmd=partial(self._param32bit, getattr( - pyspcm, 'SPC_TRIG_EXT0_LEVEL{}'.format(l))), - set_cmd=partial(self._set_param32bit, getattr( - pyspcm, 'SPC_TRIG_EXT0_LEVEL{}'.format(l))), + self.add_parameter(f'external_trigger_level_{l}', + label=f'external trigger level {l}', + get_cmd=partial(self._param32bit, + getattr(pyspcm, f'SPC_TRIG_EXT0_LEVEL{l}')), + set_cmd=partial(self._set_param32bit, + getattr(pyspcm, f'SPC_TRIG_EXT0_LEVEL{l}')), unit='mV', - docstring='trigger level {} for external trigger'.format(l)) + docstring=f'trigger level {l} for external trigger') for i in [0, 1, 2, 3]: - self.add_parameter('trigger_mode_channel_{}'.format(i), - label='trigger mode channel {}'.format(i), - get_cmd=partial(self._param32bit, getattr( - pyspcm, 'SPC_TRIG_CH{}_MODE'.format(i))), - set_cmd=partial(self._set_param32bit, getattr( - pyspcm, 'SPC_TRIG_CH{}_MODE'.format(i))), - docstring='sets the trigger mode for channel {}'.format(i)) + self.add_parameter(f'trigger_mode_channel_{i}', + label=f'trigger mode channel {i}', + get_cmd=partial(self._param32bit, + getattr(pyspcm, f'SPC_TRIG_CH{i}_MODE')), + set_cmd=partial(self._set_param32bit, + getattr(pyspcm, f'SPC_TRIG_CH{i}_MODE')), + docstring=f'sets the trigger mode for channel {i}') for l in [0, 1]: - self.add_parameter('trigger_channel_{}_level_{}'.format(i, l), - label='trigger channel {} level {}'.format( - i, l), - get_cmd=partial(self._param32bit, getattr( - pyspcm, 'SPC_TRIG_CH{}_LEVEL{}'.format(i, l))), - set_cmd=partial(self._set_param32bit, getattr( - pyspcm, 'SPC_TRIG_CH{}_LEVEL{}'.format(i, l))), - docstring='trigger level {} channel {}'.format(l, i)) + self.add_parameter(f'trigger_channel_{i}_level_{l}', + label=f'trigger channel {i} level {l}', + get_cmd=partial(self._param32bit, + getattr(pyspcm, f'SPC_TRIG_CH{i}_LEVEL{l}')), + set_cmd=partial(self._set_param32bit, + getattr(pyspcm, f'SPC_TRIG_CH{i}_LEVEL{l}')), + docstring=f'trigger level {l} channel {i}') # add parameters for setting (write only registers) @@ -599,27 +620,40 @@ def __init__(self, name, cardid='spcm0', **kwargs): # checks if requirements for the compensation get and set functions are met def _get_compensation(self, i): # if HF enabled - if(getattr(self, 'input_path_{}'.format(i))() == 1): - return self._param32bit(getattr(pyspcm, 'SPC_ACDC_OFFS_COMPENSATION{}'.format(i))) + if self.get(f'input_path_{i}') == 1: + return self._param32bit(getattr(pyspcm, f'SPC_ACDC_OFFS_COMPENSATION{i}')) else: logging.info("M4i: HF path not set, ACDC offset compensation parameter will be ignored by the M4i card\n") return M4i._NO_HF_MODE def _set_compensation(self, i, value): # if HF enabled - if(getattr(self, 'input_path_{}'.format(i))() == 1): + if self.get(f'input_path_{i}') == 1: self._set_param32bit( - getattr(pyspcm, 'SPC_ACDC_OFFS_COMPENSATION{}'.format(i)), value) + getattr(pyspcm, f'SPC_ACDC_OFFS_COMPENSATION{i}'), value) else: logging.warning("M4i: HF path not set, ignoring ACDC offset compensation set\n") + def get_hardware_version(self): + version_info = self._param32bit(pyspcm.SPC_PCIVERSION) + return version_info >> 16 + + def get_firmware_version(self): + version_info = self._param32bit(pyspcm.SPC_PCIVERSION) + return version_info & 0xFFFF + def active_channels(self): """ Return a list with the indices of the active channels """ - x = bin(self.enable_channels())[2:][::-1] + x = bin(self.enable_channels.cache())[2:][::-1] return [i for i in range(len(x)) if int(x[i])] def get_idn(self): - return dict(zip(('vendor', 'model', 'serial', 'firmware'), ('Spectrum_GMBH', szTypeToName(self.get_card_type()), self.serial_number(), ' '))) + return { + 'vendor': 'Spectrum_GMBH', + 'model': szTypeToName(self.get_card_type()), + 'serial': self.serial_number(), + 'firmware': self.firmware_version() + } def reset(self): """ Reset the card @@ -630,8 +664,8 @@ def reset(self): def convert_to_voltage(self, data, input_range): """convert an array of numbers to an array of voltages.""" - resolution = self.ADC_to_voltage() - return data * input_range / resolution + resolution = self.ADC_to_voltage.cache() + return data * (input_range / resolution) def initialize_channels(self, channels=None, mV_range=1000, input_path=0, termination=0, coupling=0, compensation=None, @@ -673,7 +707,7 @@ def _channel_mask(self, channels=range(4)): """ cx = 0 for c in channels: - cx += getattr(pyspcm, 'CHANNEL{}'.format(c)) + cx += getattr(pyspcm, f'CHANNEL{c}') return cx def _read_channel(self, channel, memsize=None): @@ -685,7 +719,7 @@ def _read_channel(self, channel, memsize=None): if memsize is None: memsize = self._channel_memsize posttrigger_size = memsize - self._channel_pretrigger_memsize - mV_range = getattr(self, 'range_channel_%d' % channel).get() + mV_range = self.get(f'range_channel_{channel}') cx = self._channel_mask() self.enable_channels(cx) data = self.single_software_trigger_acquisition( @@ -710,20 +744,15 @@ def set_channel_settings(self, channel_index, mV_range, input_path, termination, compensation """ # initialize - getattr(self, 'input_path_{}'.format(channel_index))( - input_path) # 0: 1 MOhm + self.set(f'input_path_{channel_index}', input_path) # 0: default; 1: HF/50 Ohm if termination is not None: - getattr(self, 'termination_{}'.format( - channel_index))(termination) # 0: DC + self.set(f'termination_{channel_index}', termination) # 0: 1 MOhm; 1: 50 Ohm if coupling is not None: - getattr(self, 'ACDC_coupling_{}'.format( - channel_index))(coupling) # 0: DC - getattr(self, 'range_channel_{}'.format(channel_index))( - mV_range) # note: set after voltage range + self.set(f'ACDC_coupling_{channel_index}', coupling) # 0: DC; 1 AC + self.set(f'range_channel_{channel_index}', mV_range) # note: set after voltage range # can only be used with DC coupling and 50 Ohm path (hf) if compensation is not None: - getattr(self, 'ACDC_offs_compensation_{}'.format( - channel_index))(compensation) + self.set(f'ACDC_offs_compensation_{channel_index}', compensation) def set_ext0_OR_trigger_settings(self, trig_mode, termination, coupling, level0, level1=None): @@ -742,12 +771,77 @@ def set_channel_OR_trigger_settings(self, i, trig_mode, bitlevel0, bitlevel1=Non """When a channel is used for triggering it must be enabled during the acquisition.""" self.trigger_or_mask(0) - self.channel_or_mask(getattr(pyspcm, 'SPC_TMASK0_CH{}'.format(i))) - getattr(self, 'trigger_channel_{}_level_0'.format(i))(bitlevel0) + self.channel_or_mask(getattr(pyspcm, f'SPC_TMASK0_CH{i}')) + self.set(f'trigger_channel_{i}_level_0', bitlevel0) if(bitlevel1 != None): - getattr(self, 'trigger_channel_{}_level_1'.format(i))(bitlevel1) - getattr(self, 'trigger_mode_channel_{}'.format(i))( - trig_mode) # trigger mode + self.set(f'trigger_channel_{i}_level_1', bitlevel1) + self.set(f'trigger_mode_channel_{i}', trig_mode) # trigger mode + + def setup_multi_recording(self, posttrigger_size, n_triggers=1, pretrigger_size=None): + """ Setup multi recording. + + Triggering must have been configured separately. + Data acquisition must started with start_triggered(). + The collected data can be acquired with the function get_data(). + + Args: + posttrigger_size (int): size of data trace after triggering + n_triggers (int): total number of triggers + pretrigger_size (int): size of data trace before triggering + + Example: + digitizer.setup_multi_recording(size, n_triggers) + digitizer.start_triggered() + data = digitizer.get_data() + digitizer.start_triggered() + data = digitizer.get_data() + + """ + self.card_mode(pyspcm.SPC_REC_STD_MULTI) + if not pretrigger_size: + pretrigger_size = self.pretrigger_memory_size() + else: + self.pretrigger_memory_size(pretrigger_size) + seg_size = posttrigger_size+pretrigger_size + memsize = n_triggers * seg_size + self.data_memory_size(memsize) + self.segment_size(seg_size) + self.posttrigger_memory_size(posttrigger_size) + + def start_triggered(self): + '''Starts triggered acquisition + ''' + self.general_command(pyspcm.M2CMD_CARD_START | + pyspcm.M2CMD_CARD_ENABLETRIGGER) + + def get_data(self): + ''' Reads measurement data from the digitizer. + + The data acquisition must have been started by start_acquisition() or start_triggered(). + + Returns: + 2D array with voltages per channel in V. + ''' + active_channels = self.active_channels() + memsize = self.data_memory_size.cache() + numch = len(active_channels) + + res = self.wait_ready() + if res == pyspcm.ERR_TIMEOUT: + logging.error(f'Timeout waiting for data (timeout: {self.timeout()} ms)') + elif res != pyspcm.ERR_OK: + raise Exception(f'Error waiting for data: (0x{res:04x})') + + raw_data = self._transfer_buffer_numpy(memsize, numch) + self._stop_acquisition() + + resolution = self.ADC_to_voltage.cache() + voltages = np.zeros((numch, len(raw_data)//numch)) + for i,ch in enumerate(active_channels): + mV_range = self.get(f'range_channel_{ch}') + voltages[i,:] = raw_data[i::numch] * (mV_range / 1000 / resolution) + return voltages + def _stop_acquisition(self): @@ -850,8 +944,9 @@ def _transfer_buffer_numpy(self, memsize: int, numch: int, bytes_per_sample=2) - # data acquisition self._def_transfer64bit( pyspcm.SPCM_BUF_DATA, pyspcm.SPCM_DIR_CARDTOPC, 0, data_pointer, 0, bytes_per_sample * memsize * numch) - self.general_command(pyspcm.M2CMD_DATA_STARTDMA | - pyspcm.M2CMD_DATA_WAITDMA) + self.general_command(pyspcm.M2CMD_DATA_STARTDMA | pyspcm.M2CMD_DATA_WAITDMA) + if self._last_set_result != pyspcm.ERR_OK: + raise Exception(f'Error transferring data: (0x{self._last_set_result:04x})') # convert buffer to numpy array output = np.frombuffer(data_buffer, dtype=sample_ctype) @@ -864,12 +959,11 @@ def retrieve_data(self, trace): The data acquisition must have been started by start_acquisition. Args: - + trace (dict): dictionary with acquisition settings. Returns: voltages (array) """ - memsize = trace['memsize'] numch = trace['numch'] mV_range = trace['mV_range'] @@ -1016,7 +1110,7 @@ def wait_ready(self) -> int: """ command_result = pyspcm.spcm_dwSetParam_i32(self.hCard, pyspcm.SPC_M2CMD, int(pyspcm.M2CMD_CARD_WAITREADY)) return command_result - + def blockavg_hardware_trigger_acquisition(self, mV_range, nr_averages=10, verbose=0, post_trigger=None): """ Acquire data using block averaging and hardware triggering @@ -1135,7 +1229,11 @@ def _param32bit(self, param): def _set_param32bit(self, param, value): """ Set a 32-bit parameter on the device.""" value = int(value) # convert floating point to int if necessary - pyspcm.spcm_dwSetParam_i32(self.hCard, param, value) + res = pyspcm.spcm_dwSetParam_i32(self.hCard, param, value) + self._last_set_result = res + if res != pyspcm.ERR_OK: + logging.warning(f'SetParam failed. param:0x{param:08X} value:0x{value:08X}: ' + f'result: {_errormsg_dict[res]} (0x{res:08X})') def _invalidate_buf(self, buf_type): """Invalidate device buffer.""" @@ -1168,3 +1266,4 @@ def get_card_memory(self, verbose=0): if verbose: print('card_memory: %s' % (data.value)) return (data.value) + From 2de98bb4a3a33caaf0c8bf3358d633ef09912ffb Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Sat, 3 Apr 2021 14:34:02 +0200 Subject: [PATCH 2/8] Align memory size. Improved error handling --- .../drivers/Spectrum/M4i.py | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py index b2dd76aab..983079757 100644 --- a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py +++ b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py @@ -802,8 +802,9 @@ def setup_multi_recording(self, posttrigger_size, n_triggers=1, pretrigger_size= pretrigger_size = self.pretrigger_memory_size() else: self.pretrigger_memory_size(pretrigger_size) - seg_size = posttrigger_size+pretrigger_size - memsize = n_triggers * seg_size + posttrigger_size = self._hw_memsize(posttrigger_size) + seg_size = self._hw_memsize(posttrigger_size+pretrigger_size) + memsize = self._hw_memsize(n_triggers * seg_size) self.data_memory_size(memsize) self.segment_size(seg_size) self.posttrigger_memory_size(posttrigger_size) @@ -811,8 +812,7 @@ def setup_multi_recording(self, posttrigger_size, n_triggers=1, pretrigger_size= def start_triggered(self): '''Starts triggered acquisition ''' - self.general_command(pyspcm.M2CMD_CARD_START | - pyspcm.M2CMD_CARD_ENABLETRIGGER) + self.general_command(pyspcm.M2CMD_CARD_START | pyspcm.M2CMD_CARD_ENABLETRIGGER) def get_data(self): ''' Reads measurement data from the digitizer. @@ -832,8 +832,10 @@ def get_data(self): elif res != pyspcm.ERR_OK: raise Exception(f'Error waiting for data: (0x{res:04x})') - raw_data = self._transfer_buffer_numpy(memsize, numch) - self._stop_acquisition() + try: + raw_data = self._transfer_buffer_numpy(memsize, numch) + finally: + self._stop_acquisition() resolution = self.ADC_to_voltage.cache() voltages = np.zeros((numch, len(raw_data)//numch)) @@ -881,10 +883,10 @@ def multiple_trigger_acquisition(self, mV_range, memsize, seg_size, posttrigger_ self.wait_ready() # convert transfer data to numpy array - output = self._transfer_buffer_numpy( - memsize, numch, bytes_per_sample=2) - - self._stop_acquisition() + try: + output = self._transfer_buffer_numpy(memsize, numch, bytes_per_sample=2) + finally: + self._stop_acquisition() voltages = self.convert_to_voltage(output, mV_range / 1000) @@ -969,9 +971,10 @@ def retrieve_data(self, trace): mV_range = trace['mV_range'] self.wait_ready() - - output = self._transfer_buffer_numpy(memsize, numch) - self._stop_acquisition() + try: + output = self._transfer_buffer_numpy(memsize, numch) + finally: + self._stop_acquisition() voltages = self.convert_to_voltage(output, mV_range / 1000) @@ -999,8 +1002,10 @@ def single_trigger_acquisition(self, mV_range, memsize, posttrigger_size): self.general_command(pyspcm.M2CMD_CARD_START | pyspcm.M2CMD_CARD_ENABLETRIGGER) self.wait_ready() - output = self._transfer_buffer_numpy(memsize, numch) - self._stop_acquisition() + try: + output = self._transfer_buffer_numpy(memsize, numch) + finally: + self._stop_acquisition() voltages = self.convert_to_voltage(output, mV_range / 1000) @@ -1023,9 +1028,10 @@ def gated_trigger_acquisition(self, mV_range, memsize, pretrigger_size, posttrig self.general_command(pyspcm.M2CMD_CARD_START | pyspcm.M2CMD_CARD_ENABLETRIGGER ) self.wait_ready() - output = self._transfer_buffer_numpy(memsize, numch) - - self._stop_acquisition() + try: + output = self._transfer_buffer_numpy(memsize, numch) + finally: + self._stop_acquisition() voltages = self.convert_to_voltage(output, mV_range / 1000) @@ -1052,9 +1058,10 @@ def single_software_trigger_acquisition_boxcar(self, mV_range, memsize, posttrig self.general_command(pyspcm.M2CMD_CARD_START | pyspcm.M2CMD_CARD_ENABLETRIGGER) self.wait_ready() - output = self._transfer_buffer_numpy( - memsize, numch, bytes_per_sample=4) - self._stop_acquisition() + try: + output = self._transfer_buffer_numpy(memsize, numch, bytes_per_sample=4) + finally: + self._stop_acquisition() voltages = self.convert_to_voltage( output, mV_range / 1000) / self.box_averages() @@ -1082,8 +1089,10 @@ def single_software_trigger_acquisition(self, mV_range, memsize, posttrigger_siz self.general_command(pyspcm.M2CMD_CARD_START | pyspcm.M2CMD_CARD_ENABLETRIGGER) self.wait_ready() - output = self._transfer_buffer_numpy(memsize, numch) - self._stop_acquisition() + try: + output = self._transfer_buffer_numpy(memsize, numch) + finally: + self._stop_acquisition() voltages = self.convert_to_voltage(output, mV_range / 1000) @@ -1163,9 +1172,10 @@ def blockavg_hardware_trigger_acquisition(self, mV_range, nr_averages=10, self.general_command(pyspcm.M2CMD_CARD_START | pyspcm.M2CMD_CARD_ENABLETRIGGER) self.wait_ready() - output = self._transfer_buffer_numpy(memsize, numch, bytes_per_sample=4) / nr_averages - - self._stop_acquisition() + try: + output = self._transfer_buffer_numpy(memsize, numch, bytes_per_sample=4) / nr_averages + finally: + self._stop_acquisition() voltages = self.convert_to_voltage(output, mV_range / 1000) @@ -1267,3 +1277,5 @@ def get_card_memory(self, verbose=0): print('card_memory: %s' % (data.value)) return (data.value) + def _hw_memsize(self, size): + return (size + 15) // 16 * 16 \ No newline at end of file From 6c29797207b68af2912b005fc7b3a79e9c20ec1b Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Tue, 6 Apr 2021 09:04:08 +0200 Subject: [PATCH 3/8] pretrigger is derived by M4i driver --- qcodes_contrib_drivers/drivers/Spectrum/M4i.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py index 983079757..3e742ebcd 100644 --- a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py +++ b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py @@ -800,8 +800,6 @@ def setup_multi_recording(self, posttrigger_size, n_triggers=1, pretrigger_size= self.card_mode(pyspcm.SPC_REC_STD_MULTI) if not pretrigger_size: pretrigger_size = self.pretrigger_memory_size() - else: - self.pretrigger_memory_size(pretrigger_size) posttrigger_size = self._hw_memsize(posttrigger_size) seg_size = self._hw_memsize(posttrigger_size+pretrigger_size) memsize = self._hw_memsize(n_triggers * seg_size) From d29ce9b2e937c2ff5b655374db19a5f52d8fe369 Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Tue, 6 Apr 2021 21:55:50 +0200 Subject: [PATCH 4/8] Updated documentation --- .../drivers/Spectrum/M4i.py | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py index 3e742ebcd..96ea21380 100644 --- a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py +++ b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py @@ -735,35 +735,47 @@ def set_channel_settings(self, channel_index, mV_range, input_path, termination, Args: channel_index (int): channel to update mV_range (float): measurement range for the channel - input_path (int): input path + (buffered path: 200, 500, 1000, 2000, 5000, or 10000) + (HF path: 500, 1000, 2500, or 5000) + input_path (int): input path (0: default/buffered; 1: HF/50 Ohm) termination (None or int): If None, then do not update the - termination + termination (0: 1 MOhm; 1: 50 Ohm) coupling (None or int): Set the ACDC_coupling.If None, then do not - update the coupling + update the coupling (0: DC; 1 AC) compensation (None or int): If None, then do not update the - compensation + compensation (0: off, 1: off) """ # initialize - self.set(f'input_path_{channel_index}', input_path) # 0: default; 1: HF/50 Ohm + self.set(f'input_path_{channel_index}', input_path) if termination is not None: - self.set(f'termination_{channel_index}', termination) # 0: 1 MOhm; 1: 50 Ohm + self.set(f'termination_{channel_index}', termination) if coupling is not None: - self.set(f'ACDC_coupling_{channel_index}', coupling) # 0: DC; 1 AC + self.set(f'ACDC_coupling_{channel_index}', coupling) self.set(f'range_channel_{channel_index}', mV_range) # note: set after voltage range # can only be used with DC coupling and 50 Ohm path (hf) if compensation is not None: self.set(f'ACDC_offs_compensation_{channel_index}', compensation) def set_ext0_OR_trigger_settings(self, trig_mode, termination, coupling, level0, level1=None): + ''' Configures ext0 trigger + Args: + trig_mode: 0: None, 1: Positive edge, 2: Negative edge, 4: Both, 8: High, 16: Low, + 32: Enter window, 64: Leave window, 128: Inside window, 256: Outside window, + 0x01000001: Positive + re-arm, 0x01000002: Negative + re-arm + termination: input termination 0: 1 MOhm, 1: 50 Ohm + coupling: DC/AC input coupling (0: DC, 1: AC) + level0: trigger level [mV] + level1: 2nd level for re-arm and windowed modes. [mV] + ''' self.channel_or_mask(0) - self.external_trigger_mode(trig_mode) # trigger mode - self.trigger_or_mask(pyspcm.SPC_TMASK_EXT0) # external trigger - self.external_trigger_termination(termination) # 1: 50 Ohm - self.external_trigger_input_coupling(coupling) # 0: DC - self.external_trigger_level_0(level0) # mV + self.external_trigger_mode(trig_mode) + self.trigger_or_mask(pyspcm.SPC_TMASK_EXT0) + self.external_trigger_termination(termination) + self.external_trigger_input_coupling(coupling) + self.external_trigger_level_0(level0) if(level1 != None): - self.external_trigger_level_1(level1) # mV + self.external_trigger_level_1(level1) # Note: the levels need to be set in bits, not voltages! (between -8191 to # 8191 for 14 bits) From ade5fe16a7d8a52607fb63ef543773e50cd22b29 Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Thu, 8 Apr 2021 13:51:41 +0200 Subject: [PATCH 5/8] Added 20 MHz low-pass filter setting and boxcar averaging --- .../drivers/Spectrum/M4i.py | 55 ++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py index 96ea21380..335367fc2 100644 --- a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py +++ b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py @@ -408,9 +408,11 @@ def __init__(self, name, cardid='spcm0', **kwargs): pyspcm.SPC_CARDMODE), set_cmd=partial(self._set_param32bit, pyspcm.SPC_CARDMODE), - vals=Enum(pyspcm.SPC_REC_STD_SINGLE, pyspcm.SPC_REC_STD_MULTI, pyspcm.SPC_REC_STD_GATE, pyspcm.SPC_REC_STD_ABA, - pyspcm.SPC_REC_FIFO_SINGLE, pyspcm.SPC_REC_FIFO_MULTI, pyspcm.SPC_REC_FIFO_GATE, - pyspcm.SPC_REC_FIFO_ABA, pyspcm.SPC_REC_STD_AVERAGE, pyspcm.SPC_REC_STD_BOXCAR), + vals=Enum(pyspcm.SPC_REC_STD_SINGLE, pyspcm.SPC_REC_STD_MULTI, + pyspcm.SPC_REC_STD_GATE, pyspcm.SPC_REC_STD_ABA, + pyspcm.SPC_REC_FIFO_SINGLE, pyspcm.SPC_REC_FIFO_MULTI, + pyspcm.SPC_REC_FIFO_GATE, pyspcm.SPC_REC_FIFO_ABA, + pyspcm.SPC_REC_STD_AVERAGE, pyspcm.SPC_REC_STD_BOXCAR), docstring='defines the used operating mode') # wait command @@ -669,7 +671,8 @@ def convert_to_voltage(self, data, input_range): def initialize_channels(self, channels=None, mV_range=1000, input_path=0, termination=0, coupling=0, compensation=None, - memsize=2**12, pretrigger_memsize=16): + memsize=2**12, pretrigger_memsize=16, + lp_filter=None): """ Setup channels of the digitizer for simple readout using Parameters The channels can be read out using the Parameters `channel_0`, @@ -692,7 +695,8 @@ def initialize_channels(self, channels=None, mV_range=1000, input_path=0, channels = range(4) for ch in channels: self.set_channel_settings(ch, mV_range, input_path=input_path, - termination=termination, coupling=coupling, compensation=compensation) + termination=termination, coupling=coupling, + compensation=compensation, lp_filter=lp_filter) allchannels = allchannels + getattr(pyspcm, 'CHANNEL%d' % ch) self.enable_channels(allchannels) @@ -729,7 +733,9 @@ def _read_channel(self, channel, memsize=None): value = np.mean(data[:, channel]) return value - def set_channel_settings(self, channel_index, mV_range, input_path, termination, coupling, compensation=None): + def set_channel_settings(self, channel_index, mV_range, input_path, + termination, coupling, compensation=None, + lp_filter=None): """ Update settings of the specified channel Args: @@ -744,6 +750,7 @@ def set_channel_settings(self, channel_index, mV_range, input_path, termination, update the coupling (0: DC; 1 AC) compensation (None or int): If None, then do not update the compensation (0: off, 1: off) + lp_filter (Optional[int]): enable (1) or disable (0) the 20 MHz low pass filter """ # initialize self.set(f'input_path_{channel_index}', input_path) @@ -751,6 +758,8 @@ def set_channel_settings(self, channel_index, mV_range, input_path, termination, self.set(f'termination_{channel_index}', termination) if coupling is not None: self.set(f'ACDC_coupling_{channel_index}', coupling) + if lp_filter is not None: + self.set(f'anti_aliasing_filter_{channel_index}', lp_filter) self.set(f'range_channel_{channel_index}', mV_range) # note: set after voltage range # can only be used with DC coupling and 50 Ohm path (hf) if compensation is not None: @@ -789,7 +798,8 @@ def set_channel_OR_trigger_settings(self, i, trig_mode, bitlevel0, bitlevel1=Non self.set(f'trigger_channel_{i}_level_1', bitlevel1) self.set(f'trigger_mode_channel_{i}', trig_mode) # trigger mode - def setup_multi_recording(self, posttrigger_size, n_triggers=1, pretrigger_size=None): + def setup_multi_recording(self, posttrigger_size, n_triggers=1, + pretrigger_size=None, boxcar_average=False): """ Setup multi recording. Triggering must have been configured separately. @@ -800,6 +810,7 @@ def setup_multi_recording(self, posttrigger_size, n_triggers=1, pretrigger_size= posttrigger_size (int): size of data trace after triggering n_triggers (int): total number of triggers pretrigger_size (int): size of data trace before triggering + boxcar_average (bool): use mode 'boxcar average' Example: digitizer.setup_multi_recording(size, n_triggers) @@ -809,7 +820,14 @@ def setup_multi_recording(self, posttrigger_size, n_triggers=1, pretrigger_size= data = digitizer.get_data() """ - self.card_mode(pyspcm.SPC_REC_STD_MULTI) + if boxcar_average: + if self.oversampling_factor() != 1: + raise Exception('Averaging with BOXCAR can only be used with ' + 'maximum sample rate') + self.card_mode(pyspcm.SPC_REC_STD_BOXCAR) + else: + self.card_mode(pyspcm.SPC_REC_STD_MULTI) + if not pretrigger_size: pretrigger_size = self.pretrigger_memory_size() posttrigger_size = self._hw_memsize(posttrigger_size) @@ -822,12 +840,14 @@ def setup_multi_recording(self, posttrigger_size, n_triggers=1, pretrigger_size= def start_triggered(self): '''Starts triggered acquisition ''' - self.general_command(pyspcm.M2CMD_CARD_START | pyspcm.M2CMD_CARD_ENABLETRIGGER) + self.general_command(pyspcm.M2CMD_CARD_START + | pyspcm.M2CMD_CARD_ENABLETRIGGER) def get_data(self): ''' Reads measurement data from the digitizer. - The data acquisition must have been started by start_acquisition() or start_triggered(). + The data acquisition must have been started by start_acquisition() or + start_triggered(). Returns: 2D array with voltages per channel in V. @@ -843,7 +863,12 @@ def get_data(self): raise Exception(f'Error waiting for data: (0x{res:04x})') try: - raw_data = self._transfer_buffer_numpy(memsize, numch) + if self.card_mode() == pyspcm.SPC_REC_STD_BOXCAR: + box_averages = self.box_averages() + raw_data = self._transfer_buffer_numpy(memsize, numch, bytes_per_sample=4) + else: + box_averages = 1 + raw_data = self._transfer_buffer_numpy(memsize, numch) finally: self._stop_acquisition() @@ -851,7 +876,7 @@ def get_data(self): voltages = np.zeros((numch, len(raw_data)//numch)) for i,ch in enumerate(active_channels): mV_range = self.get(f'range_channel_{ch}') - voltages[i,:] = raw_data[i::numch] * (mV_range / 1000 / resolution) + voltages[i,:] = raw_data[i::numch] * (mV_range / 1000 / resolution / box_averages) return voltages @@ -1251,8 +1276,10 @@ def _set_param32bit(self, param, value): value = int(value) # convert floating point to int if necessary res = pyspcm.spcm_dwSetParam_i32(self.hCard, param, value) self._last_set_result = res - if res != pyspcm.ERR_OK: - logging.warning(f'SetParam failed. param:0x{param:08X} value:0x{value:08X}: ' + if res == pyspcm.ERR_TIMEOUT: + logging.warning('SetParam timeout') + elif res != pyspcm.ERR_OK: + raise Exception(f'SetParam failed. param:0x{param:08X} value:0x{value:08X}: ' f'result: {_errormsg_dict[res]} (0x{res:08X})') def _invalidate_buf(self, buf_type): From 5582b0c70feb0430cd85794a401d6ab7e1137463 Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Fri, 23 Apr 2021 17:10:22 +0200 Subject: [PATCH 6/8] more details in exception --- qcodes_contrib_drivers/drivers/Spectrum/M4i.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py index 335367fc2..0a3f8dcb9 100644 --- a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py +++ b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py @@ -983,7 +983,8 @@ def _transfer_buffer_numpy(self, memsize: int, numch: int, bytes_per_sample=2) - pyspcm.SPCM_BUF_DATA, pyspcm.SPCM_DIR_CARDTOPC, 0, data_pointer, 0, bytes_per_sample * memsize * numch) self.general_command(pyspcm.M2CMD_DATA_STARTDMA | pyspcm.M2CMD_DATA_WAITDMA) if self._last_set_result != pyspcm.ERR_OK: - raise Exception(f'Error transferring data: (0x{self._last_set_result:04x})') + res = self._last_set_result + raise Exception(f'Error transferring data: {_errormsg_dict[res]} (0x{res:04x})') # convert buffer to numpy array output = np.frombuffer(data_buffer, dtype=sample_ctype) From 8adb5418e1cdef5c996c364b5bfe707eb70b97a9 Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Mon, 30 Aug 2021 18:31:09 +0200 Subject: [PATCH 7/8] Added newline --- qcodes_contrib_drivers/drivers/Spectrum/M4i.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py index 0a3f8dcb9..a0034319d 100644 --- a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py +++ b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py @@ -1316,4 +1316,4 @@ def get_card_memory(self, verbose=0): return (data.value) def _hw_memsize(self, size): - return (size + 15) // 16 * 16 \ No newline at end of file + return (size + 15) // 16 * 16 From 56c818c8f8186f20c7be6364644ed83308e38e19 Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Mon, 11 Oct 2021 13:03:07 +0200 Subject: [PATCH 8/8] Fixed review comments --- qcodes_contrib_drivers/drivers/Spectrum/M4i.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py index a0034319d..a4c590854 100644 --- a/qcodes_contrib_drivers/drivers/Spectrum/M4i.py +++ b/qcodes_contrib_drivers/drivers/Spectrum/M4i.py @@ -766,7 +766,7 @@ def set_channel_settings(self, channel_index, mV_range, input_path, self.set(f'ACDC_offs_compensation_{channel_index}', compensation) def set_ext0_OR_trigger_settings(self, trig_mode, termination, coupling, level0, level1=None): - ''' Configures ext0 trigger + """ Configures ext0 trigger Args: trig_mode: 0: None, 1: Positive edge, 2: Negative edge, 4: Both, 8: High, 16: Low, @@ -776,7 +776,7 @@ def set_ext0_OR_trigger_settings(self, trig_mode, termination, coupling, level0, coupling: DC/AC input coupling (0: DC, 1: AC) level0: trigger level [mV] level1: 2nd level for re-arm and windowed modes. [mV] - ''' + """ self.channel_or_mask(0) self.external_trigger_mode(trig_mode) self.trigger_or_mask(pyspcm.SPC_TMASK_EXT0) @@ -816,6 +816,7 @@ def setup_multi_recording(self, posttrigger_size, n_triggers=1, digitizer.setup_multi_recording(size, n_triggers) digitizer.start_triggered() data = digitizer.get_data() + # do another measurement with same settings digitizer.start_triggered() data = digitizer.get_data() @@ -838,20 +839,20 @@ def setup_multi_recording(self, posttrigger_size, n_triggers=1, self.posttrigger_memory_size(posttrigger_size) def start_triggered(self): - '''Starts triggered acquisition - ''' + """ Starts triggered acquisition + """ self.general_command(pyspcm.M2CMD_CARD_START | pyspcm.M2CMD_CARD_ENABLETRIGGER) def get_data(self): - ''' Reads measurement data from the digitizer. + """ Reads measurement data from the digitizer. The data acquisition must have been started by start_acquisition() or start_triggered(). Returns: 2D array with voltages per channel in V. - ''' + """ active_channels = self.active_channels() memsize = self.data_memory_size.cache() numch = len(active_channels)