# Qcodes driver Keithley 6430 SMU
# Based on QtLab legacy driver
# https://github.com/qdev-dk/qtlab/blob/master/instrument_plugins/Keithley_6430.py
from typing import List, Tuple
from qcodes.instrument.visa import VisaInstrument
from qcodes.utils.validators import Ints, Numbers, Bool, Strings, Enum
from qcodes.utils.helpers import create_on_off_val_mapping
import logging
import warnings
from functools import partial
log = logging.getLogger(__name__)
on_off_vals = create_on_off_val_mapping(on_val=1, off_val=0)
[docs]
class Keithley_6430(VisaInstrument):
r"""
This is the Qcodes driver for the Keithley 6430 SMU.
Args:
name: The name used internally by QCoDeS
address: Network address or alias of the instrument
terminator: Termination character in VISA communication
reset: resets to default values
"""
def __init__(self, name: str,
address: str,
terminator="\n",
reset: bool = False,
**kwargs):
super().__init__(name, address, terminator=terminator, **kwargs)
self.add_parameter('source_current_compliance',
unit='A',
get_parser=float,
set_cmd='SENS:CURR:PROT {}',
get_cmd='SENS:CURR:PROT?',
vals=Numbers(1e-9, 105e-3)
)
self.add_parameter('source_voltage_compliance',
unit='V',
get_parser=float,
set_cmd='SENS:VOLT:PROT {}',
get_cmd='SENS:VOLT:PROT?',
vals=Numbers(0.2e-3, 210),
)
self.add_parameter('source_current_compliance_tripped',
get_cmd='SENS:CURR:PROT:TRIP?',
val_mapping=on_off_vals,
docstring='True if current has reached specified '
'compliance.',
)
self.add_parameter('source_voltage_compliance_tripped',
get_cmd='SENS:VOLT:PROT:TRIP?',
val_mapping=on_off_vals,
docstring='True if voltage has reached specified '
'compliance.',
)
self.add_parameter('source_current',
unit='A',
get_parser=float,
label='Source current',
set_cmd='SOUR:CURR:LEV {}',
get_cmd='SOUR:CURR:LEV?',
vals=Numbers(-105e-3, 105e-3),
docstring='When in current sourcing mode, tries to '
'set current to this level.',
)
self.add_parameter('sense_current',
unit='A',
get_parser=float,
label='Measured current',
get_cmd=partial(self._read_value, 'CURR:DC'),
snapshot_get=False,
docstring='Value of measured current, when in '
'current sensing mode.',
)
self.add_parameter('sense_voltage',
unit='V',
get_parser=float,
label='Measured voltage',
get_cmd=partial(self._read_value, 'VOLT:DC'),
snapshot_get=False,
docstring='Value of measured voltage, when in '
'voltage sensing mode.',
)
self.add_parameter('sense_resistance',
unit='Ohm',
get_parser=float,
label='Measured resistance',
get_cmd=partial(self._read_value, 'RES'),
snapshot_get=False,
docstring='Value of measured resistance, when in '
'resistance sensing mode.',
)
self.add_parameter('source_current_range',
unit='A',
get_parser=float,
set_cmd='SOUR:CURR:RANG {}',
get_cmd='SOUR:CURR:RANG?',
vals=Numbers(1e-12, 105e-3)
)
self.add_parameter('source_voltage',
unit='V',
get_parser=float,
label='Source voltage',
set_cmd='SOUR:VOLT:LEV {}',
get_cmd='SOUR:VOLT:LEV?',
vals=Numbers(-210, 210),
docstring='When in voltage sourcing mode, tries to '
'set voltage to this level.',
)
self.add_parameter('source_voltage_range',
unit='V',
get_parser=float,
set_cmd='SOUR:VOLT:RANG {}',
get_cmd='SOUR:VOLT:RANG?',
vals=Numbers(200e-3, 200)
)
self.add_parameter('source_delay_auto',
set_cmd=':SOUR:DEL:AUTO {}',
get_cmd=':SOUR:DEL:AUTO?',
val_mapping=on_off_vals,
docstring="Automatically set a delay period that "
"is appropriate for the present "
"source/measure setup configuration.",
)
self.add_parameter('source_delay',
unit='s',
get_parser=float,
set_cmd=':SOUR:DEL {}',
get_cmd=':SOUR:DEL?',
vals=Numbers(0, 9999.998),
docstring="Settling time after setting source "
"value.",
)
self.add_parameter('output_enabled',
set_cmd='OUTP {}',
get_cmd='OUTP?',
val_mapping=on_off_vals,
docstring='Turns the source on or off.',
)
self.add_parameter('output_auto_off_enabled',
set_cmd=':SOUR:CLE:AUTO {}',
get_cmd='OUTP?',
val_mapping=on_off_vals,
)
self.add_parameter('source_mode',
set_cmd='SOUR:FUNC {}',
get_cmd=self._get_source_mode,
vals=Enum('VOLT', 'CURR'),
docstring="Either 'VOLT' to source voltage, "
"or 'CURR' for current.",
)
self.add_parameter('sense_mode',
set_cmd=self._set_sense_mode,
get_cmd=self._get_sense_mode,
vals=Strings(),
docstring="Sensing mode."
"Set to 'VOLT:DC', "
"'CURR:DC', or 'RES', or a combination "
"thereof by using comma.",
)
self.add_parameter('sense_autorange',
set_cmd=self._set_sense_autorange,
get_cmd=self._get_sense_autorange,
vals=Bool(),
docstring="If True, all ranges in all modes are"
" chosen automatically",
)
self.add_parameter('sense_current_range',
unit='A',
get_parser=float,
set_cmd=':SENS:CURR:RANG {}',
get_cmd=':SENS:CURR:RANG?',
vals=Numbers(1e-12, 1e-1),
)
self.add_parameter('sense_voltage_range',
unit='V',
get_parser=float,
set_cmd=':SENS:VOLT:RANG {}',
get_cmd=':SENS:VOLT:RANG?',
vals=Enum(200, 20, 2, 0.2),
)
self.add_parameter('sense_resistance_range',
unit='Ohm',
get_parser=float,
set_cmd=':SENS:RES:RANG {}',
get_cmd=':SENS:RES:RANG?',
vals=Numbers(2, 2e13),
)
self.add_parameter('sense_resistance_offset_comp_enabled',
set_cmd=':SENS:RES:OCOM {}',
get_cmd=':SENS:RES:OCOM?',
val_mapping=on_off_vals,
docstring="Set offset compensation on/off for "
"resistance measurements.",
)
self.add_parameter('trigger_source',
set_cmd=':TRIG:SOUR {}',
get_cmd=':TRIG:SOUR?',
vals=Enum('IMM', 'TLIN'),
docstring="Specify trigger control source."
"IMMediate or TLINk.",
)
self.add_parameter('arm_source',
set_cmd=':ARM:SOUR {}',
get_cmd=':ARM:SOUR?',
vals=Enum('IMM', 'TLIN', "TIM", "MAN", "BUS",
"NST", "PST", "BST"),
docstring="Specify arm control source."
"IMMediate, or TLINk, TIMer, MANual,"
" BUS, NSTest, PSTest, or BSTest.",
)
self.add_parameter('trigger_count',
set_cmd=':TRIG:COUN {}',
get_cmd=':TRIG:COUN?',
vals=Ints(),
docstring="How many times to trigger.",
)
self.add_parameter('arm_count',
set_cmd=':ARM:COUN {}',
get_cmd=':ARM:COUN?',
vals=Ints(),
docstring="How many times to arm.",
)
self.add_parameter('nplc',
get_parser=float,
set_cmd=':NPLC {}',
get_cmd=':NPLC?',
vals=Numbers(0.01, 10),
docstring="Set integration time to the specified"
"value in Number of Powerline Cycles.",
)
self.add_parameter('digits',
get_parser=int,
set_cmd='DISP:DIG {}',
get_cmd='DISP:DIG?',
vals=Ints(4, 7),
docstring="Display resolution.",
)
self.add_parameter('autozero',
set_cmd='SYST:AZER:STAT {}',
get_cmd='SYST:AZER:STAT?',
val_mapping=on_off_vals,
docstring="Enable autozero."
"Enabling maximizes accuracy, "
"disabling increases speed.",
)
self.add_parameter('filter_auto',
set_cmd='AVER:AUTO {}',
get_cmd='AVER:AUTO?',
val_mapping=on_off_vals,
docstring="Automatically choose filtering.",
)
self.add_parameter('filter_repeat_enabled',
set_cmd=':AVER:REP:STAT {}',
get_cmd='AVER:AUTO?',
val_mapping=on_off_vals,
docstring="Enable repeat filter.",
)
self.add_parameter('filter_median_enabled',
set_cmd=':MED:STAT {}',
get_cmd=':MED:STAT?',
val_mapping=on_off_vals,
docstring="Enable median filter.",
)
self.add_parameter('filter_moving_enabled',
set_cmd=':AVER:STAT {}',
get_cmd=':AVER:STAT?',
val_mapping=on_off_vals,
docstring="Enable moving average.",
)
self.add_parameter('filter_repeat',
get_parser=int,
set_cmd=':AVER:REP:COUN {}',
get_cmd=':AVER:REP:COUN?',
vals=Ints(),
docstring="Number of readings that are acquired"
"and stored in the filter buffer.",
)
self.add_parameter('filter_median',
get_parser=int,
set_cmd=':MED:RANK {}',
get_cmd=':MED:RANK?',
vals=Ints(),
docstring="Number of reading samples"
" for the median filter process.",
)
self.add_parameter('filter_moving',
get_parser=int,
set_cmd=':AVER:COUN {}',
get_cmd=':AVER:COUN?',
vals=Ints(),
docstring="Number of reading samples"
" in the moving average filter.",
)
self.connect_message()
if reset:
self.reset()
[docs]
def reset(self) -> None:
r"""
Resets instrument to default values
"""
self.write('*RST')
[docs]
def read(self) -> Tuple[float, float, float]:
"""
Arm, trigger, and readout. Note that the values may not be valid if
sense mode doesn't include them.
Returns:
tuple of (voltage (V), current (A), resistance (Ohm))
"""
if not (self.output_enabled() or self.output_auto_off_enabled()):
raise Exception(
"Either source must be turned on manually or "
"``output_auto_off_enabled`` has to be enabled before "
"measuring a sense parameter."
)
s = self.ask(':READ?')
logging.debug(f'Read: {s}')
v, i, r = [float(n) for n in s.split(',')][:3]
return v, i, r
def _read_value(self, quantity: str) -> float:
"""
Read voltage, current or resistance through the sensing module.
Issues a warning if reading a value that does not correspond to the
sensing mode.
Args:
quantity: either "VOLT:DC", "CURR:DC" or "RES"
Returns:
Measured value of the requested quantity.
"""
mode_now = self.sense_mode()
if quantity not in mode_now:
warnings.warn(f"{self.short_name} tried reading {quantity}, but "
f"mode is set to {mode_now}. Value might be out of "
f"date.")
mapping = {"VOLT:DC": 0, "CURR:DC": 1, "RES": 2}
return self.read()[mapping[quantity]]
[docs]
def init(self) -> None:
"""
Go into the arm/trigger layers from the idle mode.
"""
self.write(':INIT')
def _set_sense_mode(self, mode: str) -> None:
"""
Set the sense_mode to the specified value
Input:
mode: mode(s) to be set. Choose from self._sense_modes.
Use comma to separate multiple modes.
"""
modes = [m.strip(' ') for m in mode.split(',')]
if not all([m in ["RES", "CURR:DC", "VOLT:DC"] for m in modes]):
raise ValueError(f'invalid sense_mode {modes}')
modes_str = '"' + '","'.join(modes) + '"'
string = f':SENS:FUNC {modes_str}'
self.write(':SENS:FUNC:OFF:ALL')
self.write(string)
def _get_sense_mode(self) -> str:
"""
Read the sense_mode from the device
"""
string = 'SENS:FUNC?'
ans = self.ask(string).replace('"', '')
return ans
def _get_source_mode(self) -> str:
"""
Read the source_mode from the device
"""
string = 'SOUR:FUNC?'
ans = self.ask(string).strip('"')
return ans
def _set_sense_autorange(self, val: bool) -> None:
"""
Switch sense_autorange on or off for all modes.
"""
n = int(val)
self.write(f'SENS:CURR:RANG:AUTO {n}')
self.write(f'SENS:VOLT:RANG:AUTO {n}')
self.write(f'SENS:RES:RANG:AUTO {n}')
def _get_sense_autorange(self) -> bool:
"""
Get status of sense_autorange. Returns true iff true for all modes
"""
reply0 = bool(int(self.ask('SENS:CURR:RANG:AUTO?')))
reply1 = bool(int(self.ask('SENS:VOLT:RANG:AUTO?')))
reply2 = bool(int(self.ask('SENS:RES:RANG:AUTO?')))
return reply0 and reply1 and reply2