Source code for qcodes_contrib_drivers.drivers.Bilt.ITest

# This Python file uses the following encoding: utf-8
# Loick Le Guevel, 2019
# Etienne Dumur <etienne.dumur@gmail.com>, 2021
# Simon Zihlmann <zihlmann.simon@gmail.com>, 2021
# Victor Millory <victor.millory@cea.fr>, 2021

from typing import Union, Tuple, Any
from functools import partial
from math import ceil
from time import sleep

from qcodes.instrument import Instrument
from qcodes.instrument import InstrumentChannel, ChannelList
from qcodes.parameters import MultiChannelInstrumentParameter
from qcodes.instrument import VisaInstrument
from qcodes.utils import validators as vals
from qcodes.parameters import create_on_off_val_mapping

[docs] class iTestChannel(InstrumentChannel): """ A single channel of iTest. """
[docs] def __init__(self, parent: Instrument, name: str, chan_num: int) -> None: """ Args: parent: The instrument to which the channel is attached. name: The name of the channel. chan_num: The number of the channel in question. """ super().__init__(parent, name) self.chan_num = chan_num # Get channel id i, c = round(chan_num/4.)+1, chan_num%4 self.chan_id = 'i{};c{};'.format(i,c) self.add_parameter('v', label='Channel {} voltage'.format(chan_num), unit='V', docstring='Voltage of the channel in volt.', get_cmd=partial(self._parent._get_voltage, chan_num), get_parser=float, set_cmd=partial(self._parent._set_voltage, chan_num), 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', label='Channel {} current'.format(chan_num), unit='A', docstring='Current of the channel in ampere.', get_cmd=partial(self._parent._get_current, chan_num), get_parser=float ) self.add_parameter('ramp_slope', label='Channel {} ramp slope'.format(chan_num), unit='V/ms', docstring='Slope of the ramp in V/ms.', get_cmd=partial(self._parent._get_ramp_slope, chan_num), get_parser=float, set_cmd=partial(self._parent._set_ramp_slope, chan_num), vals=vals.Numbers(0, 1) ) self.add_parameter('output_mode', label='Channel {} output mode'.format(chan_num), docstring='Mode of the output {exp, ramp}.', get_cmd=partial(self._parent._get_output_function, chan_num), get_parser=str, set_cmd=partial(self._parent._set_output_function, chan_num), set_parser=str, initial_value='exp', vals=vals.Enum('ramp', 'exp') ) self.add_parameter('v_range', label = 'Channel {} voltage range'.format(chan_num), docstring='Range of the channel in volt.', 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, 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') ) 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, 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, 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}' ) self.add_parameter('synchronous_enable', docstring='Is the channel in synchronous mode.', get_cmd=None, get_parser=bool, set_cmd=None, vals=vals.Bool(), initial_value=False ) self.add_parameter('synchronous_delay', docstring='Time between to voltage measurement in second.', get_cmd=None, get_parser=float, set_cmd=None, vals=vals.Numbers(1e-3, 10), initial_value=1e-3 ) self.add_parameter('synchronous_threshold', docstring='Threshold to unblock communication in volt.', get_cmd=None, get_parser=float, set_cmd=None, vals=vals.Numbers(0, 1e-3), 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 )
[docs] def start(self) -> None: """ Switch on the channel. """ self._parent._set_chan_state(self.chan_num, '1')
[docs] def stop(self) -> None: """ Switch off the channel. """ self._parent._set_chan_state(self.chan_num, '0')
[docs] def clear_alarm(self) -> None: """ Clear the alarm and warnings of the channel. """ self._parent._clear_chan_alarm(self.chan_num)
[docs] class iTestMultiChannelParameter(MultiChannelInstrumentParameter): """ """ def __init__(self, channels, param_name, *args, **kwargs): super().__init__(channels, param_name, *args, **kwargs)
[docs] class ITest(VisaInstrument): """ This is the QCoDeS python driver for the iTest device from Bilt. """
[docs] def __init__(self,name:str, address:str, num_chans:int=16, init_start:bool=False, 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. Args: 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, 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 set voltage and the measured voltage difference is below "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 """ super().__init__(name, address=address, terminator='\n', device_clear=False, **kwargs) 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 channels = ChannelList(parent=self, name='Channels', chan_type=iTestChannel, multichan_paramclass=iTestMultiChannelParameter) for i in self.chan_range: channel = iTestChannel(self, name='chan{:02}'.format(i), chan_num=i) channels.append(channel) self.add_submodule('ch{:02}'.format(i),channel) channels.lock() self.add_submodule('channels',channels) if init_start: for channel in self.channels: channel.stop() channel.v.set(0) 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: """ Set cmd for the chXX_v parameter Args: chan: The 1-indexed channel number v_set: The target voltage """ chan_id = self.chan_to_id(chan) self.write('{}VOLT {:.8f}'.format(chan_id, v_set)) self.write(chan_id + 'TRIG:INPUT:INIT') if self.channels[chan-1].synchronous_enable(): v = self._get_voltage(chan) while abs(v_set - v)>=self.channels[chan-1].synchronous_threshold(): 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 Args: chan: The 1-indexed channel number Returns: Voltage """ chan_id = self.chan_to_id(chan) return float(self.ask('{}MEAS:VOLT?'.format(chan_id))) def _get_current(self, chan:int) -> float: """ Get cmd for the chXX_i parameter Args: chan: The 1-indexed channel number Returns: Current """ chan_id = self.chan_to_id(chan) return float(self.ask('{}MEAS:CURR?'.format(chan_id))) def _set_ramp_slope(self, chan:int, slope:float) -> None: """ Set slope of chXX for ramp mode Args: chan The 1-indexed channel number slope Slope of chXX in V/ms """ 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 Args: chan: The 1-indexed channel number Returns: chXX_slope parameter """ 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: """ Set how to perform output voltage update Args: chan: The 1-indexed channel number ouf: Mode """ chan_id = self.chan_to_id(chan) if outf=='exp': mode = '0' elif outf=='ramp': mode = '1' else: raise ValueError(f'Got unexpected output function mode: {outf}.') self.write(chan_id + 'trig:input ' + mode) def _get_output_function(self, chan:int) -> str: """ Get output volage update function Args: chan: The 1-indexed channel number Returns: mode """ chan_id = self.chan_to_id(chan) mode = int(self.ask(chan_id + 'TRIG:INPUT?')) if mode == 0: return 'exp' elif mode == 1: return 'ramp' else: raise ValueError('Got unexpected output function mode: {}.'.format(mode)) def _set_chan_range(self, chan:int, volt: float) -> None: """ Set output voltage range Args: chan : The 1-indexed channel number volt : Voltage range (1.2 or 12) """ chan_id = self.chan_to_id(chan) 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: """ Get output voltage range Args: chan: The 1-indexed channel number Returns: volt: Output voltage range """ chan_id = self.chan_to_id(chan) 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: chan_id = self.chan_to_id(chan) if isinstance(pos_sat,(int,float)): self.write(chan_id + 'VOLT:SAT:POS {:.8f}'.format(pos_sat)) 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) if isinstance(neg_sat,(int,float)): self.write(chan_id + 'VOLT:SAT:NEG {:.8f}'.format(neg_sat)) 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 Args: chan: The 1-indexed channel number Returns: state: Power state """ chan_id = self.chan_to_id(chan) state = self.ask(chan_id + 'OUTP ?') if state in ['1', '0'] : return state else: raise ValueError('Unknown state output: {}'.format(state)) def _set_chan_state(self, chan:int, state:str) -> None: """ Set channel power state Args: chan: The 1-indexed channel number state: power state """ chan_id = self.chan_to_id(chan) self.write(chan_id + 'OUTP ' + state) def _set_chan_name(self, chan:int, name: str) -> None: """ Set the name of the channel Args: chan: Channel to be named name: Name of the channel """ 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')
[docs] def chan_to_ic(self, chan:int) -> Tuple[int, int]: """ Indexing conversion from channel number (1 to 16) to iX;c; Args: chan: The 1-indexed channel number Returns: i,c: i=card number, c=channel number of card i """ i = ceil(chan/4.) c = chan-(i-1)*4 return i,c
[docs] def chan_to_id(self, chan:int) -> str: i,c = self.chan_to_ic(chan) return 'i{};c{};'.format(i,c)
[docs] def set_dacs_zero(self) -> None: """ Ramp all voltages to zero. """ for ch in self.channels: ch.v(0)
[docs] 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))