diff --git a/core_tools.egg-info/SOURCES.txt b/core_tools.egg-info/SOURCES.txt
index 57af58f2a348d67e514b8e07bef302ba3c1d9902..87e3eea8e34736baf205cb8fdb1bebc93f3e6d26 100644
--- a/core_tools.egg-info/SOURCES.txt
+++ b/core_tools.egg-info/SOURCES.txt
@@ -1,3 +1,4 @@
+README.md
 setup.py
 core_tools/__init__.py
 core_tools.egg-info/PKG-INFO
@@ -34,13 +35,8 @@ core_tools/GUI/param_viewer/param_viewer_GUI_window.py
 core_tools/HVI/__init__.py
 core_tools/HVI/charge_stability_diagram/HVI_charge_stability_diagram.py
 core_tools/HVI/charge_stability_diagram/__init__.py
-core_tools/HVI/charge_stability_diagram_fast/HVI_charge_stability_diagram.py
-core_tools/HVI/charge_stability_diagram_fast/__init__.py
 core_tools/HVI/single_shot_exp/HVI_single_shot.py
 core_tools/HVI/single_shot_exp/__init__.py
-core_tools/HVI/single_shot_exp_SD_corr/HVI_single_shot.py
-core_tools/HVI/single_shot_exp_SD_corr/__init__.py
-core_tools/HVI/single_shot_exp_SD_corr/generate_SD_corr_sequence.py
 core_tools/sweeps/__init__.py
 core_tools/sweeps/pulse_lib_sweep.py
 core_tools/sweeps/Modulated_scans/DEMOD_tests.py
diff --git a/core_tools/GUI/keysight_videomaps/data_getter/scan_generator_Keysight.py b/core_tools/GUI/keysight_videomaps/data_getter/scan_generator_Keysight.py
index 5d003b665aba01c4009b430615f0e1ee2daca0d4..efa219ddfb17fe3175f4e5dab429b442148b6f5c 100644
--- a/core_tools/GUI/keysight_videomaps/data_getter/scan_generator_Keysight.py
+++ b/core_tools/GUI/keysight_videomaps/data_getter/scan_generator_Keysight.py
@@ -5,8 +5,8 @@ Created on Fri Aug  9 16:50:02 2019
 @author: V2
 """
 from qcodes import MultiParameter
-from projects.keysight_measurement.HVI.ChargeStabilityDiagram.HVI_charge_stability_diagram import load_HVI, set_and_compile_HVI, excute_HVI, HVI_ID
-from projects.keysight_measurement.M3102A import DATA_MODE
+from core_tools.HVI.charge_stability_diagram.HVI_charge_stability_diagram import load_HVI, set_and_compile_HVI, excute_HVI, HVI_ID
+from core_tools.drivers.M3102A import DATA_MODE
 import matplotlib.pyplot as plt
 import numpy as np
 import time
diff --git a/core_tools/GUI/keysight_videomaps/liveplotting.py b/core_tools/GUI/keysight_videomaps/liveplotting.py
index 359c2ab3552bb14f4da9b657bbd1f2b9a4931cad..a1bf6fdbb62b1cf6f39600230c451e713e8d093d 100644
--- a/core_tools/GUI/keysight_videomaps/liveplotting.py
+++ b/core_tools/GUI/keysight_videomaps/liveplotting.py
@@ -13,7 +13,7 @@ from .data_getter import scan_generator_Virtual
 from .plotter.plotting_functions import _1D_live_plot, _2D_live_plot
 from qcodes import MultiParameter
 from qcodes.measure import Measure
-from qtt.utilities.tools import addPPTslide
+from core_tools.utility.powerpoint import addPPTslide
 import time
 import logging
 
diff --git a/core_tools/HVI/__pycache__/__init__.cpython-37.pyc b/core_tools/HVI/__pycache__/__init__.cpython-37.pyc
index af264deeb0e449be89943581b48da81e7bec6a57..e4c2bbbd43a89f35f89686d4f261a45c61a94348 100644
Binary files a/core_tools/HVI/__pycache__/__init__.cpython-37.pyc and b/core_tools/HVI/__pycache__/__init__.cpython-37.pyc differ
diff --git a/core_tools/HVI/charge_stability_diagram/HVI_charge_stability_diagram.py b/core_tools/HVI/charge_stability_diagram/HVI_charge_stability_diagram.py
index f9090862f2ec2697eb84f96ebab12f1a76a01c6d..949d19ae53813041454b381646ff29a4141e7020 100644
--- a/core_tools/HVI/charge_stability_diagram/HVI_charge_stability_diagram.py
+++ b/core_tools/HVI/charge_stability_diagram/HVI_charge_stability_diagram.py
@@ -8,7 +8,7 @@ try:
 except:
     warnings.warn("\nAttemting to use a file that needs Keysight AWG libraries. Please install if you need them.\n")
 
-import V2_software.HVI_files.charge_stability_diagram as ct
+import core_tools.HVI.charge_stability_diagram as ct
 import time
 
 HVI_ID = "HVI_charge_stability_diagram.HVI"
@@ -33,12 +33,12 @@ def load_HVI(AWGs, channel_map, *args,**kwargs):
 	HVI = keysightSD1.SD_HVI()
 	a = HVI.open(ct.__file__[:-11] + "HVI_charge_stability_diagram.HVI")
 
-	error = HVI.assignHardwareWithUserNameAndSlot("Module 0",0,2)
-	error = HVI.assignHardwareWithUserNameAndSlot("Module 1",0,3)
-	error = HVI.assignHardwareWithUserNameAndSlot("Module 2",0,4)
-	error = HVI.assignHardwareWithUserNameAndSlot("Module 3",0,5)
-	error = HVI.assignHardwareWithUserNameAndSlot("Module 4",0,6)
-	
+	error1 = HVI.assignHardwareWithUserNameAndSlot("Module 0",0,2)
+	error2 = HVI.assignHardwareWithUserNameAndSlot("Module 1",0,3)
+	error3 = HVI.assignHardwareWithUserNameAndSlot("Module 2",0,4)
+	error4 = HVI.assignHardwareWithUserNameAndSlot("Module 3",0,5)
+	error5 = HVI.assignHardwareWithUserNameAndSlot("Module 4",0,6)
+	print(a, error1, error2, error3, error4, error5)
 	HVI.compile()
 	HVI.load()
 
@@ -87,7 +87,7 @@ def excute_HVI(HVI, AWGs, channel_map, playback_time, n_rep, *args, **kwargs):
 	length = int(playback_time/10 + 20)
 
 	for awgname, awg in AWGs.items():
-		awg.writeRegisterByNumber(3, int(length))
+		awg.awg.writeRegisterByNumber(3, int(length))
 
 	dig = kwargs['digitizer'] 
 	t_single_point = kwargs['t_measure']
@@ -95,12 +95,12 @@ def excute_HVI(HVI, AWGs, channel_map, playback_time, n_rep, *args, **kwargs):
 
 	t_single_point_formatted = int((t_single_point)/10) # divide by 10 since 100MHz clock (160 ns HVI overhead)
 
-	dig.writeRegisterByNumber(2, npt)
-	dig.writeRegisterByNumber(3, t_single_point_formatted)
-	
+	dig.SD_AIN.writeRegisterByNumber(2, npt)
+	dig.SD_AIN.writeRegisterByNumber(3, t_single_point_formatted)
+
 	if 'averaging' in kwargs:
-		dig.set_meas_time(kwargs['t_measure'])
-		dig.set_MAV_filter(16,1)
+		dig.set_meas_time(kwargs['t_measure'], fourchannel = True)
+		dig.set_MAV_filter(16,1, fourchannel = True)
 
 	HVI.start()
 
diff --git a/core_tools/HVI/charge_stability_diagram/__pycache__/HVI_charge_stability_diagram.cpython-37.pyc b/core_tools/HVI/charge_stability_diagram/__pycache__/HVI_charge_stability_diagram.cpython-37.pyc
index 86e01ff890b58bae64f67c5126c2de13adcca9b7..025292859ab786d3674da877c1187e7e8d03aca9 100644
Binary files a/core_tools/HVI/charge_stability_diagram/__pycache__/HVI_charge_stability_diagram.cpython-37.pyc and b/core_tools/HVI/charge_stability_diagram/__pycache__/HVI_charge_stability_diagram.cpython-37.pyc differ
diff --git a/core_tools/HVI/charge_stability_diagram/__pycache__/__init__.cpython-37.pyc b/core_tools/HVI/charge_stability_diagram/__pycache__/__init__.cpython-37.pyc
index dca38e504ef20a07126f6bdee1ad32042b8a70d8..a1d7f140b103c6c8d3f43764e3f5466a6d5dd128 100644
Binary files a/core_tools/HVI/charge_stability_diagram/__pycache__/__init__.cpython-37.pyc and b/core_tools/HVI/charge_stability_diagram/__pycache__/__init__.cpython-37.pyc differ
diff --git a/core_tools/HVI/single_shot_exp/HVI_single_shot.py b/core_tools/HVI/single_shot_exp/HVI_single_shot.py
index e87c7ce3f870eecb9cf73ad575ae65f96bb5679c..dacc568ddb4d6def4fa9b842860f45b2ef087c96 100644
--- a/core_tools/HVI/single_shot_exp/HVI_single_shot.py
+++ b/core_tools/HVI/single_shot_exp/HVI_single_shot.py
@@ -4,7 +4,7 @@ define a function that loads the HVI file that will be used thoughout the experi
 import keysightSD1
 import core_tools.HVI.single_shot_exp as ct
 
-HVI_ID = "HVI_single_shot_test.HVI"
+HVI_ID = "HVI_single_shot.HVI"
 
 
 def load_HVI(AWGs, channel_map, *args,**kwargs):
@@ -24,7 +24,7 @@ def load_HVI(AWGs, channel_map, *args,**kwargs):
 
 			
 	HVI = keysightSD1.SD_HVI()
-	error = HVI.open(ct.__file__[:-11] + "HVI_single_shot_test.HVI")
+	error = HVI.open(ct.__file__[:-11] + "HVI_single_shot.HVI")
 	print(error)
 
 	error = HVI.assignHardwareWithUserNameAndSlot("Module 0",0,2)
@@ -33,6 +33,10 @@ def load_HVI(AWGs, channel_map, *args,**kwargs):
 	print(error)
 	error = HVI.assignHardwareWithUserNameAndSlot("Module 2",0,4)
 	print(error)
+	error = HVI.assignHardwareWithUserNameAndSlot("Module 3",0,5)
+	print(error)
+	error = HVI.assignHardwareWithUserNameAndSlot("Module 4",0,6)
+	print(error)
 
 	error = HVI.compile()
 	print(error)
@@ -87,23 +91,19 @@ def excute_HVI(HVI, AWGs, channel_map, playback_time, n_rep, *args, **kwargs):
 
 	for awgname, awg in AWGs.items():
 		err = awg.awg.writeRegisterByNumber(2, int(nrep))
-		print(err)
 		err = awg.awg.writeRegisterByNumber(3, int(length))
-		print(err)
-
-	# dig = kwargs['digitizer'] 
-	# dig_wait = kwargs['dig_wait']
-
-	# delay_1 = int(dig_wait/10)
-	# dig.writeRegisterByNumber(2, int(nrep))
-	# dig.writeRegisterByNumber(4, int(delay_1))
-	# dig.writeRegisterByNumber(3, int(length-delay_1-2))
 
-	# if 'averaging' in kwargs:
-	# 	dig.set_meas_time(kwargs['t_measure'])
-	# 	dig.set_MAV_filter(16,1)
+	dig = kwargs['digitizer'] 
+	dig_wait = kwargs['dig_wait']
+	print(dig_wait)
+	delay_1 = int(dig_wait/10)
+	err = dig.SD_AIN.writeRegisterByNumber(2, int(nrep))
+	err = dig.SD_AIN.writeRegisterByNumber(4, int(delay_1))
+	err = dig.SD_AIN.writeRegisterByNumber(3, int(length-delay_1-2))
+
+	if 'averaging' in kwargs:
+		dig.set_meas_time(kwargs['t_measure'], fourchannel=True)
+		dig.set_MAV_filter(16,1, fourchannel=True)
 	# start sequence
 	err = AWGs['AWG1'].awg.writeRegisterByNumber(1, 0)
-	print(err)
 	err = AWGs['AWG1'].awg.writeRegisterByNumber(0,int(1))
-	print(err)
diff --git a/core_tools/HVI/single_shot_exp/__pycache__/HVI_single_shot.cpython-37.pyc b/core_tools/HVI/single_shot_exp/__pycache__/HVI_single_shot.cpython-37.pyc
index e6205378733f97db7ef69f3e3ed3f863a0013a72..11561082c9b27a133d61f22a935dc0989b3894a3 100644
Binary files a/core_tools/HVI/single_shot_exp/__pycache__/HVI_single_shot.cpython-37.pyc and b/core_tools/HVI/single_shot_exp/__pycache__/HVI_single_shot.cpython-37.pyc differ
diff --git a/core_tools/HVI/single_shot_exp/__pycache__/__init__.cpython-37.pyc b/core_tools/HVI/single_shot_exp/__pycache__/__init__.cpython-37.pyc
index f1adf6021ec9e7217b4e4cc083e02aa377af54c3..a0ccd6ef1aadb3bc5a81e6231ed0a2fd5d30a2f5 100644
Binary files a/core_tools/HVI/single_shot_exp/__pycache__/__init__.cpython-37.pyc and b/core_tools/HVI/single_shot_exp/__pycache__/__init__.cpython-37.pyc differ
diff --git a/core_tools/__pycache__/__init__.cpython-37.pyc b/core_tools/__pycache__/__init__.cpython-37.pyc
index 67a257e43c02d86619b43d0eafc7d3dfd5a51709..f5eacf945784c6cb047c3403090c6003467ba161 100644
Binary files a/core_tools/__pycache__/__init__.cpython-37.pyc and b/core_tools/__pycache__/__init__.cpython-37.pyc differ
diff --git a/core_tools/drivers/D5a.py b/core_tools/drivers/D5a.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c090ab6df22c49c3aeb9f51edc1343e03c4de99
--- /dev/null
+++ b/core_tools/drivers/D5a.py
@@ -0,0 +1,150 @@
+from qcodes.instrument.base import Instrument
+from qcodes.utils.validators import Enum, Numbers
+import numpy as np
+
+try:
+    from spirack import D5a_module
+except ImportError:
+    raise ImportError(('The D5a_module class could not be found. '
+                       'Try installing it using pip install spirack'))
+
+from functools import partial
+
+
+class D5a(Instrument):
+    """
+    Qcodes driver for the D5a DAC SPI-rack module.
+
+    functions:
+    -   set_dacs_zero   set all DACs to zero voltage
+
+    parameters:
+    -   dacN:       get and set DAC voltage
+    -   stepsizeN   get the minimum step size corresponding to the span
+    -   spanN       get and set the DAC span: '4v uni', '4v bi', or '2.5v bi'
+
+    where N is the DAC number from 1 up to 16
+
+    """
+
+    def __init__(self, name, spi_rack, module, inter_delay=0.1, dac_step=10e-3,
+                 reset_voltages=False, mV=False, number_dacs=16, **kwargs):
+        """ Create instrument for the D5a module.
+
+        The D5a module works with volts as units. For backward compatibility
+        there is the option to allow mV for the dacX parameters.
+
+        The output span of the DAC module can be changed with the spanX
+        command. Be carefull when executing this command with a sample
+        connected as voltage jumps can occur.
+
+        Args:
+            name (str): name of the instrument.
+
+            spi_rack (SPI_rack): instance of the SPI_rack class as defined in
+                the spirack package. This class manages communication with the
+                individual modules.
+
+            module (int): module number as set on the hardware.
+            inter_delay (float): time in seconds, passed to dac parameters of the object
+            dac_step (float): max step size (V or mV), passed to dac parameters of the object
+            reset_voltages (bool): passed to D5a_module constructor
+            mV (bool): if True, then use mV as units in the dac parameters
+            number_dacs (int): number of DACs available. This is 8 for the D5mux
+        """
+        super().__init__(name, **kwargs)
+
+        self.d5a = D5a_module(spi_rack, module, reset_voltages=reset_voltages)
+        self._number_dacs = number_dacs
+
+        self._span_set_map = {
+            '4v uni': 0,
+            '4v bi': 2,
+            '2v bi': 4,
+        }
+
+        self._span_get_map = {v: k for k, v in self._span_set_map.items()}
+
+        self.add_function('set_dacs_zero', call_cmd=self._set_dacs_zero,
+                          docstring='Reset all dacs to zero voltage. No ramping is performed.')
+
+        if mV:
+            self._gain = 1e3
+            unit = 'mV'
+        else:
+            self._gain = 1
+            unit = 'V'
+
+        # make a cache, to get a numerically not rounded value.
+        self.voltage_cache = np.zeros([number_dacs])
+        for i in range(number_dacs):
+            self.voltage_cache[i] = self.__get_dac(i)
+
+        for i in range(self._number_dacs):
+            validator = self._get_validator(i)
+
+            self.add_parameter('dac{}'.format(i + 1),
+                               label='DAC {}'.format(i + 1),
+                               get_cmd=partial(self._get_dac, i),
+                               set_cmd=partial(self._set_dac, i),
+                               unit=unit,
+                               vals=validator,
+                               step=dac_step,
+                               inter_delay=inter_delay)
+
+            self.add_parameter('stepsize{}'.format(i + 1),
+                               get_cmd=partial(self.d5a.get_stepsize, i),
+                               unit='V',
+                               docstring='Returns the smallest voltage step of the DAC.')
+
+            self.add_parameter('span{}'.format(i + 1),
+                               get_cmd=partial(self._get_span, i),
+                               set_cmd=partial(self._set_span, i),
+                               vals=Enum(*self._span_set_map.keys()),
+                               docstring='Change the output span of the DAC. This command also updates the validator.')
+
+    def set_dac_unit(self, unit: str) -> None:
+        """Set the unit of dac parameters"""
+        allowed_values = Enum('mV', 'V')
+        allowed_values.validate(unit)
+        self._gain = {'V': 1, 'mV': 1e3}[unit]
+        for i in range(1, self._number_dacs + 1):
+            setattr(self.parameters[f'dac{i}'], 'unit', unit)
+            setattr(self.parameters[f'dac{i}'], 'vals', self._get_validator(i - 1))
+
+    def _set_dacs_zero(self):
+        for i in range(self._number_dacs):
+            self._set_dac(i, 0.0)
+
+    def _set_dac(self, dac, value):
+        self.voltage_cache[dac] = value
+        return self.d5a.set_voltage(dac, value / self._gain)
+
+    def _get_dac(self, dac):
+        return self.voltage_cache[dac]
+    
+    def __get_dac(self, dac):
+        return self._gain * self.d5a.voltages[dac]
+
+    def _get_span(self, dac):
+        return self._span_get_map[self.d5a.span[dac]]
+
+    def _set_span(self, dac, span_str):
+        self.d5a.change_span_update(dac, self._span_set_map[span_str])
+        self.parameters['dac{}'.format(
+            dac + 1)].vals = self._get_validator(dac)
+
+    def _get_validator(self, dac):
+        span = self.d5a.span[dac]
+
+        if span == D5a_module.range_2V_bi:
+            validator = Numbers(-2 * self._gain, 2 * self._gain)
+        elif span == D5a_module.range_4V_bi:
+            validator = Numbers(-4 * self._gain, 4 * self._gain)
+        elif span == D5a_module.range_4V_uni:
+            validator = Numbers(0, 4 * self._gain)
+        else:
+            msg = 'The found DAC span of {} does not correspond to a known one'
+            raise Exception(msg.format(span))
+
+        return validator
diff --git a/core_tools/drivers/M3102A.py b/core_tools/drivers/M3102A.py
index e33a5eed8d49e5edbdd023cd61332d5597df0793..ced545c3eb9c209e38e1fa63a652b0e6aed8750c 100644
--- a/core_tools/drivers/M3102A.py
+++ b/core_tools/drivers/M3102A.py
@@ -1,6 +1,5 @@
 from qcodes import Instrument, MultiParameter
 from dataclasses import dataclass
-from si_prefix import si_format
 import warnings
 import logging
 import time
@@ -62,7 +61,6 @@ class line_trace(MultiParameter):
     """
     def __init__(self, name, instrument, inst_name , raw=False):
         self.my_instrument = instrument
-        self.name = name
         super().__init__(name=inst_name,
                          names = (name +'_ch1', name +'_ch2'),
                          shapes=((1,),(1,)),
@@ -85,11 +83,10 @@ class line_trace(MultiParameter):
         """
         generate channels mask for start multiple control functions
         """
-        channel_mask = list('0000') # TODO make general version for n channels
+        channel_mask = 0
 
         for i in self.channels:
-            channel_mask[-i] = '1'
-        channel_mask = int(''.join(channel_mask), 2)
+            channel_mask += 1 << (i - 1)
 
         return channel_mask
 
@@ -102,104 +99,133 @@ class line_trace(MultiParameter):
 
         return self.get_data()
 
-    def get_data(self):
-        """
-        Get data from the cards
 
-        TODO : IMPLEMENT BUFFERING METHODS for faster data transfers.
-        """
+    def _read_channel_data(self, channel_number, channel_data_raw):
+        start = time.perf_counter()
+        i = 0
+        points_aquired = 0
+        while points_aquired < len(channel_data_raw):
+            np_ready = self.my_instrument.SD_AIN.DAQcounterRead(channel_number)
+            check_error(np_ready)
+
+            if np_ready + points_aquired > len(channel_data_raw):
+                np_ready = len(channel_data_raw) - points_aquired
+                logging.error("more data points in digitizer ram then what is being collected.")
+
+
+            if np_ready > 0:
+                # Always read with a timeout to prevent infinite blocking of HW (and reboot of system).
+                # There are np_ready points available. This can be read in 1 second.
+                req_points = self.my_instrument.SD_AIN.DAQread(channel_number, np_ready, 1000)
+                check_error(req_points)
+                if not type(req_points) is int and len(req_points) != np_ready:
+                    logging.error(f'DAQread failure. ready:{np_ready} read:{len(req_points)}')
+
+                channel_data_raw[points_aquired: points_aquired + np_ready] = req_points
+                points_aquired = points_aquired + np_ready
+                i = 0
+
+            if np_ready == 0:
+                i+=1
+                time.sleep(0.001)
+                if i > 100:
+                    logging.error(f"digitizer did not manage to collect enough data points for channel {channel_number}, "
+                                  f"returning zeros. ({points_aquired})")
+                    break
+
+        logging.info(f'channel {channel_number}: retrieved {points_aquired} points in {(time.perf_counter()-start)*1000:3.1f} ms')
+
+
+    def _get_data_normal(self):
         data_out = tuple()
 
         for channel_property in self.my_instrument.channel_properties.values():
-            start = time.perf_counter()
-            if self.my_instrument.mode == MODES.AVERAGE and channel_property.number in [2,4]:
-                continue
             if channel_property.active == False:
                 continue
 
             # make flat data structures.
-            if self.my_instrument.mode == MODES.AVERAGE:
-                channel_data_raw = np.zeros( [ channel_property.cycles*10], np.uint16)
-            else:
-                channel_data_raw = np.zeros( [ channel_property.cycles*channel_property.points_per_cycle], np.double)
-
-            # get data out of the buffer
-            np_ready = self.my_instrument.SD_AIN.DAQcounterRead(channel_property.number)
-            check_error(np_ready)
-
-            i = 0
-            points_aquired = 0
-            while points_aquired < len(channel_data_raw):
-#                print('np_ready = %i' % np_ready)
-                if np_ready + points_aquired > len(channel_data_raw):
-                    np_ready = len(channel_data_raw) - points_aquired
-                    logging.error("more data points in digitizer ram then what is being collected.")
+            channel_data_raw = np.zeros([channel_property.cycles*channel_property.points_per_cycle], np.double)
+
+            self._read_channel_data(channel_property.number, channel_data_raw)
+
+            # format the data with correct amplitude
+            # convert 16-bit to relative scale (-1.0 .. 1.0)
+            f = 1 / 32768
+            # multiply with the relevant channel amplitude (standard in volt -> mV!)
+            f *= channel_property.full_scale*1000
+            # inplace multiplication on numpy array is fast
+            channel_data_raw *= f
+
+            # reshape for [repetitions, time] and average
+            channel_data_raw = channel_data_raw.reshape([channel_property.cycles, channel_property.points_per_cycle])
+
+            if self.my_instrument.data_mode == DATA_MODE.FULL:
+                data_out += (channel_data_raw, )
+            elif self.my_instrument.data_mode == DATA_MODE.AVERAGE_TIME:
+                data_out += (np.average(channel_data_raw, axis = 1), )
+            elif self.my_instrument.data_mode == DATA_MODE.AVERAGE_CYCLES:
+                data_out += (np.average(channel_data_raw, axis = 0), )
+            elif self.my_instrument.data_mode == DATA_MODE.AVERAGE_TIME_AND_CYCLES:
+                data_out += (np.average(channel_data_raw), )
 
+        return data_out
 
-                if np_ready > 0:
-                    # Always read with a timeout to prevent infinite blocking of HW (and reboot of system).
-                    # There are np_ready points available. This can be read in 1 second.
-                    req_points = self.my_instrument.SD_AIN.DAQread(channel_property.number, np_ready, 1000)
-                    check_error(req_points)
-                    if not type(req_points) is int and len(req_points) != np_ready:
-                        logging.error(f'DAQread failure. ready:{np_ready} read:{len(req_points)}')
 
-                    channel_data_raw[points_aquired: points_aquired + np_ready] = req_points
-                    points_aquired = points_aquired + np_ready
-                    i = 0
+    def _get_data_average(self):
+        data_out = tuple()
 
-                if np_ready == 0:
-                    i+=1
-                    time.sleep(0.001)
-                    if i > 100:
-                        logging.error(f"digitizer did not manage to collect enough data points, returning zeros. {points_aquired}, {channel_property}")
-                        break
+         # note that we are acquirering two channels at the same time in this mode.
+        for channel_property in self.my_instrument.channel_properties.values():
+            # averaging mode: channels are read in pairs.
+            if channel_property.number in [2,4]:
+                # even numbers are read with odd channel.
+                continue
+            if channel_property.number not in self.channels and channel_property.number+1 not in self.channels:
+                # don't read anything if both channels not active.
+                continue
 
-                np_ready = self.my_instrument.SD_AIN.DAQcounterRead(channel_property.number)
+            # make flat data structures.
+            channel_data_raw = np.zeros([channel_property.cycles*10], np.uint16)
 
-            logging.info(f'channel {channel_property.number}: retrieved {points_aquired} points in {(time.perf_counter()-start)*1000:3.1f} ms')
+            self._read_channel_data(channel_property.number, channel_data_raw)
 
             # format the data
-            if self.my_instrument.mode == MODES.AVERAGE:
-                # note that we are acquirering two channels at the same time in this mode..
-                channel_data_raw = channel_data_raw.reshape([channel_property.cycles, 10]).transpose().astype(np.int32)
-                channel_data = np.empty([2,channel_property.cycles])
-                channel_data[0] = ((channel_data_raw[1] & 2**16-1) << 16) | (channel_data_raw[0] & 2**16-1)
-                channel_data[1] = ((channel_data_raw[3] & 2**16-1) << 16) | (channel_data_raw[2] & 2**16-1)
-
-                # correct amplitude,
-                channel_data[0] *= 5 * 2/(channel_property.t_measure-160)*channel_property.full_scale / 2**15 # outputs V, 5 for the aquisition in blocks of 5
-                channel_data[1] *= 5 * 2/(channel_property.t_measure-160)*channel_property.full_scale / 2**15 # outputs V
-
-                # make sure only the data of the wanted channel is returned.
-                if channel_property.number in self.channels:
-                    if self.my_instrument.data_mode in [DATA_MODE.AVERAGE_CYCLES, DATA_MODE.AVERAGE_TIME_AND_CYCLES]:
-                       data_out += (np.average(channel_data[0]), )
-                    else:
-                        data_out += (channel_data[0], )
-
-                if channel_property.number + 1 in self.channels:
-                    if self.my_instrument.data_mode in [DATA_MODE.AVERAGE_CYCLES, DATA_MODE.AVERAGE_TIME_AND_CYCLES]:
-                       data_out += (np.average(channel_data[1]), )
-                    else:
-                        data_out += (channel_data[1], )
-            else:
-                # correct amplitude,
-                channel_data_raw /= 32768. #convert bit to relative scale (-1/1)
-                channel_data_raw *= channel_property.full_scale*1000 #multiply with the relevant channel amplitude (standard in volt -> mV!)
-                channel_data_raw = channel_data_raw.reshape([channel_property.cycles, channel_property.points_per_cycle])
-
-                if self.my_instrument.data_mode == DATA_MODE.FULL:
-                    data_out += (channel_data_raw, )
-                elif self.my_instrument.data_mode == DATA_MODE.AVERAGE_TIME:
-                    data_out += (np.average(channel_data_raw, axis = 1), )
-                elif self.my_instrument.data_mode == DATA_MODE.AVERAGE_CYCLES:
-                    data_out += (np.average(channel_data_raw, axis = 0), )
-                elif self.my_instrument.data_mode == DATA_MODE.AVERAGE_TIME_AND_CYCLES:
-                    data_out += (np.average(channel_data_raw), )
+            channel_data_raw = channel_data_raw.reshape([channel_property.cycles, 10]).transpose().astype(np.int32)
+            channel_data = np.empty([2,channel_property.cycles])
+            channel_data[0] = ((channel_data_raw[1] & 2**16-1) << 16) | (channel_data_raw[0] & 2**16-1)
+            channel_data[1] = ((channel_data_raw[3] & 2**16-1) << 16) | (channel_data_raw[2] & 2**16-1)
+
+            # correct amplitude,
+            # outputs V, 5 for the aquisition in blocks of 5
+            channel_data[0] *= 5 * 2/(channel_property.t_measure-160)*channel_property.full_scale / 2**15
+            channel_data[1] *= 5 * 2/(channel_property.t_measure-160)*channel_property.full_scale / 2**15
+
+            # only add the data of the selected channels.
+            if channel_property.number in self.channels:
+                if self.my_instrument.data_mode in [DATA_MODE.AVERAGE_CYCLES, DATA_MODE.AVERAGE_TIME_AND_CYCLES]:
+                   data_out += (np.average(channel_data[0]), )
+                else:
+                    data_out += (channel_data[0], )
+
+            if channel_property.number + 1 in self.channels:
+                if self.my_instrument.data_mode in [DATA_MODE.AVERAGE_CYCLES, DATA_MODE.AVERAGE_TIME_AND_CYCLES]:
+                   data_out += (np.average(channel_data[1]), )
+                else:
+                    data_out += (channel_data[1], )
 
         return data_out
 
+
+    def get_data(self):
+        """
+        Get data from the cards
+        """
+        if self.my_instrument.mode == MODES.NORMAL:
+            return self._get_data_normal()
+        else:
+            return self._get_data_average()
+
+
     def start_digitizers(self):
         # start digizers.
         self.my_instrument.daq_start_multiple(self.channel_mask)
@@ -277,13 +303,11 @@ class SD_DIG(Instrument):
         super().__init__(name)
         """
         init keysight digitizer
-
         Args:
             name (str) : name of the digitizer
             chassis (int) : chassis number
             slot (int) : slot in the chassis where the digitizer is.
             n_channels (int) : number of channels on the digitizer card.
-
         NOTE: channels start for number 1! (e.g. channel 1, channel 2, channel 3, channel 4)
         """
         self.SD_AIN = keysightSD1.SD_AIN()
@@ -350,7 +374,7 @@ class SD_DIG(Instrument):
     def set_operating_mode(self, operation_mode):
         """
         Modes for operation
-
+        Only affects daq start and daq trigger in get_raw().
         Args:
             operation_mode (int) : mode of operation
                 0 : use software triggers (does call start and trigger in software)
@@ -362,7 +386,6 @@ class SD_DIG(Instrument):
     def set_active_channels(self, channels):
         """
         set the active channels:
-
         Args:
             channels (list) : channels numbers that need to be used
         """
@@ -393,7 +416,6 @@ class SD_DIG(Instrument):
     def set_daq_settings(self, channel, n_cycles, t_measure, sample_rate = 500e6, DAQ_trigger_delay = 0, DAQ_trigger_mode = 1):
         """
         quickset for the daq settings
-
         Args:
             n_cycles (int) : number of trigger to record.
             t_measure (float) : time to measure (unit : ns)
@@ -455,7 +477,6 @@ class SD_DIG(Instrument):
     def daq_flush(self, daq, verbose=False):
         """
         Flush the specified DAQ
-
         Args:
             daq (int)       : the DAQ you are flushing
         """
@@ -463,7 +484,6 @@ class SD_DIG(Instrument):
 
     def daq_stop(self, daq, verbose=False):
         """ Stop acquiring data on the specified DAQ
-
         Args:
             daq (int)       : the DAQ you are disabling
         """
@@ -472,7 +492,6 @@ class SD_DIG(Instrument):
     def writeRegisterByNumber(self, regNumber, varValue):
         """
         Write to a register of the AWG, by reffreing to the register number
-
         Args:
             regNumber (int) : number of the registry (0 to 16)
             varValue (int/double) : value to be written into the registry
@@ -483,7 +502,6 @@ class SD_DIG(Instrument):
 
     def daq_start_multiple(self, daq_mask, verbose=False):
         """ Start acquiring data or waiting for a trigger on the specified DAQs
-
         Args:
             daq_mask (int)  : the input DAQs you are enabling, composed as a bitmask
                               where the LSB is for DAQ_0, bit 1 is for DAQ_1 etc.
@@ -492,7 +510,6 @@ class SD_DIG(Instrument):
 
     def daq_trigger_multiple(self, daq_mask, verbose=False):
         """ Manually trigger the specified DAQs
-
         Args:
             daq_mask (int)  : the DAQs you are triggering, composed as a bitmask
                               where the LSB is for DAQ_0, bit 1 is for DAQ_1 etc.
@@ -541,7 +558,6 @@ class SD_DIG(Instrument):
     def set_digitizer_software(self, t_measure, cycles, sample_rate= 500e6, data_mode = DATA_MODE.FULL, channels = [1,2], Vmax = 2, fourchannel = False):
         """
         quick set of minumal settings to make it work.
-
         Args:
             t_measure (float) : time to measure in ns
             cycles (int) : number of cycles
@@ -568,7 +584,6 @@ class SD_DIG(Instrument):
     def set_digitizer_analog_trg(self, t_measure, cycles, sample_rate= 500e6, data_mode = DATA_MODE.FULL, channels = [1,2], Vmax = 2):
         """
         quick set of minumal settings to make it work.
-
         Args:
             t_measure (float) : time to measure in ns
             cycles (int) : number of cycles
@@ -592,10 +607,9 @@ class SD_DIG(Instrument):
             self.set_meas_time(t_measure)
             self.set_MAV_filter(16,1)
 
-    def set_digitizer_HVI(self, t_measure, cycles, sample_rate= 500e6, data_mode = DATA_MODE.FULL, channels = [1,2], Vmax = 2):
+    def set_digitizer_HVI(self, t_measure, cycles, sample_rate=500e6, data_mode=DATA_MODE.FULL, channels=[1,2], Vmax=2):
         """
-        quick set of minumal settings to make it work.
-
+        quick set of minimal settings to make it work.
         Args:
             t_measure (float) : time to measure in ns
             cycles (int) : number of cycles
@@ -617,8 +631,8 @@ class SD_DIG(Instrument):
 if __name__ == '__main__':
 #%%
           # load digitizer
-    digitizer1.close()
-    digitizer1 = SD_DIG("digitizer1", chassis = 0, slot = 5)
+    # digitizer1.close()
+    digitizer1 = SD_DIG("digitizer1", chassis = 0, slot = 6)
 
     # clear all ram (normally not needed, but just to sure)
     digitizer1.daq_flush(1)
@@ -626,44 +640,45 @@ if __name__ == '__main__':
     digitizer1.daq_flush(3)
     digitizer1.daq_flush(4)
 
-    digitizer1.set_aquisition_mode(MODES.AVERAGE)
+    # digitizer1.set_aquisition_mode(MODES.AVERAGE)
 
     #%%
     # simple example
-
+    digitizer1.set_digitizer_software(1e3, 10, sample_rate=500e6, data_mode=DATA_MODE.AVERAGE_TIME_AND_CYCLES, channels=[1,2], Vmax=0.25, fourchannel=False)
+    print(digitizer1.measure())
     ####################################
     #  settings (feel free to change)  #
-    ####################################
-    t_list = np.logspace(2.3, 2.7, 20)
-    res =[]
-    for t in t_list:
-        cycles = 1000
-        t_measure = t #e3 # ns
-        ####################################
-
-
-        # show some multiparameter properties
-        # print(digitizer1.measure.shapes)
-        # print(digitizer1.measure.setpoint_units)
-        # print(digitizer1.measure.setpoints)
-        # # measure the parameter
-        digitizer1.set_digitizer_software(t_measure, cycles, data_mode=DATA_MODE.FULL, channels = [1,2])
-        digitizer1.set_MAV_filter()
-        data = digitizer1.measure()
-        #    print(data)
-    #    plt.clf()
-        #    plt.plot(data[0][:,2], 'o-')
-        #    plt.plot(data[0][:,3], 'o-')
-    #    plt.plot(data[1], 'o-')
-        # print(data[0].shape, data[1].shape)
-
-        res.append(np.mean(data[1]))
-
-    #t_list = t_list-166
-    #res = np.array(res)/np.array(t_list)
-    plt.figure(2)
-    plt.clf()
-    plt.plot(t_list, res, 'o-')
+    # ####################################
+    # t_list = np.logspace(2.3, 2.7, 20)
+    # res =[]
+    # for t in t_list:
+    #     cycles = 1000
+    #     t_measure = t #e3 # ns
+    #     ####################################
+
+
+    #     # show some multiparameter properties
+    #     # print(digitizer1.measure.shapes)
+    #     # print(digitizer1.measure.setpoint_units)
+    #     # print(digitizer1.measure.setpoints)
+    #     # # measure the parameter
+    #     digitizer1.set_digitizer_software(t_measure, cycles, data_mode=DATA_MODE.FULL, channels = [1,2])
+    #     digitizer1.set_MAV_filter()
+    #     data = digitizer1.measure()
+    #     #    print(data)
+    # #    plt.clf()
+    #     #    plt.plot(data[0][:,2], 'o-')
+    #     #    plt.plot(data[0][:,3], 'o-')
+    # #    plt.plot(data[1], 'o-')
+    #     # print(data[0].shape, data[1].shape)
+
+    #     res.append(np.mean(data[1]))
+
+    # #t_list = t_list-166
+    # #res = np.array(res)/np.array(t_list)
+    # plt.figure(2)
+    # plt.clf()
+    # plt.plot(t_list, res, 'o-')
 
     #def fit_func(x, m, q):
     #    return x*m+q
@@ -672,4 +687,4 @@ if __name__ == '__main__':
     #param, var = scipy.optimize.curve_fit(fit_func, t_list, res)
     #plt.plot(np.linspace(0, max(t_list), 50), fit_func(np.linspace(0, max(t_list), 50), *param))
     #plt.xlabel('Integration time (ns)')
-    #plt.title('Intercept: %.2f' %param[1])
+    #plt.title('Intercept: %.2f' %param[1])
\ No newline at end of file
diff --git a/core_tools/drivers/M3102_firmware_loader.py b/core_tools/drivers/M3102_firmware_loader.py
new file mode 100644
index 0000000000000000000000000000000000000000..b864d4a7d42ae107709c15b0a885ee9d59dbe798
--- /dev/null
+++ b/core_tools/drivers/M3102_firmware_loader.py
@@ -0,0 +1,33 @@
+"""
+Standard firmware files for the digitizer of the V2 setup.
+
+CLEAN : normal image, revert to the normal AWG.
+AVG : special image with MAV and interator. A custom qcodes driver needs to be used for that.
+"""
+M3102A_CLEAN = "C:/V2_code/FPGA_Bitstreams/Digitizer_FW1.41/clean_4_41.sbp"
+M3102A_AVG = "C:/V2_code/FPGA_Bitstreams/Digitizer_FW1.41/averaging_firmware_1_41.sbp"
+
+from core_tools.drivers.M3102A import MODES
+
+def firmware_loader(dig, file):
+	"""
+	load a custom firmware image on board of the digitizer
+
+	Args:
+		dig <SD_dig (qcodes instrument) : digitzer object
+		file (string) : location where to find the new firmware
+	"""
+	err = dig.SD_AIN.FPGAload(file)
+	if file == M3102A_CLEAN:
+		dig.set_aquisition_mode(MODES.NORMAL)
+	else:
+		dig.set_aquisition_mode(MODES.AVERAGE)
+
+
+	if err == 0 :
+		print("Succesful upload of the firmware")
+	else:
+		print("upload failed, error code {} (see keysight manual for instructions)".format(err))
+
+if __name__ == '__main__':
+	firmware_loader(dig, M3102A_CLEAN)
\ No newline at end of file
diff --git a/core_tools/drivers/__pycache__/harware.cpython-37.pyc b/core_tools/drivers/__pycache__/harware.cpython-37.pyc
index 32c2c717096282368fea81d1b9cb50781245af55..c3e4adfe5a18a54939049a4880a82f3b6e1e8b51 100644
Binary files a/core_tools/drivers/__pycache__/harware.cpython-37.pyc and b/core_tools/drivers/__pycache__/harware.cpython-37.pyc differ
diff --git a/core_tools/sweeps/__pycache__/__init__.cpython-37.pyc b/core_tools/sweeps/__pycache__/__init__.cpython-37.pyc
index 650a11821c64ae38743818bdb25ae9b01d2f65f5..29eb9bf6d3c33b76b1150636201cf6da20d80b84 100644
Binary files a/core_tools/sweeps/__pycache__/__init__.cpython-37.pyc and b/core_tools/sweeps/__pycache__/__init__.cpython-37.pyc differ
diff --git a/core_tools/sweeps/__pycache__/pulse_lib_sweep.cpython-37.pyc b/core_tools/sweeps/__pycache__/pulse_lib_sweep.cpython-37.pyc
index e6dbc34485b95182467b711fabed9cfe25d189c8..f93f2884ba64901205a20bd2845f59363058b6e0 100644
Binary files a/core_tools/sweeps/__pycache__/pulse_lib_sweep.cpython-37.pyc and b/core_tools/sweeps/__pycache__/pulse_lib_sweep.cpython-37.pyc differ
diff --git a/core_tools/sweeps/exp_readout_runner.py b/core_tools/sweeps/exp_readout_runner.py
new file mode 100644
index 0000000000000000000000000000000000000000..94a777ed826ae579234f3c300e610ff85b0c63e7
--- /dev/null
+++ b/core_tools/sweeps/exp_readout_runner.py
@@ -0,0 +1,145 @@
+from core_tools.utility.digitizer_param_conversions import IQ_to_scalar, down_sampler, Elzerman_param, get_phase_compentation_IQ_signal
+
+from core_tools.GUI.keysight_videomaps.data_getter.scan_generator_Keysight import construct_1D_scan_fast
+from core_tools.HVI.single_shot_exp.HVI_single_shot import load_HVI, set_and_compile_HVI, excute_HVI, HVI_ID
+from core_tools.sweeps.pulse_lib_sweep import spin_qubit_exp
+from core_tools.utility.mk_digitizer_param import get_digitizer_param
+from core_tools.utility.dig_utility import autoconfig_digitizer
+from core_tools.drivers.M3102_firmware_loader import M3102A_CLEAN, M3102A_AVG
+from core_tools.drivers.M3102A import DATA_MODE
+
+import qcodes as qc
+
+def measure_optimal_phase_IQ_sigal(SD = 'vSD2_P'):
+    '''
+    Measure the phase rotation that is needed to rotate the sigal of the SD into the I line of the IQ plane
+    
+    Args:
+        SD (str) : gates name to sweep (sigal generator)
+    Returns : 
+        phase (double) : phase rotation in the IQ plane
+    '''
+    station = qc.Station.default
+    #make scan around SD to make phase estimation
+    param = construct_1D_scan_fast(SD, 10, 400, 50000, False, station.pulse, station.dig, [1,2], 100e6)
+    return get_phase_compentation_IQ_signal(param)
+
+def run_readout_exp(name, segment, t_meas, n_rep, show_raw_traces, threshold, blib_down, phase):
+    '''
+    autoconfig utility for runing a readout experiment
+
+    Args:
+        name (str) : name of the measurement to be run
+        segment (segment_container) : segment to be played on the AWG
+        t_meas (double) : measurement time to set to the digitizer (will be removed when digitzer updates)
+        n_rep (double) : number times a experiment needs to be repeated
+        show_raw_traces (bool) : show raw traces
+        threshold (double) : threadhold for the detection
+        blib_down (bool) : direction of the blib (if true, downward blib expected).
+        phase (double) : phase rotation in the IQ plane for SD
+    Returns:
+        data (qCoDeS dataset) : returns the dataset of the measured data.
+    '''
+
+    station = qc.Station.default
+
+    data_mode = DATA_MODE.FULL
+    if n_rep == 1:
+        data_mode = DATA_MODE.AVERAGE_CYCLES
+    
+    dig_param = get_digitizer_param(station.dig, t_meas, n_rep, data_mode)
+    IQ_meas_param = IQ_to_scalar(dig_param, phase)
+    down_sampled_seq = down_sampler(IQ_meas_param, 0.3e6, None)
+    elzerman_det = Elzerman_param(down_sampled_seq, threshold, blib_down)
+    
+    if not isinstance(segment, list):
+        segment = [segment]
+    
+    my_seq = station.pulse.mk_sequence(segment)
+    
+    settings = dict()
+    settings['averaging'] = False
+
+    my_seq.add_HVI(HVI_ID, load_HVI, set_and_compile_HVI, excute_HVI, digitizer = station.dig, **settings)
+    my_seq.n_rep = n_rep
+    my_seq.neutralise = True
+
+    if show_raw_traces == True:
+        my_exp = spin_qubit_exp(name, my_seq, down_sampled_seq)
+    else:
+        my_exp = spin_qubit_exp(name, my_seq, elzerman_det)
+    data = my_exp.run()
+
+    elzerman_pulse = None
+    my_exp = None
+    my_seq = None
+    station.pulse.uploader.release_memory()
+
+    return data
+
+
+def run_PSB_exp(name, segment, t_meas, n_rep, raw_traces, phase):
+    '''
+    autoconfig utility for runing a readout experiment with PSB
+    (will use fpga averaging when possible)
+
+    Args:
+        name (str) : name of the measurement to be run
+        segment (segment_container) : segment to be played on the AWG
+        t_meas (double) : measurement time to set to the digitizer (will be removed when digitzer updates)
+        n_rep (double) : number times a experiment needs to be repeated
+        raw_traces (bool) : show raw traces
+        phase (double) : phase rotation in the IQ plane for SD
+    Returns:
+        data (qCoDeS dataset) : returns the dataset of the measured data.
+    '''
+    station = qc.Station.default
+    
+    if raw_traces == False:
+        autoconfig_digitizer(M3102A_AVG)
+        data_mode = DATA_MODE.AVERAGE_TIME_AND_CYCLES
+        if n_rep == 1:
+            data_mode = DATA_MODE.AVERAGE_CYCLES
+        station.dig.set_data_handling_mode(data_mode)
+    else:
+        autoconfig_digitizer(M3102A_CLEAN)
+        data_mode = DATA_MODE.FULL
+        station.dig.set_data_handling_mode(data_mode)
+        
+    
+
+    dig_param = get_digitizer_param(station.dig, t_meas, n_rep, data_mode)
+    IQ_meas_param = IQ_to_scalar(dig_param, phase)
+    
+    if raw_traces == True:
+        down_sampled_seq = down_sampler(IQ_meas_param, 5e6)
+
+    my_seq = station.pulse.mk_sequence([segment])
+    
+    settings = dict()
+    settings['averaging'] = True
+    if raw_traces == True:
+        settings['averaging'] = False
+
+
+    my_seq.add_HVI(HVI_ID, load_HVI, set_and_compile_HVI, excute_HVI, digitizer = station.dig, **settings)
+    my_seq.n_rep = n_rep
+    my_seq.neutralise = True
+
+    if raw_traces == True:
+        my_exp = spin_qubit_exp(name, my_seq, down_sampled_seq)
+    else:
+        my_exp = spin_qubit_exp(name, my_seq, IQ_meas_param)
+    data = my_exp.run()
+
+    elzerman_pulse = None
+    my_exp = None
+    my_seq = None
+    station.pulse.uploader.release_memory()
+
+    autoconfig_digitizer(M3102A_AVG)
+
+    return data
+
+
+
diff --git a/core_tools/utility/dig_utility.py b/core_tools/utility/dig_utility.py
index 693f7e3cf8837edd61624f4658966121ed39be42..f608bd6943fb599fb86d9dd0a95b1ad21dc23f02 100644
--- a/core_tools/utility/dig_utility.py
+++ b/core_tools/utility/dig_utility.py
@@ -1,5 +1,5 @@
-from V2_software.drivers.M3102A.M3102A import SD_DIG, MODES, DATA_MODE
-from V2_software.drivers.M3102A.M3102_firmware_loader import firmware_loader, M3102A_CLEAN, M3102A_AVG
+from core_tools.drivers.M3102A import SD_DIG, MODES, DATA_MODE
+from core_tools.drivers.M3102_firmware_loader import firmware_loader, M3102A_CLEAN, M3102A_AVG
 import qcodes as qc
 
 def autoconfig_digitizer(firmware = M3102A_AVG):
@@ -12,7 +12,7 @@ def autoconfig_digitizer(firmware = M3102A_AVG):
 	t_measure = 1e4 #1ms (unit ns)
 	cycles = 1 # just measure once.
 
-	station.dig.set_digitizer_software(t_measure, cycles, data_mode=DATA_MODE.AVERAGE_TIME_AND_CYCLES, channels = [1,2])
+	station.dig.set_digitizer_software(t_measure, cycles, data_mode=DATA_MODE.AVERAGE_TIME_AND_CYCLES, channels = [1,2], fourchannel=True )
 
 	station.dig.daq_flush(1)
 	station.dig.daq_flush(2)
diff --git a/core_tools/utility/digitizer_param_conversions.py b/core_tools/utility/digitizer_param_conversions.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9584d7373fe1bd21a573cee7d7117f31e70b1da
--- /dev/null
+++ b/core_tools/utility/digitizer_param_conversions.py
@@ -0,0 +1,210 @@
+from qcodes import MultiParameter
+from scipy import signal
+import numpy as np
+import matplotlib.pyplot as plt
+import lmfit
+
+
+def lin_func(x, a, b):
+    return x*a + b
+
+
+#TODO move to better location at later point
+def get_phase_compentation_IQ_signal(param):
+    '''
+    Args:
+        param (Multiparameter) : parameter with a get method that returns an I and Q array of a signal.
+    Return:
+        rotation_angle (double) : angle at which to rotate the signal to project it to the I plane.
+    '''
+    data_in = param.get()
+    
+    model = lmfit.Model(lin_func)
+    
+    a = np.average(data_in[0])/np.average(data_in[1])
+    b = 0
+    param = model.make_params(a=a, b=b)
+    result = model.fit(data_in[1], param, x=data_in[0])
+    
+    angle = np.angle(1+1j*result.best_values['a'])
+    
+    # print(result.best_values['a'])
+    # print(angle)
+    # x= np.linspace(-120, 20, 100) 
+    # plt.plot(x, lin_func(x, result.best_values['a'], result.best_values['b']))
+    # plt.plot(data_in[0], data_in[1])
+    # new_data = data_in[0] +1j*data_in[1]
+    # new_data *= np.exp(1j*(-angle))
+    # plt.plot(np.real(new_data), np.imag(new_data))
+
+    # new_data = x +1j*lin_func(x, result.best_values['a'], result.best_values['b'])
+    # new_data *= np.exp(1j*(-angle))
+    # plt.plot(np.real(new_data), np.imag(new_data))
+    return angle
+
+class IQ_to_scalar(MultiParameter):
+    '''
+    parameter that converts IQ data of the digitizer into scalar data
+    NOTE : ONLY ONE IQ CHANNEL PAIR SUPPORTED ATM !!!
+    '''
+    def __init__(self, digitizer, phase_rotation):
+        '''
+        Args:
+            digitizer (MultiParameter) : instrument class of the digitizer
+            phase_rotation (double) : rotation in the IQ plane (will keep only I)
+        '''
+        self.dig = digitizer
+        self.phase_rotation = phase_rotation
+        self.sample_rate = digitizer.sample_rate
+
+        names = ('RF_readout_amplitude',)
+        super().__init__('IQ_to_scalar_convertor', names=names, shapes=(self.dig.shapes[0],),
+                         labels=(self.dig.labels[0],), units=(self.dig.units[0],),
+                         setpoints=(self.dig.setpoints[0],),
+                         setpoint_names=(self.dig.setpoint_names[0],),
+                         setpoint_labels=(self.dig.setpoint_labels[0],),
+                         setpoint_units=(self.dig.setpoint_units[0],))
+
+    def get_raw(self):
+        data_in = self.dig.get_raw()
+        data_out = (data_in[0] + 1j*data_in[1])*np.exp(1j*self.phase_rotation)
+
+        return (data_out.real, )
+
+
+class down_sampler(MultiParameter):
+    '''
+    Down sampler for a digitizer, given a certain bandwidth, take make blibs for example more clear.
+    '''
+    def __init__(self, digitizer, bandwidth, highpass = None):
+        '''
+        Args: 
+            digitizer (MultiParameter) : instrument class of the digitizer 
+            bandwidth (double) : wanted bandwidth (3db point of a butterworth filter)
+            highpass (double) : if defined, sets a highpass filter at a given freq
+        '''
+        self.dig = digitizer
+        self.bandwidth = bandwidth
+        self.highpass = highpass
+        self.sample_rate = digitizer.sample_rate
+        self.sample_drop_rate = int(self.dig.sample_rate/bandwidth/2)
+
+        shapes, setpoints = self.get_shape()
+        super().__init__('Elzerman_state_differentiator', names=digitizer.names, shapes=shapes,
+                         labels=digitizer.labels, units=digitizer.units, setpoints=setpoints,
+                         setpoint_names=digitizer.setpoint_names,
+                         setpoint_labels=digitizer.setpoint_labels,
+                         setpoint_units=digitizer.setpoint_units)
+
+    def get_raw(self):
+        data_out = tuple()
+        data_in = self.dig.get()
+
+        for i in range(len(data_in)):
+            filtered_data = np.empty(self.shapes[0])
+
+            if filtered_data.ndim == 2:
+                for j in range(len(filtered_data)):
+                    filtered_data[j,:] = filter_data(data_in[i][j], self.bandwidth, self.dig.sample_rate, 'lowpass')[::self.sample_drop_rate]
+                
+                    if self.highpass is not None:
+                        filtered_data[j,:] =  filter_data(filtered_data[j], self.highpass,  self.sample_rate/self.sample_drop_rate, 'highpass')
+            else:
+                filtered_data[:] = filter_data(data_in[i], self.bandwidth, self.dig.sample_rate, 'lowpass')[::self.sample_drop_rate]
+                
+                if self.highpass is not None:
+                    filtered_data[:] =  filter_data(filtered_data[:], self.highpass, self.sample_rate/self.sample_drop_rate, 'highpass')
+
+            data_out += (filtered_data,)
+
+        return tuple(data_out)
+
+    def get_shape(self):
+        shapes, setpoints = tuple(), tuple()
+
+        for i in range(len(self.dig.names)):
+            setpt = self.dig.setpoints[i]
+            shape = tuple()
+
+            if len(setpt) > 1:
+                setpt = (setpt[0], np.asarray(setpt[1]).flatten()[::self.sample_drop_rate], )
+                shape = (len(setpt[0]), len(setpt[1]))
+            else:
+                setpt = (np.asarray(setpt).flatten()[::self.sample_drop_rate], )
+                shape = setpt[0].shape
+
+            shapes += (shape, )
+            setpoints += (setpt, )
+
+        return shapes, setpoints
+
+    def update_shape(self):
+        self.shapes, self.setpoints = self.get_shape()
+
+class Elzerman_param(MultiParameter):
+    """
+    parameter that aims to detect blibs.
+    expected that the input data is already well filtered.
+    """
+    def __init__(self, digitizer, threshold, blib_down):
+        '''
+        Args:
+            digitizer (MultiParameter) :  parameter providing the data for the dip detection
+            threshold (double) : threadhold for the detection
+            blib_down (bool) : direction of the blib (if true, downward blib expected).
+        '''
+        self.dig = digitizer
+        self.threshold = threshold  
+        self.blib_down = blib_down
+
+        names = tuple()
+        shapes = tuple()
+        labels = tuple()
+        units = tuple()
+        setpoints = tuple()
+
+        for i in range(len(digitizer.names)):
+            names += ("readout_signal_ch{}".format(i), )
+            shapes += ((), )
+            labels += ('Spin probability' ,)
+            units += ('%', )
+            
+            setpoints += ((),)
+
+        super().__init__('Elzerman_state_differentiaor', names=names, shapes=shapes,
+                         labels=labels, units=units, setpoints=setpoints)
+
+    
+    def get_raw(self):
+        # expected format from the getter <tuple<np.ndarray[ndim=2,dtype=double]>>
+        data_in = self.dig.get()
+        data_out = []
+
+        for data in data_in:
+            if self.blib_down == True:
+                blib_pt = np.where(data<self.threshold)[0]
+            else :
+                blib_pt = np.where(data>self.threshold)[0]
+            
+            data_out.append(np.unique(blib_pt).size/data.shape[0])
+
+        return data_out
+
+def filter_data(data, cutoff, fs, pass_zero, order = 4):
+    '''
+    filter noise out of a dataset
+    Args:
+        data (np.ndarray) : data array to filter
+        cutoff (double) : cutoff in HZ (not that this is the last 0db point and not -3db)
+        fs (double) : sample rate of the signal
+        pass_zero (str) : 'lowpass' or 'highpass'
+    '''
+    b, a = signal.butter(order, cutoff/(fs/2), pass_zero)
+    return signal.filtfilt(b, a, data)
+
+
+if __name__ == '__main__':
+    a = np.zeros([1000])
+    a[100:400] = 1
+
+    filter_data(a, 10e3, 2e6, 'highpass')
\ No newline at end of file
diff --git a/core_tools/utility/powerpoint.py b/core_tools/utility/powerpoint.py
index 4761a3ee62ae8b408cd5eb7dab69b8b6ab004311..324aacb28b0763cbf52a1536520b89d9f455c4b6 100644
--- a/core_tools/utility/powerpoint.py
+++ b/core_tools/utility/powerpoint.py
@@ -309,7 +309,7 @@ try:
                 notes = '\n' + extranotes + '\n' + notes
             if gates is not None:
                 notes = 'gates: ' + str(gates.allvalues()) + '\n\n' + notes
-        if isinstance(notes, qcodes.DataSet):
+        if isinstance(notes, qcodes.data.data_set.DataSet):
             notes = reshape_metadata(notes, printformat='s', add_gates=True)
 
         if notes is not None:
diff --git a/example_T2Hahn.py b/example_T2Hahn.py
new file mode 100644
index 0000000000000000000000000000000000000000..6544e84e2b1aa467394443d7a0b1c368ba912dcb
--- /dev/null
+++ b/example_T2Hahn.py
@@ -0,0 +1,48 @@
+from pulse_templates.utility.plotting import plot_seg
+from pulse_templates.demo_pulse_lib.virtual_awg import get_demo_lib
+from pulse_templates.utility.oper import add_block, add_ramp
+from pulse_templates.utility.plotting import plot_seg
+
+from pulse_templates.coherent_control.single_qubit_gates.standard_set import single_qubit_std_set
+from pulse_templates.coherent_control.single_qubit_gates.single_qubit_gates import single_qubit_gate_spec
+from pulse_templates.elzerman_pulses.basic_elzerman_pulse import elzerman_read
+from pulse_templates.coherent_control.single_qubit_gates.T2_meas import T2_hahn
+
+from pulse_lib.segments.utility.looping import linspace
+
+pulse = get_demo_lib('quad')
+
+INIT = pulse.mk_segment()
+MANIP = pulse.mk_segment()
+READ = pulse.mk_segment()
+
+# assume 1QD -- elzerman init
+t_init = 50e3
+gates = ('vP4',)
+p_0 = (0, )
+
+add_block(INIT, t_init, gates, p_0)
+
+
+
+# T2 hahn
+MANIP.vP4 += 20
+xpi2 = single_qubit_gate_spec('qubit4_MW', 1.1e8, 1000, MW_power=120, padding=2) #X
+xpi = single_qubit_gate_spec('qubit4_MW', 1.1e8, 2000, MW_power=120, padding=2) #X2
+
+ss_set = single_qubit_std_set()
+ss_set.X = xpi2
+ss_set.X2 = xpi
+
+times = linspace(100, 1e5, 100, axis=0, name='time', unit='ns')
+
+T2_hahn(MANIP, ss_set, times, 1e6)
+
+#
+p_read = (0, )
+t_read = 100e3
+
+elzerman_read(READ, gates, t_read, p_read, disable_trigger=False)
+
+# run exp()
+run_exp([INIT, MANIP, READ], nrep = 100, ..)
diff --git a/keysightSD1/__pycache__/__init__.cpython-37.pyc b/keysightSD1/__pycache__/__init__.cpython-37.pyc
index 6d004f27c47df9774e6f467424ec2855aaa75840..407f3c1af412b5f482b6cd8d7407260cfda814db 100644
Binary files a/keysightSD1/__pycache__/__init__.cpython-37.pyc and b/keysightSD1/__pycache__/__init__.cpython-37.pyc differ