From 3a09603e3ad3e28c1c3db2c04ad99ddb1d8e0633 Mon Sep 17 00:00:00 2001 From: Simon Zihlmann Date: Tue, 29 Aug 2023 10:24:00 +0200 Subject: [PATCH 1/2] add parameters for a single iq measurements (PointIQ), similar to PointMagPhase parameters fix function that checks for properly set sweep. --- .../drivers/CopperMountain/M5180.py | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/CopperMountain/M5180.py b/qcodes_contrib_drivers/drivers/CopperMountain/M5180.py index 4f993867a..22e017ab9 100644 --- a/qcodes_contrib_drivers/drivers/CopperMountain/M5180.py +++ b/qcodes_contrib_drivers/drivers/CopperMountain/M5180.py @@ -149,7 +149,7 @@ def get_raw(self) -> Tuple[ParamRawDataType, ParamRawDataType]: assert isinstance(self.instrument, M5180) # check that npts, start and stop fullfill requirements if point_check_sweep_first is True. - if self.instrument.point_check_sweep_first: + if self.instrument.point_check_sweep_first(): if self.instrument.npts() != 2: raise ValueError('Npts is not 2 but {}. Please set it to 2'.format(self.instrument.npts())) if self.instrument.stop() - self.instrument.start() != 1: @@ -173,7 +173,79 @@ def get_raw(self) -> Tuple[ParamRawDataType, ParamRawDataType]: # Return the average of the trace, which will have "start" as # its setpoint sxx_mean = np.mean(sxx) - return 20*math.log10(abs(sxx_mean)), cmath.phase(sxx_mean) + return 20*math.log10(abs(sxx_mean)), (cmath.phase(sxx_mean)) + + + +class PointIQ(MultiParameter): + """ + Returns the average Sxx of a frequency sweep, in terms of I and Q. + Work around for a CW mode where only one point is read. + npts=2 and stop = start + 1 (in Hz) is required. + """ + + def __init__(self, + name: str, + instrument: "M5180", + **kwargs: Any, + ) -> None: + """I and Q measurement of a single point at start + frequency. + + Args: + name (str): Name of point measurement + instrument: Instrument to which parameter is bound to. + """ + + super().__init__( + name, + instrument=instrument, + names=( + f"{instrument.short_name}_{name}_i", + f"{instrument.short_name}_{name}_q"), + labels=( + f"{instrument.short_name} {name} i", + f"{instrument.short_name} {name} q", + ), + units=("V", "V"), + setpoints=((), (),), + shapes=((), (),), + **kwargs, + ) + + def get_raw(self) -> Tuple[ParamRawDataType, ParamRawDataType]: + """Gets data from instrument + + Returns: + Tuple[ParamRawDataType, ...]: I, Q + """ + + assert isinstance(self.instrument, M5180) + # check that npts, start and stop fullfill requirements if point_check_sweep_first is True. + if self.instrument.point_check_sweep_first(): + if self.instrument.npts() != 2: + raise ValueError('Npts is not 2 but {}. Please set it to 2'.format(self.instrument.npts())) + if self.instrument.stop() - self.instrument.start() != 1: + raise ValueError('Stop-start is not 1 Hz but {} Hz. Please adjust' + 'start or stop.'.format(self.instrument.stop()-self.instrument.start())) + + self.instrument.write('CALC1:PAR:COUN 1') # 1 trace + self.instrument.write('CALC1:PAR1:DEF {}'.format(self.name[-3:])) + self.instrument.trigger_source('bus') # set the trigger to bus + self.instrument.write('TRIG:SEQ:SING') # Trigger a single sweep + self.instrument.ask('*OPC?') # Wait for measurement to complete + + # get data from instrument + self.instrument.write('CALC1:TRAC1:FORM SMITH') # ensure correct format + sxx_raw = self.instrument.ask("CALC1:TRAC1:DATA:FDAT?") + + # Get data as numpy array + sxx = np.fromstring(sxx_raw, dtype=float, sep=',') + + # Return the average of the trace, which will have "start" as + # its setpoint + return np.mean(sxx[0::2]), np.mean(sxx[1::2]) + class M5180(VisaInstrument): @@ -397,6 +469,18 @@ def __init__(self, name : str, self.add_parameter(name='point_s22', parameter_class=PointMagPhase) + self.add_parameter(name='point_s11_iq', + parameter_class=PointIQ) + + self.add_parameter(name='point_s12_iq', + parameter_class=PointIQ) + + self.add_parameter(name='point_s21_iq', + parameter_class=PointIQ) + + self.add_parameter(name='point_s22_iq', + parameter_class=PointIQ) + self.add_parameter(name="point_check_sweep_first", parameter_class=ManualParameter, initial_value=True, From 58353fe250b4fd91e4c0558dd941fb518a519db3 Mon Sep 17 00:00:00 2001 From: Simon Zihlmann Date: Tue, 29 Aug 2023 11:59:07 +0200 Subject: [PATCH 2/2] improvements of the ITest driver including: - initializing the DAC is not changing any outputs by default (there is a flag to reset it to 0V and 12 range) - ramping is now by default in exponential mode to be consistent with the idea of ramping qcodes parameters (step, inter_delay, post_delay) - implementation of autorange parameter - global zeroing and printing functions --- qcodes_contrib_drivers/drivers/Bilt/ITest.py | 154 +++++++++++++++---- 1 file changed, 120 insertions(+), 34 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Bilt/ITest.py b/qcodes_contrib_drivers/drivers/Bilt/ITest.py index c2b0753f3..787945959 100644 --- a/qcodes_contrib_drivers/drivers/Bilt/ITest.py +++ b/qcodes_contrib_drivers/drivers/Bilt/ITest.py @@ -1,6 +1,8 @@ # This Python file uses the following encoding: utf-8 # Loick Le Guevel, 2019 # Etienne Dumur , 2021 +# Simon Zihlmann , 2021 +# Victor Millory , 2021 from typing import Union, Tuple, Any from functools import partial @@ -42,7 +44,10 @@ def __init__(self, parent: Instrument, get_cmd=partial(self._parent._get_voltage, chan_num), get_parser=float, set_cmd=partial(self._parent._set_voltage, chan_num), - vals=vals.Numbers(-50, 50) + inter_delay=self._parent._v_inter_delay, + post_delay=self._parent._v_post_delay, + step=self._parent._v_step, + vals=vals.Numbers(-12, 12) ) self.add_parameter('i', @@ -70,6 +75,7 @@ def __init__(self, parent: Instrument, get_parser=str, set_cmd=partial(self._parent._set_output_function, chan_num), set_parser=str, + initial_value='exp', vals=vals.Enum('ramp', 'exp') ) @@ -79,31 +85,39 @@ def __init__(self, parent: Instrument, set_cmd=partial(self._parent._set_chan_range, chan_num), set_parser=float, get_cmd=partial(self._parent._get_chan_range, chan_num), - get_parser=float) + get_parser=float, + vals=vals.Numbers(1.2, 12) + ) self.add_parameter('state', docstring='State of the channel {on, off}.', get_cmd=partial(self._parent._get_chan_state, chan_num), set_cmd=partial(self._parent._set_chan_state, chan_num), val_mapping=create_on_off_val_mapping(on_val='1', - off_val='0')) + off_val='0') + ) self.add_parameter('pos_sat', get_cmd=partial(self._parent._get_chan_pos_sat, chan_num), get_parser=str, set_cmd=partial(self._parent._set_chan_pos_sat, chan_num), - set_parser=str,) + set_parser=str, + initial_value=12 + ) self.add_parameter('neg_sat', get_cmd=partial(self._parent._get_chan_neg_sat, chan_num), get_parser=str, set_cmd=partial(self._parent._set_chan_neg_sat, chan_num), - set_parser=str,) + set_parser=str, + initial_value=-12 + ) self.add_parameter('bilt_name', set_cmd=partial(self._parent._set_chan_name, chan_num), set_parser=str, - initial_value=f'Chan{chan_num:02d}') + initial_value=f'Chan{chan_num:02d}' + ) self.add_parameter('synchronous_enable', docstring='Is the channel in synchronous mode.', @@ -111,7 +125,8 @@ def __init__(self, parent: Instrument, get_parser=bool, set_cmd=None, vals=vals.Bool(), - initial_value=True) + initial_value=False + ) self.add_parameter('synchronous_delay', docstring='Time between to voltage measurement in second.', @@ -119,7 +134,8 @@ def __init__(self, parent: Instrument, get_parser=float, set_cmd=None, vals=vals.Numbers(1e-3, 10), - initial_value=1e-3) + initial_value=1e-3 + ) self.add_parameter('synchronous_threshold', docstring='Threshold to unblock communication in volt.', @@ -127,7 +143,17 @@ def __init__(self, parent: Instrument, get_parser=float, set_cmd=None, vals=vals.Numbers(0, 1e-3), - initial_value=1e-5) + initial_value=1e-5 + ) + + self.add_parameter('v_autorange', + docstring='If the voltage autorange is activated.', + get_cmd=partial(self._parent._get_chan_v_autorange, chan_num), + get_parser=bool, + set_cmd=partial(self._parent._set_chan_v_autorange, chan_num), + vals=vals.Bool(), + initial_value=False + ) def start(self) -> None: @@ -143,7 +169,11 @@ def stop(self) -> None: """ self._parent._set_chan_state(self.chan_num, '0') - + def clear_alarm(self) -> None: + """ + Clear the alarm and warnings of the channel. + """ + self._parent._clear_chan_alarm(self.chan_num) class iTestMultiChannelParameter(MultiChannelInstrumentParameter): """ @@ -162,9 +192,12 @@ def __init__(self,name:str, address:str, num_chans:int=16, init_start:bool=False, - synchronous_enable:bool=True, + synchronous_enable:bool=False, synchronous_delay:float=1, synchronous_threshold:float=1e-5, + v_inter_delay:float=5e-3, + v_post_delay:float=45e-3, # settling time to 99% + v_step:float=20e-3, **kwargs: Any) -> None: """ Instantiate the instrument. @@ -173,8 +206,8 @@ def __init__(self,name:str, name: The instrument name used by qcodes address: The VISA name of the resource num_chans: Number of channels to assign. Default: 16 - init_start: If true set all channels to 0V, 1.2V range and switch - then on. + init_start: If true: set all channels to 0V, 12V range, exponential mode and switch + them on. synchronous_enable: If true, block the communication until the set voltage is reached. The communication is block through a simple while loop with a waiting time "synchronous_delay" at each iteration until the @@ -182,6 +215,9 @@ def __init__(self,name:str, "synchronous_threshold". synchronous_delay: Time between to voltage measurement in second. synchronous_threshold: Threshold to unblock communication in volt. + v_inter_delay: delay in units of s between setting new value of the voltage parameter, defaults to 5e-3. + v_post_delay: delay in units of s after setting voltage parameter to final value, defaults to 45e-3. + v_step: max step size of the voltage parameter in units of V, defaults to 20e-3. Returns: ITest object @@ -193,6 +229,9 @@ def __init__(self,name:str, self.idn = self.get_idn() self.num_chans = num_chans + self._v_inter_delay = v_inter_delay + self._v_post_delay = v_post_delay + self._v_step = v_step self.chan_range = range(1,self.num_chans+1) # Create the channels @@ -205,9 +244,6 @@ def __init__(self,name:str, channel = iTestChannel(self, name='chan{:02}'.format(i), chan_num=i) - channel.synchronous_enable(synchronous_enable) - channel.synchronous_delay(synchronous_delay) - channel.synchronous_threshold(synchronous_threshold) channels.append(channel) self.add_submodule('ch{:02}'.format(i),channel) @@ -216,13 +252,16 @@ def __init__(self,name:str, if init_start: for channel in self.channels: + channel.stop() channel.v.set(0) - channel.v_range(1.2) + channel.v_range(12) + channel.v_autorange(False) + channel.synchronous_enable(False) + channel.output_mode('exp') channel.start() self.connect_message() - def _set_voltage(self, chan:int, v_set:float) -> None: """ @@ -241,7 +280,6 @@ def _set_voltage(self, chan:int, sleep(self.channels[chan-1].synchronous_delay()) v = self._get_voltage(chan) - def _get_voltage(self, chan:int) -> float: """ Get cmd for the chXX_v parameter @@ -256,7 +294,6 @@ def _get_voltage(self, chan:int) -> float: return float(self.ask('{}MEAS:VOLT?'.format(chan_id))) - def _get_current(self, chan:int) -> float: """ Get cmd for the chXX_i parameter @@ -271,7 +308,6 @@ def _get_current(self, chan:int) -> float: return float(self.ask('{}MEAS:CURR?'.format(chan_id))) - def _set_ramp_slope(self, chan:int, slope:float) -> None: """ @@ -284,7 +320,6 @@ def _set_ramp_slope(self, chan:int, chan_id = self.chan_to_id(chan) self.write('{}VOLT:SLOP {:.8f}'.format(chan_id, slope)) - def _get_ramp_slope(self, chan:int) -> str: """ Get slope of chXX @@ -298,7 +333,6 @@ def _get_ramp_slope(self, chan:int) -> str: chan_id = self.chan_to_id(chan) return self.ask('{}VOLT:SLOP?'.format(chan_id)) - def _set_output_function(self, chan:int, outf:str) -> None: """ @@ -319,7 +353,6 @@ def _set_output_function(self, chan:int, self.write(chan_id + 'trig:input ' + mode) - def _get_output_function(self, chan:int) -> str: """ Get output volage update function @@ -339,7 +372,6 @@ def _get_output_function(self, chan:int) -> str: else: raise ValueError('Got unexpected output function mode: {}.'.format(mode)) - def _set_chan_range(self, chan:int, volt: float) -> None: """ @@ -350,8 +382,16 @@ def _set_chan_range(self, chan:int, volt : Voltage range (1.2 or 12) """ chan_id = self.chan_to_id(chan) - self.write(chan_id + 'VOLT:RANGE ' + str(volt)) - + if self._get_chan_state(chan)=='1': + print('Channel {} is on and therefore the range cannot be changed. Turn it off first.'.format(chan)) + else: + # update the pos and neg saturation parameter + self._set_chan_pos_sat(chan, abs(volt)) + self._set_chan_neg_sat(chan, -abs(volt)) + # self.channels[chan-1].v.vals=vals.Numbers(-abs(volt), abs(volt)) #does not work, throws an error at init since channels are not yet attached to instrument + # --> solve problem by moving all the communication functions to the level of the channel and not on the leel of instrument. Like this other parameters from the same channel are easily accessible via self.parameter + # change the range + self.write(chan_id + 'VOLT:RANGE ' + str(volt)) def _get_chan_range(self, chan:int) -> str: """ @@ -367,6 +407,35 @@ def _get_chan_range(self, chan:int) -> str: return self.ask(chan_id + 'VOLT:RANGE?')[:-2] + def _get_chan_v_autorange(self, chan:int) -> bool: + """ + Get the channel voltage autorange state + + Args: + chan: The 1-indexed channel number + + Returns: + chXX_v_autorange parameter + """ + chan_id = self.chan_to_id(chan) + v_autorange_state = self.ask('{}VOLT:RANGE:AUTO?'.format(chan_id)) + + if v_autorange_state in ['1', '0'] : + return True if v_autorange_state=='1' else False + else: + raise ValueError('Unknown state output: {}'.format(v_autorange_state)) + return False + + def _set_chan_v_autorange(self, chan:int, state:bool) -> None: + """ + Set channel voltage autorange state + + Args: + chan: The 1-indexed channel number + state: power state + """ + chan_id = self.chan_to_id(chan) + self.write(chan_id + 'VOLT:RANGE:AUTO {}'.format('1' if(state) else '0') ) def _set_chan_pos_sat(self, chan:int, pos_sat: Union[float, str]) -> None: @@ -376,7 +445,6 @@ def _set_chan_pos_sat(self, chan:int, elif isinstance(pos_sat,str): self.write(chan_id + 'VOLT:SAT:POS MAX') - def _set_chan_neg_sat(self, chan:int, neg_sat: Union[float, str]) -> None: chan_id = self.chan_to_id(chan) @@ -385,17 +453,14 @@ def _set_chan_neg_sat(self, chan:int, elif isinstance(neg_sat,str): self.write(chan_id + 'VOLT:SAT:NEG MIN') - def _get_chan_pos_sat(self, chan:int) -> str: chan_id = self.chan_to_id(chan) return self.ask(chan_id + 'VOLT:SAT:POS ?') - def _get_chan_neg_sat(self, chan:int) -> str: chan_id = self.chan_to_id(chan) return self.ask(chan_id + 'VOLT:SAT:NEG ?') - def _get_chan_state(self, chan:int) -> str: """ Get channel power state @@ -414,7 +479,6 @@ def _get_chan_state(self, chan:int) -> str: else: raise ValueError('Unknown state output: {}'.format(state)) - def _set_chan_state(self, chan:int, state:str) -> None: """ @@ -427,7 +491,6 @@ def _set_chan_state(self, chan:int, chan_id = self.chan_to_id(chan) self.write(chan_id + 'OUTP ' + state) - def _set_chan_name(self, chan:int, name: str) -> None: """ @@ -440,6 +503,16 @@ def _set_chan_name(self, chan:int, chan_id = self.chan_to_id(chan) self.write(chan_id + 'chan:name "{}"'.format(name)) + def _clear_chan_alarm(self, chan:int) -> None: + """ + Clear the alarm/warning for a given channel + + Args: + chan: The 1-indexed channel number + """ + chan_id = self.chan_to_id(chan) + self.write(chan_id + 'LIM:CLEAR') + self.write(chan_id + 'STAT:CLEAR') def chan_to_ic(self, chan:int) -> Tuple[int, int]: """ @@ -455,8 +528,21 @@ def chan_to_ic(self, chan:int) -> Tuple[int, int]: c = chan-(i-1)*4 return i,c - def chan_to_id(self, chan:int) -> str: i,c = self.chan_to_ic(chan) return 'i{};c{};'.format(i,c) + + def set_dacs_zero(self) -> None: + """ + Ramp all voltages to zero. + """ + for ch in self.channels: + ch.v(0) + + def print_dac_voltages(self) -> None: + """ + Prints the voltage of all channels to cmdl. + """ + for ch in self.channels: + print('voltage on {}:{} {}'.format(ch.name, ch.v(), ch.v.unit))