diff --git a/qcodes/instrument_drivers/AlazarTech/ATS.py b/qcodes/instrument_drivers/AlazarTech/ATS.py index 4fea97e06d95..016f1d9a8cca 100644 --- a/qcodes/instrument_drivers/AlazarTech/ATS.py +++ b/qcodes/instrument_drivers/AlazarTech/ATS.py @@ -2,6 +2,7 @@ import logging import numpy as np import os +import inspect from qcodes.instrument.base import Instrument from qcodes.instrument.parameter import Parameter @@ -198,6 +199,10 @@ def __init__(self, name, system_id=1, board_id=1, dll_path=None, **kwargs): self.buffer_list = [] + # Some ATS models do not support a bwlimit. This flag defines if the + # ATS supports a bwlimit or not. True by default. + self._bwlimit_support = True + def get_idn(self): board_kind = self._board_names[ self._ATS_dll.AlazarGetBoardKind(self._handle)] @@ -334,13 +339,14 @@ def config(self, clock_source=None, sample_rate=None, clock_edge=None, for i in range(1, self.channels + 1): self._call_dll('AlazarInputControl', - self._handle, i, + self._handle, 2**(i-1), # Channel in binary format self.parameters['coupling' + str(i)], self.parameters['channel_range' + str(i)], self.parameters['impedance' + str(i)]) - self._call_dll('AlazarSetBWLimit', - self._handle, i, - self.parameters['bwlimit' + str(i)]) + if self._bwlimit_support: + self._call_dll('AlazarSetBWLimit', + self._handle, i, + self.parameters['bwlimit' + str(i)]) self._call_dll('AlazarSetTriggerOperation', self._handle, self.trigger_operation, @@ -796,12 +802,59 @@ def __init__(self, name, alazar_name, **kwargs): str(alazar_name) + " was found on this instrument server") + self._acquisitionkwargs = {} + # Obtain a list of all valid ATS acquisition kwargs + # These will be the kwargs that can be added to acquisitionkwargs + self._acquisitionkwargs_names = list(inspect.signature( + self.alazar.acquire).parameters.keys()) + # Remove acquisition_controller, because it is not JSON-compatible + self._acquisitionkwargs_names.remove('acquisition_controller') + self.add_parameter(name='acquisitionkwargs', + get_cmd=lambda: self._acquisitionkwargs) + def _get_alazar(self): return self.alazar def _set_alazar(self, alazar): self.alazar = alazar + def get_acquisitionkwarg(self, kwarg): + """ + Obtain an acquisitionkwarg for the ATS. + It first checks if the kwarg is an actual ATS acquisitionkwarg, + and raises an error otherwise. + It then checks if the kwarg is in ATS_controller._acquisitionkwargs. + If not, it will retrieve the ATS latest parameter value + + Args: + kwarg: acquisitionkwarg to look for + + Returns: + Value of the acquisitionkwarg + """ + assert kwarg in self._acquisitionkwargs_names, \ + "Kwarg {} is not a valid ATS acquisitionkwarg".format(kwarg) + if kwarg in self._acquisition_kwargs.keys(): + return self._acquisition_kwargs[kwarg] + else: + # Must get latest value, since it will not be updated in ATS + return self.alazar.parameters[kwarg].get_latest() + + def update_acquisitionkwargs(self, **kwargs): + """ + Update the ATS_controller acquisitionkwargs, but only if all kwargs are + actually valid kwargs in the function ATS.acquire() + Args: + kwargs: Updated ATS acquisition kwargs + Returns: + None + """ + kwargs_valid = all(map( + lambda kwarg: kwarg in self._acquisitionkwargs_names, + kwargs.keys())) + assert kwargs_valid, 'Not all kwargs are valid ATS acquisitionkwargs' + self._acquisitionkwargs.update(**kwargs) + def pre_start_capture(self): """ Use this method to prepare yourself for the data acquisition diff --git a/qcodes/instrument_drivers/AlazarTech/ATS9870.py b/qcodes/instrument_drivers/AlazarTech/ATS9870.py index 32cd383de1e5..61e1cb1954b9 100644 --- a/qcodes/instrument_drivers/AlazarTech/ATS9870.py +++ b/qcodes/instrument_drivers/AlazarTech/ATS9870.py @@ -94,7 +94,7 @@ def __init__(self, name, **kwargs): parameter_class=AlazarParameter, label='Trigger Engine ' + i, unit=None, - value='TRIG_ENGINE_' + ('J' if i == 0 else 'K'), + value='TRIG_ENGINE_'+('J' if i == '1' else 'K'), byte_to_value_dict={0: 'TRIG_ENGINE_J', 1: 'TRIG_ENGINE_K'}) self.add_parameter(name='trigger_source' + i, diff --git a/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py b/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py index 83527773c216..b90d6e4ba8d6 100644 --- a/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py +++ b/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py @@ -1,13 +1,146 @@ from .ATS import AcquisitionController import math import numpy as np +from qcodes.instrument.parameter import ManualParameter +from qcodes.utils import validators as vals + + +class Basic_AcquisitionController(AcquisitionController): + """Basic AcquisitionController tested on ATS9360 + returns unprocessed data averaged by record with 2 channels + """ + def __init__(self, name, alazar_name, **kwargs): + self.samples_per_record = None + self.records_per_buffer = None + self.buffers_per_acquisition = None + self.buffer = None + super().__init__(name, alazar_name, **kwargs) + self.alazar = self._get_alazar() + + self.add_parameter(name='average_mode', + parameter_class=ManualParameter, + initial_value='trace', + vals=vals.Enum('none', 'trace', 'point')) + + + self.buffer_idx = 0 + # Names and shapes must have initial value, even though they will be + # overwritten in set_acquisitionkwargs. If we don't do this, the + # RemoteInstrument will not recognize that it returns multiple values. + self.add_parameter(name="acquisition", + names=['channel_signal'], + get_cmd=self.do_acquisition, + shapes=((),), + snapshot_value=False) + + def setup(self, **kwargs): + """ + This function sets up the ATS for an acquisition. + In particular, it updates the acquisition kwargs, and the attributes + for the parameter acquisition. + These attributes depend on the controller's average_mode. + This function must be performed after setting the acquisitionkwargs, + and before starting an actual Loop + """ + + self.update_acquisitionkwargs(**kwargs) + + channel_selection = self.get_acquisitionkwarg('channel_selection') + samples_per_record = self.get_acquisitionkwarg('samples_per_record') + records_per_buffer = self.get_acquisitionkwarg('records_per_buffer') + buffers_per_acquisition = self.get_acquisitionkwarg( + 'buffers_per_acquisition') + + self.acquisition.names = tuple( + ['Channel_{}_signal'.format(ch) for ch in channel_selection]) + + self.acquisition.labels = self.acquisition.names + self.acquisition.units = ['V'*len(channel_selection)] + + if self.average_mode() == 'point': + self.acquisition.shapes = tuple([()]*len(channel_selection)) + elif self.average_mode() == 'trace': + shape = (samples_per_record,) + self.acquisition.shapes = tuple([shape] * len(channel_selection)) + else: + shape = (records_per_buffer * buffers_per_acquisition, + samples_per_record) + self.acquisition.shapes = tuple([shape] * len(channel_selection)) + + def do_acquisition(self): + value = self._get_alazar().acquire(acquisition_controller=self, + **self.acquisitionkwargs()) + return value + + def pre_start_capture(self): + alazar = self._get_alazar() + number_of_channels = len(alazar.channel_selection.get()) + self.buffer_idx = 0 + if self.average_mode() in ['point', 'trace']: + self.buffer = np.zeros(alazar.samples_per_record.get() * + alazar.records_per_buffer.get() * + number_of_channels) + else: + self.buffer = np.zeros((alazar.buffers_per_acquisition.get(), + alazar.samples_per_record.get() * + alazar.records_per_buffer.get() * + number_of_channels)) + + def pre_acquire(self): + # gets called after 'AlazarStartCapture' + pass + + def handle_buffer(self, data): + if self.buffer_idx < self.buffers_per_acquisition: + if self.average_mode() in ['point', 'trace']: + self.buffer += data + else: + self.buffer[self.buffer_idx] = data + else: + print('*'*20+'\nIgnoring extra ATS buffer') + self.buffer_idx += 1 + + def post_acquire(self): + # Perform averaging over records. + # The averaging mode depends on parameter average_mode + records_per_acquisition = self.buffers_per_acquisition * \ + self.records_per_buffer + + def channel_offset(ch): + return ch * self.samples_per_record * self.records_per_buffer + + if self.average_mode() == 'none': + records = [self.buffer[:, + channel_offset(ch):channel_offset(ch+1)].reshape( + (records_per_acquisition, self.samples_per_record)) + for ch in range(self.number_of_channels)] + elif self.average_mode() == 'trace': + records = [np.zeros(self.samples_per_record) for _ in + range(self.number_of_channels)] + + for channel in range(self.number_of_channels): + for i in range(self.records_per_buffer): + i0 = channel_offset(channel) + i * self.samples_per_record + i1 = i0 + self.samples_per_record + records[channel] += \ + self.buffer[i0:i1] / records_per_acquisition + elif self.average_mode() == 'point': + trace_length = self.samples_per_record * self.records_per_buffer + records = [np.mean(self.buffer[i*trace_length:(i+1)*trace_length]) / + records_per_acquisition + for i in range(self.number_of_channels)] + + # Scale datapoints + for i, record in enumerate(records): + channel_range = eval('self.alazar.channel_range{}()'.format(i + 1)) + records[i] = 2 * (record / 2 ** 16 - 0.5) * channel_range + return records # DFT AcquisitionController class DFT_AcquisitionController(AcquisitionController): def __init__(self, name, alazar_name, demodulation_frequency, **kwargs): self.demodulation_frequency = demodulation_frequency - self.acquisitionkwargs = {'acquisition_controller': self} self.samples_per_record = None self.records_per_buffer = None self.buffers_per_acquisition = None @@ -21,13 +154,6 @@ def __init__(self, name, alazar_name, demodulation_frequency, **kwargs): super().__init__(name, alazar_name, **kwargs) self.add_parameter("acquisition", get_cmd=self.do_acquisition) - def set_acquisitionkwargs(self, **kwargs): - self.acquisitionkwargs.update(**kwargs) - - def do_acquisition(self): - value = self._get_alazar().acquire(**self.acquisitionkwargs) - return value - def pre_start_capture(self): alazar = self._get_alazar() self.samples_per_record = alazar.samples_per_record.get()