Skip to content

Commit

Permalink
Merge pull request microsoft#2028 from QCoDeS/iv_sweep
Browse files Browse the repository at this point in the history
B1517A: Additions to driver for IV staircase sweep
  • Loading branch information
astafan8 authored Jun 8, 2020
2 parents e9066e8 + dab46d8 commit 2fb0870
Show file tree
Hide file tree
Showing 12 changed files with 1,561 additions and 555 deletions.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion qcodes/instrument/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1783,7 +1783,7 @@ def __init__(self,
name: str,
names: Sequence[str],
shapes: Sequence[Sequence[Optional[int]]],
instrument: Optional['Instrument'] = None,
instrument: Optional['InstrumentBase'] = None,
labels: Optional[Sequence[str]] = None,
units: Optional[Sequence[str]] = None,
setpoints: Optional[Sequence[Sequence]] = None,
Expand Down
16 changes: 14 additions & 2 deletions qcodes/instrument/sims/keysight_b1500.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,27 @@ devices:
r: "B1517A,0;B1517A,0;B1520A,0;0,0;0,0;B1530A,0;0,0;0,0;0,0;0,0"
- q: "TI 1,-16"
r: "NAI+000.000E-06"
- q: "WTDCV 0,0,0,0,0"
- q: "WTDCV 0.0,0.0,0.0,0.0,0.0"
- q: "WDCV 3,1,0,0,1"
- q: "WDCV 3,1,0.0,0.0,1"
- q: "ACT 2,1"
- q: "IMP 101"
- q: "LMN 0"
- q: "MT 0,0.1,7"
- q: "FMT 1,0"
- q: "WV 1,1,0,0,0,1,0.1,2"
- q: "WT 0.0,0.0,0.0,0.0,0.0"
- q: "WV 1,1,0,0.0,0.0,1,0.1,2.0"
- q: "WV 2,1,0,0.0,0.0,1,0.1,2.0"
- q: "*LRN? 32"
r: "RI 1,0;RI 2,0"
- q: "*LRN? 33"
r: "WT 0.0,0.0,0.0,0.0,0.0;WV 1,1,0,0.0,0.0,1,0.1,2.0;WTDCV 0.0,0.0,0.0,0.0,0.0"
- q: "*LRN? 46"
r: "CMM 1,0;CMM 2,0"
- q: "*LRN? 100"
r: "WDCV 3,1,0.0,0.0,1;WTDCV 0.0,0.0,0.0,0.0,0.0"
- q: "*LRN? 72"
r: "ACT 2,1"

resources:
GPIB::1::INSTR:
Expand Down
231 changes: 219 additions & 12 deletions qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import re
import textwrap
from typing import Optional, Union
from typing import Optional, Union, Dict, List, Tuple
from collections import defaultdict

from qcodes import VisaInstrument
from qcodes import VisaInstrument, MultiParameter
from qcodes.instrument_drivers.Keysight.keysightb1500.KeysightB1500_module \
import _FMTResponse, fmt_response_base_parser
from qcodes.utils.helpers import create_on_off_val_mapping
from .KeysightB1530A import B1530A
from .KeysightB1520A import B1520A
from .KeysightB1517A import B1517A
from .KeysightB1500_module import B1500Module, parse_module_query_response, \
parse_spot_measurement_response
from . import constants
from .constants import ChannelList
from .message_builder import MessageBuilder


Expand All @@ -36,29 +38,41 @@ def __init__(self, name, address, **kwargs):
get_cmd=None,
val_mapping=create_on_off_val_mapping(
on_val=True, off_val=False),
initial_cache_value=False,
docstring=textwrap.dedent("""
Enable or disable cancelling of the offset of the
high-resolution A/D converter (ADC).
Set the function to OFF in cases that the measurement speed is
more important than the measurement accuracy. This roughly halves
the integration time."""))
# Instrument is initialized with this setting having value of
# `False`, hence let's set the parameter to this value since it is
# not possible to request this value from the instrument.
self.autozero_enabled.cache.set(False)

self.add_parameter(name='run_iv_staircase_sweep',
parameter_class=IVSweepMeasurement,
docstring=textwrap.dedent("""
This is MultiParameter. Running the sweep runs the measurement
on the list of source values defined using
`setup_staircase_sweep` method. The output is a
primary parameter (e.g. Gate current) and a secondary
parameter (e.g. Source/Drain current) both of which use the same
setpoints. Note you must `set_measurement_mode` and specify
2 channels as the argument before running the sweep. First
channel (SMU) must be the channel on which you set the sweep (
WV) and second channel(SMU) must be the one which remains at
constants voltage.
"""))

self.connect_message()

def add_module(self, name: str, module: B1500Module):
def add_module(self, name: str, module: B1500Module) -> None:
super().add_submodule(name, module)

self.by_kind[module.MODULE_KIND].append(module)
self.by_slot[module.slot_nr] = module
for ch in module.channels:
self.by_channel[ch] = module

def reset(self):
def reset(self) -> None:
"""Performs an instrument reset.
This does not reset error queue!
Expand All @@ -73,7 +87,7 @@ def get_status(self) -> int:
# comes with time stamp
# FMT1,0: ASCII (12 digits data with header) <CR/LF^EOI>

def _find_modules(self):
def _find_modules(self) -> None:
from .constants import UNT

r = self.ask(MessageBuilder()
Expand Down Expand Up @@ -113,7 +127,8 @@ def from_model_name(model: str, slot_nr: int, parent: 'KeysightB1500',
else:
raise NotImplementedError("Module type not yet supported.")

def enable_channels(self, channels: ChannelList = None):
def enable_channels(self, channels: Optional[constants.ChannelList] = None
) -> None:
"""Enable specified channels.
If channels is omitted or `None`, then all channels are enabled.
Expand All @@ -122,7 +137,10 @@ def enable_channels(self, channels: ChannelList = None):

self.write(msg.message)

def disable_channels(self, channels: ChannelList = None):
def disable_channels(
self,
channels: Optional[constants.ChannelList] = None
) -> None:
"""Disable specified channels.
If channels is omitted or `None`, then all channels are disabled.
Expand Down Expand Up @@ -282,3 +300,192 @@ def clear_buffer_of_error_message(self) -> None:
msg = MessageBuilder().err_query()
self.write(msg.message)

def clear_timer_count(self, chnum: Optional[int] = None) -> None:
"""
This command clears the timer count. This command is effective for
all measurement modes, regardless of the TSC setting. This command
is not effective for the 4 byte binary data output format
(FMT3 and FMT4).
Args:
chnum: SMU or MFCMU channel number. Integer expression. 1 to 10.
See Table 4-1 on page 16 of 2016 manual. If chnum is
specified, this command clears the timer count once at the
source output start by the DV, DI, or DCV command for the
specified channel. The channel output switch of the
specified channel must be ON when the timer count is
cleared.
If chnum is not specified, this command clears the timer count
immediately,
"""
msg = MessageBuilder().tsr(chnum=chnum)
self.write(msg.message)

def set_measurement_mode(self,
mode: Union[constants.MM.Mode, int],
channels: Optional[constants.ChannelList] = None
) -> None:
"""
This method specifies the measurement mode and the channels used
for measurements. This method must be entered to specify the
measurement mode. For the high speed spot measurements,
do not use this method.
NOTE Order of the channels are important. The SMU which is setup to
run the sweep goes first.
Args:
mode: Measurement mode. See `constants.MM.Mode` for all possible
modes
channels: Measurement channel number. See `constants.ChannelList`
for all possible channels.
"""
msg = MessageBuilder().mm(mode=mode, channels=channels).message
self.write(msg)

def get_measurement_mode(self) -> Dict[str, Union[constants.MM.Mode,
List]]:
"""
This method gets the measurement mode(MM) and the channels used
for measurements. It outputs a dictionary with 'mode' and
'channels' as keys.
"""
msg = MessageBuilder().lrn_query(type_id=constants.LRN.
Type.TM_AV_CM_FMT_MM_SETTINGS)
response = self.ask(msg.message)
match = re.search('MM(?P<mode>.*?),(?P<channels>.*?)(;|$)', response)

if not match:
raise ValueError('Measurement Mode (MM) not found.')

out_dict: Dict[str, Union[constants.MM.Mode, List]] = {}
resp_dict = match.groupdict()
out_dict['mode'] = constants.MM.Mode(int(resp_dict['mode']))
out_dict['channels'] = list(map(int, resp_dict['channels'].split(',')))
return out_dict

def get_response_format_and_mode(self) -> \
Dict[str, Union[constants.FMT.Format, constants.FMT.Mode]]:
"""
This method queries the the data output format and mode.
"""
msg = MessageBuilder().lrn_query(type_id=constants.LRN.
Type.TM_AV_CM_FMT_MM_SETTINGS)
response = self.ask(msg.message)
match = re.search('FMT(?P<format>.*?),(?P<mode>.*?)(;|$)',
response)

if not match:
raise ValueError('Measurement Mode (FMT) not found.')

out_dict: Dict[str, Union[constants.FMT.Format, constants.FMT.Mode]] \
= {}
resp_dict = match.groupdict()
out_dict['format'] = constants.FMT.Format(int(resp_dict[
'format']))
out_dict['mode'] = constants.FMT.Mode(int(resp_dict['mode']))
return out_dict

def enable_smu_filters(
self,
enable_filter: bool,
channels: Optional[constants.ChannelList] = None
) -> None:
"""
This methods sets the connection mode of a SMU filter for each channel.
A filter is mounted on the SMU. It assures clean source output with
no spikes or overshooting. A maximum of ten channels can be set.
Args:
enable_filter : Status of the filter.
False: Disconnect (initial setting).
True: Connect.
channels : SMU channel number. Specify channel from
`constants.ChNr` If you do not specify chnum, the FL
command sets the same mode for all channels.
"""
self.write(MessageBuilder().fl(enable_filter=enable_filter,
channels=channels).message)


class IVSweepMeasurement(MultiParameter):
"""
IV sweep measurement outputs a list of primary and secondary
parameter.
Args:
name: Name of the Parameter.
instrument: Instrument to which this parameter communicates to.
"""

def __init__(self, name: str, instrument: B1517A, **kwargs):
super().__init__(
name,
names=tuple(['param1', 'param2']),
units=tuple(['A', 'A']),
labels=tuple(['Param1 Current', 'Param2 Current']),
shapes=((1,),) * 2,
setpoint_names=(('Voltage',),) * 2,
setpoint_labels=(('Voltage',),) * 2,
setpoint_units=(('V',),) * 2,
instrument=instrument,
**kwargs)

self.instrument: B1517A
self.root_instrument: KeysightB1500

self.param1 = _FMTResponse(None, None, None, None)
self.param2 = _FMTResponse(None, None, None, None)
self.source_voltage = _FMTResponse(None, None, None, None)
self._fudge: float = 1.5

def get_raw(self) -> Tuple[Tuple[float, ...], Tuple[float, ...]]:
measurement_mode = self.instrument.get_measurement_mode()
if len(measurement_mode['channels']) != 2:
raise ValueError('Two measurement channels are needed, one for '
'gate current and other for source drain '
'current.')

smu = self.instrument.by_channel[measurement_mode['channels'][0]]

if not smu.setup_fnc_already_run:
raise Exception(f'Sweep setup has not yet been run successfully '
f'on {smu.full_name}')

delay_time = smu.iv_sweep.step_delay()
if smu._average_coefficient < 0:
# negative coefficient means nplc and positive means just
# averaging
nplc = 128 * abs(smu._average_coefficient)
power_line_time_period = 1 / smu.power_line_frequency
calculated_time = 2 * nplc * power_line_time_period
else:
calculated_time = smu._average_coefficient * \
delay_time
num_steps = smu.iv_sweep.sweep_steps()
estimated_timeout = max(delay_time, calculated_time) * num_steps
new_timeout = estimated_timeout * self._fudge

format_and_mode = self.instrument.get_response_format_and_mode()
fmt_format = format_and_mode['format']
fmt_mode = format_and_mode['mode']
try:
self.root_instrument.write(MessageBuilder().fmt(1, 1).message)
with self.root_instrument.timeout.set_to(new_timeout):
raw_data = self.instrument.ask(MessageBuilder().xe().message)
parsed_data = fmt_response_base_parser(raw_data)
finally:
self.root_instrument.write(MessageBuilder().fmt(fmt_format,
fmt_mode).message)

self.param1 = _FMTResponse(
*[parsed_data[i][::3] for i in range(0, 4)])
self.param2 = _FMTResponse(
*[parsed_data[i][1::3] for i in range(0, 4)])
self.source_voltage = _FMTResponse(
*[parsed_data[i][2::3] for i in range(0, 4)])

self.shapes = ((len(self.source_voltage.value),),) * 2
self.setpoints = ((self.source_voltage.value,),) * 2

return self.param1.value, self.param2.value
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
_FMTResponse = namedtuple('FMTResponse', 'value status channel type')


def parse_fmt_1_0_response(raw_data_val: str) -> _FMTResponse:
def fmt_response_base_parser(raw_data_val: str) -> _FMTResponse:
"""
Parse the response from SPA for `FMT 1,0` format into a named tuple
with names, value (value of the data), status (Normal or with compliance
error such as C, T, V), channel (channel number of the output data such
as CH1,CH2), type (current 'I' or voltage 'V').
as CH1,CH2), type (current 'I' or voltage 'V'). This parser is tested
for FMT1,0 and FMT1,1 response.
Args:
raw_data_val: Unparsed (raw) data for the instrument.
Expand Down Expand Up @@ -270,3 +271,12 @@ def is_enabled(self) -> bool:
int(x) for x in activated_channels if x != ''
)
return is_enabled

def clear_timer_count(self) -> None:
"""
This command clears the timer count. This command is effective for
all measurement modes, regardless of the TSC setting. This command
is not effective for the 4 byte binary data output format
(FMT3 and FMT4).
"""
self.root_instrument.clear_timer_count(chnum=self.channels)
Loading

0 comments on commit 2fb0870

Please sign in to comment.