Skip to content

Commit

Permalink
Merge #4581
Browse files Browse the repository at this point in the history
4581: Update Textronix drivers to conform with standard r=jenshnielsen a=jenshnielsen

This also moves the Keithley drivers from the Tektronix folder to a new Keithley drivers folder

Co-authored-by: Jens H. Nielsen <Jens.Nielsen@microsoft.com>
  • Loading branch information
bors[bot] and jenshnielsen authored Sep 12, 2022
2 parents 02eee7d + 7034cc4 commit 93ef162
Show file tree
Hide file tree
Showing 47 changed files with 4,902 additions and 4,151 deletions.
2 changes: 2 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ genapi:
../qcodes/instrument_drivers/basel \
../qcodes/instrument_drivers/HP \
../qcodes/instrument_drivers/ithaco \
../qcodes/instrument_drivers/Keithley \
../qcodes/instrument_drivers/keysight \
../qcodes/instrument_drivers/Lakeshore \
../qcodes/instrument_drivers/QuantumDesign/DynaCoolPPMS/private/* \
../qcodes/instrument_drivers/rigol/* \
../qcodes/instrument_drivers/stahl/*\
../qcodes/instrument_drivers/stanford_research/* \
../qcodes/instrument_drivers/signal_hound/* \
../qcodes/instrument_drivers/tektronix/* \
../qcodes/instrument_drivers/weinschel/* \
../qcodes/instrument_drivers/yokogawa
mkdir -p api/generated/
Expand Down
7 changes: 7 additions & 0 deletions docs/drivers_api/Keithley.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. _keithley_api :

Keithley Drivers
================

.. automodule:: qcodes.instrument_drivers.Keithley
:autosummary:
7 changes: 7 additions & 0 deletions docs/drivers_api/Tektronix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. _tektronix_api :

Tektronix Drivers
=================

.. automodule:: qcodes.instrument_drivers.tektronix
:autosummary:
2 changes: 2 additions & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ sphinx-apidoc -o _auto -d 10 ..\qcodes ^
..\qcodes\instrument_drivers\basel ^
..\qcodes\instrument_drivers\HP ^
..\qcodes\instrument_drivers\ithaco ^
..\qcodes\instrument_drivers\Keithley ^
..\qcodes\instrument_drivers\Lakeshore ^
..\qcodes\instrument_drivers\QuantumDesign\DynaCoolPPMS\private\* ^
..\qcodes\instrument_drivers\rigol\* ^
..\qcodes\instrument_drivers\stahl\* ^
..\qcodes\instrument_drivers\stanford_research\* ^
..\qcodes\instrument_drivers\signal_hound\* ^
..\qcodes\instrument_drivers\tektronix\* ^
..\qcodes\instrument_drivers\weinschel\* ^
..\qcodes\instrument_drivers\yokogawa
mkdir api\generated\
Expand Down
222 changes: 222 additions & 0 deletions qcodes/instrument_drivers/Keithley/Keithley_2000.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
from functools import partial
from typing import Any, Callable, Union

from qcodes.instrument import VisaInstrument
from qcodes.validators import Bool, Enum, Ints, MultiType, Numbers


def _parse_output_string(s: str) -> str:
"""Parses and cleans string outputs of the Keithley"""
# Remove surrounding whitespace and newline characters
s = s.strip()

# Remove surrounding quotes
if (s[0] == s[-1]) and s.startswith(("'", '"')):
s = s[1:-1]

s = s.lower()

# Convert some results to a better readable version
conversions = {
"mov": "moving",
"rep": "repeat",
}

if s in conversions.keys():
s = conversions[s]

return s


def _parse_output_bool(value: str) -> bool:
return True if int(value) == 1 else False


class Keithley2000(VisaInstrument):
"""
Driver for the Keithley 2000 multimeter.
"""

def __init__(self, name: str, address: str, reset: bool = False, **kwargs: Any):
super().__init__(name, address, terminator="\n", **kwargs)

self._trigger_sent = False

# Unfortunately the strings have to contain quotation marks and a
# newline character, as this is how the instrument returns it.
self._mode_map = {
"ac current": '"CURR:AC"',
"dc current": '"CURR:DC"',
"ac voltage": '"VOLT:AC"',
"dc voltage": '"VOLT:DC"',
"2w resistance": '"RES"',
"4w resistance": '"FRES"',
"temperature": '"TEMP"',
"frequency": '"FREQ"',
}

self.add_parameter(
"mode",
get_cmd="SENS:FUNC?",
set_cmd="SENS:FUNC {}",
val_mapping=self._mode_map,
)

# Mode specific parameters
self.add_parameter(
"nplc",
get_cmd=partial(self._get_mode_param, "NPLC", float),
set_cmd=partial(self._set_mode_param, "NPLC"),
vals=Numbers(min_value=0.01, max_value=10),
)

# TODO: validator, this one is more difficult since different modes
# require different validation ranges
self.add_parameter(
"range",
get_cmd=partial(self._get_mode_param, "RANG", float),
set_cmd=partial(self._set_mode_param, "RANG"),
vals=Numbers(),
)

self.add_parameter(
"auto_range_enabled",
get_cmd=partial(self._get_mode_param, "RANG:AUTO", _parse_output_bool),
set_cmd=partial(self._set_mode_param, "RANG:AUTO"),
vals=Bool(),
)

self.add_parameter(
"digits",
get_cmd=partial(self._get_mode_param, "DIG", int),
set_cmd=partial(self._set_mode_param, "DIG"),
vals=Ints(min_value=4, max_value=7),
)

self.add_parameter(
"averaging_type",
get_cmd=partial(self._get_mode_param, "AVER:TCON", _parse_output_string),
set_cmd=partial(self._set_mode_param, "AVER:TCON"),
vals=Enum("moving", "repeat"),
)

self.add_parameter(
"averaging_count",
get_cmd=partial(self._get_mode_param, "AVER:COUN", int),
set_cmd=partial(self._set_mode_param, "AVER:COUN"),
vals=Ints(min_value=1, max_value=100),
)

self.add_parameter(
"averaging_enabled",
get_cmd=partial(self._get_mode_param, "AVER:STAT", _parse_output_bool),
set_cmd=partial(self._set_mode_param, "AVER:STAT"),
vals=Bool(),
)

# Global parameters
self.add_parameter(
"display_enabled",
get_cmd="DISP:ENAB?",
get_parser=_parse_output_bool,
set_cmd="DISP:ENAB {}",
set_parser=int,
vals=Bool(),
)

self.add_parameter(
"trigger_continuous",
get_cmd="INIT:CONT?",
get_parser=_parse_output_bool,
set_cmd="INIT:CONT {}",
set_parser=int,
vals=Bool(),
)

self.add_parameter(
"trigger_count",
get_cmd="TRIG:COUN?",
get_parser=int,
set_cmd="TRIG:COUN {}",
vals=MultiType(
Ints(min_value=1, max_value=9999),
Enum("inf", "default", "minimum", "maximum"),
),
)

self.add_parameter(
"trigger_delay",
get_cmd="TRIG:DEL?",
get_parser=float,
set_cmd="TRIG:DEL {}",
unit="s",
vals=Numbers(min_value=0, max_value=999999.999),
)

self.add_parameter(
"trigger_source",
get_cmd="TRIG:SOUR?",
set_cmd="TRIG:SOUR {}",
val_mapping={
"immediate": "IMM",
"timer": "TIM",
"manual": "MAN",
"bus": "BUS",
"external": "EXT",
},
)

self.add_parameter(
"trigger_timer",
get_cmd="TRIG:TIM?",
get_parser=float,
set_cmd="TRIG:TIM {}",
unit="s",
vals=Numbers(min_value=0.001, max_value=999999.999),
)

self.add_parameter("amplitude", unit="arb.unit", get_cmd=self._read_next_value)

self.add_function("reset", call_cmd="*RST")

if reset:
self.reset()

# Set the data format to have only ascii data without units and channels
self.write("FORM:DATA ASCII")
self.write("FORM:ELEM READ")

self.connect_message()

def trigger(self) -> None:
if not self.trigger_continuous():
self.write("INIT")
self._trigger_sent = True

def _read_next_value(self) -> float:
# Prevent a timeout when no trigger has been sent
if not self.trigger_continuous() and not self._trigger_sent:
return 0.0

self._trigger_sent = False

return float(self.ask("SENSE:DATA:FRESH?"))

def _get_mode_param(
self, parameter: str, parser: Callable[[str], Any]
) -> Union[float, str, bool]:
"""Read the current Keithley mode and ask for a parameter"""
mode = _parse_output_string(self._mode_map[self.mode()])
cmd = f"{mode}:{parameter}?"

return parser(self.ask(cmd))

def _set_mode_param(self, parameter: str, value: Union[float, str, bool]) -> None:
"""Read the current Keithley mode and set a parameter"""
if isinstance(value, bool):
value = int(value)

mode = _parse_output_string(self._mode_map[self.mode()])
cmd = f"{mode}:{parameter} {value}"

self.write(cmd)
Loading

0 comments on commit 93ef162

Please sign in to comment.