Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Keithley 2000 modes #334

Merged
merged 11 commits into from
Sep 20, 2016
103 changes: 66 additions & 37 deletions qcodes/instrument_drivers/tektronix/Keithley_2000.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from qcodes import VisaInstrument
from qcodes.utils.validators import Numbers, Ints, Enum, MultiType
from qcodes.utils.validators import Numbers, Ints, Enum, MultiType, Bool

from functools import partial


def parse_output_string(s):
""" Parses and cleans string outputs of the Keithley """
# Remove surrounding whitespace and newline characters
Expand All @@ -25,90 +26,110 @@ def parse_output_string(s):

return s


def parse_output_bool(value):
return 'on' if int(value) == 1 else 'off'
return True if int(value) == 1 else False


class Keithley_2000(VisaInstrument):
"""
Driver for the Keithley 2000 multimeter.
"""
def __init__(self, name, address, reset=False, **kwargs):
super().__init__(name, address, **kwargs)
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass the terminator='\n' along to super().__init__ (in fact, perhaps only there, there's probably no reason to put it in the K2000 constructor is there?) and pyvisa will take out the newlines for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is expected behavior, but when using a val_mapping the newline doesn't get chopped off. As evidenced by the usage in the constructor and the fact that the current _mode_map works.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's in the signature of this constructor, but you don't do anything with it after that. But if you instead put it in the super().__init__ call, it really should take the newlines out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See for example the K2600 driver

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see... didn't think of adding it to the super() call.

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={
'ac current': '"CURR:AC"\n',
'dc current': '"CURR:DC"\n',
'ac voltage': '"VOLT:AC"\n',
'dc voltage': '"VOLT:DC"\n',
'2w resistance': '"RES"\n',
'4w resistance': '"FRES"\n',
'temperature': '"TEMP"\n',
'frequency': '"FREQ"\n',
})
val_mapping=self._mode_map)

# Mode specific parameters
self.add_parameter('nplc',
get_cmd=partial(self._get_mode_param, 'NPLC', float),
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),
get_cmd=partial(self._get_mode_param, 'RANG',
float),
set_cmd=partial(self._set_mode_param, 'RANG'),
vals=Numbers())

self.add_parameter('auto_range',
get_cmd=partial(self._get_mode_param, 'RANG:AUTO', parse_output_bool),
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=Enum('on', 'off'))
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),
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),
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',
get_cmd=partial(self._get_mode_param, 'AVER:STAT', parse_output_bool),
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=Enum('on', 'off'))
vals=Bool())

# Global parameters
self.add_parameter('display',
self.add_parameter('display_enabled',
get_cmd='DISP:ENAB?',
get_parser=parse_output_bool,
set_cmd='DISP:ENAB {}',
vals=Enum('on', 'off'))
set_parser=int,
vals=Bool())

self.add_parameter('trigger_continuous',
get_cmd='INIT:CONT?',
get_parser=parse_output_bool,
set_cmd='INIT:CONT {}',
vals=Enum('on', 'off'))
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')))
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 {}',
units='s',
vals=Numbers(min_value=0, max_value=999999.999))
Expand All @@ -117,15 +138,16 @@ def __init__(self, name, address, reset=False, **kwargs):
get_cmd='TRIG:SOUR?',
set_cmd='TRIG:SOUR {}',
val_mapping={
'immediate': 'IMM\n',
'timer': 'TIM\n',
'manual': 'MAN\n',
'bus': 'BUS\n',
'external': 'EXT\n',
'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 {}',
units='s',
vals=Numbers(min_value=0.001, max_value=999999.999))
Expand All @@ -142,25 +164,32 @@ def __init__(self, name, address, reset=False, **kwargs):
self.connect_message()

def trigger(self):
if self.trigger_continuous() == 'off':
if not self.trigger_continuous():
self.write('INIT')
self._trigger_sent = True

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

self._trigger_sent = False

return float(self.ask('DATA:FRESH?'))

def _get_mode_param(self, parameter, parser):
cmd = '{}:{}?'.format(self.mode(), parameter)
""" Read the current Keithley mode and ask for a parameter """
mode = parse_output_string(self._mode_map[self.mode()])
cmd = '{}:{}?'.format(mode, parameter)

return parser(self.ask(cmd))

def _set_mode_param(self, parameter, value):
cmd = '{}:{} {}'.format(self.mode(), parameter, value)
""" 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 = '{}:{} {}'.format(mode, parameter, value)

self.write(cmd)
self.write(cmd)