Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions qcodes/instrument_drivers/stanford_research_systems/SR830.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
from qcodes import VisaInstrument
from qcodes.utils.validators import Numbers, Ints

class Stanford_Research_Systems_SR830(VisaInstrument):
"""
Driver for the Stanford Research Systems SR830 lock-in amplifier.
"""
def __init__(self, name, address, reset=False, **kwargs):
super().__init__(name, address, **kwargs)

# Reference and phase
self.add_parameter('phase',
get_cmd='PHAS?',
get_parser=float,
set_cmd='PHAS {}',
units='deg',
vals=Numbers(min_value=-360, max_value=729.99))

self.add_parameter('reference_source',
get_cmd='FMOD?',
set_cmd='FMOD {}',
val_mapping={
'external': '0\n',
'internal': '1\n',
})

self.add_parameter('frequency',
get_cmd='FREQ?',
get_parser=float,
set_cmd='FREQ {:e}',
units='Hz',
vals=Numbers(min_value=1e-3, max_value=102e3))

self.add_parameter('ext_trigger',
get_cmd='RSLP?',
set_cmd='RSLP {}',
val_mapping={
'sine': '0\n',
'ttl rising': '1\n',
'ttl falling': '2\n',
})

self.add_parameter('harmonic',
get_cmd='HARM?',
get_parser=int,
set_cmd='HARM {}',
vals=Ints(min_value=1, max_value=19999))

self.add_parameter('amplitude',
get_cmd='SLVL?',
get_parser=float,
set_cmd='SLVL {}',
units='V',
vals=Numbers(min_value=0.004, max_value=5.000))

# Input and filter
self.add_parameter('input_config',
get_cmd='ISRC?',
set_cmd='ISRC {}',
val_mapping={
'a': '0\n',
'a-b': '1\n',
'1 mohm': '2\n',
'100 mohm': '3\n',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

those units would be Amperes, no?

})

self.add_parameter('input_shield',
get_cmd='IGND?',
set_cmd='IGND {}',
val_mapping={
'float': '0\n',
'ground': '1\n',
})

self.add_parameter('input_coupling',
get_cmd='ICPL?',
set_cmd='ICPL {}',
val_mapping={
'ac': '0\n',
'dc': '1\n',
})

self.add_parameter('notch_filter',
get_cmd='ILIN?',
set_cmd='ILIN {}',
val_mapping={
'out': '0\n',
'line in': '1\n',
'2x line in': '2\n',
'both in': '3\n',
})

# Gain and time constant
self.add_parameter('sensitivity',
get_cmd='SENS?',
set_cmd='SENS {}',
val_mapping={
"2 nv": '0\n',
"5 nv": '1\n',
"10 nv": '2\n',
"20 nv": '3\n',
"50 nv": '4\n',
"100 nv": '5\n',
"200 nv": '6\n',
"500 nv": '7\n',
"1 uv": '8\n',
"2 uv": '9\n',
"5 uv": '10\n',
"10 uv": '11\n',
"20 uv": '12\n',
"50 uv": '13\n',
"100 uv": '14\n',
"200 uv": '15\n',
"500 uv": '16\n',
"1 mv": '17\n',
"2 mv": '18\n',
"5 mv": '19\n',
"10 mv": '20\n',
"20 mv": '21\n',
"50 mv": '22\n',
"100 mv": '23\n',
"200 mv": '24\n',
"500 mv": '25\n',
"1 v": '26\n',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would this (and similarly for time_constant and filter_slope) be more useful as numbers, ie 2e-9: '0\n' with units='V'?

Then you could use the value (param.get() or param.get_latest()) in calculations.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I was just thinking the same, but actually the numbers have different meanings,
500 mV is equal to 500 nA depending if the input is in voltage or current mode

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is that what input_config is about? So then 1 MOhm means 500 mV turns into 500 nA, but 100 MOhm means the same 500 mV turns into 5 nA? I suppose we could make the parameters reflect what's going on behind the scenes... leave this parameter as it is because somewhere inside there really is a voltage digitization step with sensitivity of 500 mV... but in front of that there may be a 1 or 100 MOhm load resistor.

But @MerlinSmiles you're right that that may not be the most useful from a user perspective, ideally we'd change the val_mapping to reflect the actual current sensitivity ala #92 - I was hoping we could do it just with units, and that would work sort of for 1 MOhm (units=uA) but it's ugly for 100 MOhm (units=10nA?)

@MerlinSmiles MerlinSmiles Jun 15, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the driver should reflect the user-perspective.
The actual input configuration is:

  • A (meaning voltage measurement)
  • A-B(voltage too)
  • I (10^6) (Meaning current measurement, with an amplification)
  • I (10^8) (the same thing)

If I remember correctly the user doesnt have to care about the actual amplification as the instrument returns the value in amps, is that right @Rubenknex ?

And the user shouldnt have to think about the internal way how the amplification works.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, I agree (the good physicist would still need to know somewhat, as there's a difference between 1 pA sensitivity into a 1 MOhm load and into a 100 MOhm load... but that doesn't change the fact that even the good physicist doesn't need to divide by 1e6 or 1e8 all the time just to set the correct current sensitivity).

However, unless we want to write a bunch of complicated stuff now just to rip out later when we implement #92 I would suggest leaving this as is right now, based just on volts, and update it when we can do so cleanly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what happens with the 100 MOhm setting then? Does 17 mean 1 nA in both the 1 MOhm and 100 MOhm cases, or does it mean 10 pA in the 100 MOhm case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it is the same sensitivity for both 1 and 100 MOhm, otherwise it would be quite confusing. I have to check the manual to be sure though.

@MerlinSmiles MerlinSmiles Jun 15, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd propose a different way of handling this.
The units are only V and A, and the sensitivity is just a number to that unit. So I'd like put .set(1e-6) no matter of volts or amps. Or is that something you wont like @Rubenknex ?

Since this is not a setting that has to be super fast, we can make a parser that checks the input config and then allows to get or set the sensitivity in the right units?

I'll fiddle around with this. It would be nicer to have it not change in a month or two.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sure, it won't be hard to make that happen, make setter and getter functions for input_config that after changing itself, change units and val_mapping for this param (hmm, need a new method to change val_mapping, that's in __init__ right now) and get it too. Then when we abstract this concept we can simplify this without breaking changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So it would be

lockin.input_config('1 mohm')
lockin.sensitivity('50 uv/pa')
vs.
lockin.sensitivity(50e-12)

I agree using a number looks cleaner. The implementation however is a bit more difficult.

})

self.add_parameter('reserve_mode',
get_cmd='RMOD?',
set_cmd='RMOD {}',
val_mapping={
'high reserve': '0\n',
'normal': '1\n',
'low noise': '2\n',
})

self.add_parameter('time_constant',
get_cmd='OFLT?',
set_cmd='OFLT {}',
val_mapping={
"10 us": '0\n',
"30 us": '1\n',
"100 us": '2\n',
"300 us": '3\n',
"1 ms": '4\n',
"3 ms": '5\n',
"10 ms": '6\n',
"30 ms": '7\n',
"100 ms": '8\n',
"300 ms": '9\n',
"1 s": '10\n',
"3 s": '11\n',
"10 s": '12\n',
"30 s": '13\n',
"100 s": '14\n',
"300 s": '15\n',
"1 ks": '16\n',
"3 ks": '17\n',
"10 ks": '18\n',
"30 ks": '19\n',
})

self.add_parameter('filter_slope',
get_cmd='OFSL?',
set_cmd='OFSL {}',
val_mapping={
'6 db/oct': '0\n',
'12 db/oct': '1\n',
'18 db/oct': '2\n',
'24 db/oct': '3\n',
})

self.add_parameter('sync_filter',
get_cmd='SYNC?',
set_cmd='SYNC {}',
val_mapping={
'off': '0\n',
'on': '1\n',
})

# Display and output

# Aux input/output
for i in [0, 1, 2, 3]:
self.add_parameter('aux_in{}'.format(i),
get_cmd='OAUX? {}'.format(i),
get_parser=float,
units='V')

self.add_parameter('aux_out{}'.format(i),
get_cmd='AUXV? {}'.format(i),
get_parser=float,
set_cmd='AUXV {0}, {{}}'.format(i),
units='V')

# Setup
self.add_parameter('output_interface',
get_cmd='OUTX?',
set_cmd='OUTX {}',
val_mapping={
'rs232': '0\n',
'gpib': '1\n',
})

# Auto functions
self.add_function('auto_gain', call_cmd='AGAN')
self.add_function('auto_reserve', call_cmd='ARSV')
self.add_function('auto_phase', call_cmd='APHS')

# Data storage

# Data transfer
self.add_parameter('X',
get_cmd='OUTP?1',
get_parser=float,
units='V')

self.add_parameter('Y',
get_cmd='OUTP?2',
get_parser=float,
units='V')

self.add_parameter('R',
get_cmd='OUTP?3',
get_parser=float,
units='V')

self.add_parameter('P',
get_cmd='OUTP?4',
get_parser=float,
units='deg')

# Interface
self.add_function('reset', call_cmd='*RST')

self.add_function('disable_front_panel', call_cmd='OVRM 0')
self.add_function('enable_front_panel', call_cmd='OVRM 1')
Empty file.