import logging
from QInstrument.lib.QSerialInstrument import QSerialInstrument
logger = logging.getLogger(__name__)
[docs]
class QSR830(QSerialInstrument):
'''SRS SR830 Lock-in Amplifier
Properties
==========
Reference and Phase
-------------------
amplitude : float [V]
Amplitude of the reference sine output.
Rounded to 0.002 V.
Range: 0.004 <= amplitude <= 5.000
frequency : float [Hz]
Reference frequency for the internal oscillator.
Rounded to 5 significant digits or 0.0001 Hz, whichever is greater.
Range: 0.001 Hz <= frequency, frequency * harmonic <= 102 kHz
harmonic : int
Detection harmonic.
Range: 1 <= harmonic < 20000, frequency * harmonic <= 102 kHz
internal_reference : bool
True: use internal reference source.
False: use external reference source.
phase : float [degrees]
Reference phase shift.
Range: -360 <= phase <= 729.99
reference_trigger : int
Reference trigger for external reference.
0: sine zero crossing, 1: TTL rising edge, 2: TTL falling edge.
Must be 1 or 2 when frequency < 1 Hz.
Input and Filter
----------------
dc_coupling : bool
True: DC-coupled inputs.
False: AC-coupled inputs.
input_configuration : int
0: channel A, 1: A - B, 2: I (1 MOhm), 3: I (100 MOhm)
line_filter : int
Input line notch filter configuration.
0: no filters, 1: line notch, 2: 2x line notch, 3: both notch filters
shield_grounding : bool
True: input shielding is grounded.
False: input shielding is floating.
Gain and Time Constant
----------------------
dynamic_reserve : int
0: high dynamic reserve, 1: normal, 2: low noise
low_pass_slope : int
0: 6 dB/octave, 1: 12 dB/octave, 2: 18 dB/octave, 3: 24 dB/octave
sensitivity : int
Voltage input sensitivities (V/V mode):
0: 2 nV/fA 10: 5 μV/pA 20: 10 mV/nA
1: 5 nV/fA 11: 10 μV/pA 21: 20 mV/nA
2: 10 nV/fA 12: 20 μV/pA 22: 50 mV/nA
3: 20 nV/fA 13: 50 μV/pA 23: 100 mV/nA
4: 50 nV/fA 14: 100 μV/pA 24: 200 mV/nA
5: 100 nV/fA 15: 200 μV/pA 25: 500 mV/nA
6: 200 nV/fA 16: 500 μV/pA 26: 1 V/μA
7: 500 nV/fA 17: 1 mV/nA
8: 1 μV/pA 18: 2 mV/nA
9: 2 μV/pA 19: 5 mV/nA
synchronous_filter : bool
True: synchronous filtering below 200 Hz
(only engaged when harmonic * frequency < 200 Hz).
False: no synchronous filter.
time_constant : int
Input filter time constant.
0: 20 μs 10: 1 s
1: 30 μs 11: 3 s
2: 100 μs 12: 10 s
3: 300 μs 13: 30 s
4: 1 ms 14: 100 s
5: 3 ms 15: 300 s
6: 10 ms 16: 1 ks
7: 30 ms 17: 3 ks
8: 100 ms 18: 10 ks
9: 300 ms 19: 30 ks
'''
comm = dict(baudRate=QSerialInstrument.BaudRate.Baud9600,
dataBits=QSerialInstrument.DataBits.Data8,
stopBits=QSerialInstrument.StopBits.OneStop,
parity=QSerialInstrument.Parity.NoParity,
flowControl=QSerialInstrument.FlowControl.NoFlowControl,
eol='\n')
def _registerProperties(self) -> None:
'''Register all instrument properties via ``registerProperty()``.
Called automatically by ``QAbstractInstrument.__init__``. Subclasses
that extend the property set should call
``super()._registerProperties()`` first.
'''
# Reference and Phase
self._register('amplitude', 'SLVL', float)
self._register('frequency', 'FREQ', float)
self._register('harmonic', 'HARM', int)
self._register('internal_reference', 'FMOD', bool)
self._register('phase', 'PHAS', float)
self._register('reference_trigger', 'RSLP', int)
# Input and Filter
self._register('dc_coupling', 'ICPL', bool)
self._register('input_configuration', 'ISRC', int)
self._register('line_filter', 'ILIN', int)
self._register('shield_grounding', 'IGND', bool)
# Gain and Time Constant
self._register('dynamic_reserve', 'RMOD', int)
self._register('low_pass_slope', 'OFSL', int)
self._register('sensitivity', 'SENS', int)
self._register('synchronous_filter', 'SYNC', bool)
self._register('time_constant', 'OFLT', int)
def _registerMethods(self) -> None:
'''Register all instrument methods via ``registerMethod()``.
Called automatically by ``QAbstractInstrument.__init__``. Subclasses
that add methods should call ``super()._registerMethods()`` first.
'''
self.registerMethod('reset', self.reset)
self.registerMethod('auto_gain', self.auto_gain)
self.registerMethod('auto_reserve', self.auto_reserve)
self.registerMethod('auto_phase', self.auto_phase)
self.registerMethod('auto_offset_x', self.auto_offset_x)
self.registerMethod('auto_offset_y', self.auto_offset_y)
self.registerMethod('auto_offset_r', self.auto_offset_r)
def _register(self, name: str, cmd: str, ptype: type = float) -> None:
'''Register a standard instrument property.
Builds getter and setter from the SR830 command convention:
query is ``cmd + '?'``, set is ``cmd + value``. Bool properties
are transmitted as integers (0/1) per the instrument protocol.
Parameters
----------
name : str
Property name passed to ``registerProperty``.
cmd : str
SR830 command mnemonic (e.g. ``'FREQ'``).
ptype : type, optional
Value type: ``float`` (default), ``int``, or ``bool``.
'''
if ptype is bool:
def getter(): return bool(self.getValue(f'{cmd}?', int))
def setter(v): self.transmit(f'{cmd}{int(bool(v))}')
else:
def getter(): return self.getValue(f'{cmd}?', ptype)
def setter(v): self.transmit(f'{cmd}{ptype(v)}')
self.registerProperty(name, getter=getter, setter=setter,
ptype=ptype)
[docs]
def identify(self) -> bool:
'''Return True if the connected device identifies as an SR830.
Queries the instrument identification string (``*IDN?``) and
checks for the ``'SR830'`` model token in the response.
'''
return 'SR830' in self.handshake('*IDN?')
[docs]
def report(self) -> list[float]:
'''Return the current frequency, magnitude, and phase.
Uses the SNAP command for simultaneous capture, avoiding the
timing errors that would result from three sequential queries.
Returns
-------
list[float]
[frequency [Hz], R [V], theta [degrees]]
'''
response = self.handshake('SNAP?9,3,4')
return list(map(float, response.split(',')))
[docs]
def reset(self) -> None:
'''Reset the SR830 to its factory default settings.'''
self.transmit('*RST')
[docs]
def auto_gain(self) -> None:
'''Automatically adjust the sensitivity (autorange gain).'''
self.transmit('AGAN')
[docs]
def auto_reserve(self) -> None:
'''Automatically adjust the dynamic reserve.'''
self.transmit('ARSV')
[docs]
def auto_phase(self) -> None:
'''Automatically adjust the reference phase.'''
self.transmit('APHS')
[docs]
def auto_offset_x(self) -> None:
'''Automatically offset the X output channel to zero.'''
self.auto_offset(1)
[docs]
def auto_offset_y(self) -> None:
'''Automatically offset the Y output channel to zero.'''
self.auto_offset(2)
[docs]
def auto_offset_r(self) -> None:
'''Automatically offset the R output channel to zero.'''
self.auto_offset(3)
[docs]
def auto_offset(self, channel: int) -> None:
'''Automatically offset the specified output channel to zero.
Prefer the dedicated methods :meth:`auto_offset_x`,
:meth:`auto_offset_y`, and :meth:`auto_offset_r` for widget binding.
Parameters
----------
channel : int
1: X, 2: Y, 3: R
'''
if channel not in (1, 2, 3):
logger.warning(
f'auto_offset: channel must be 1, 2, or 3 (got {channel})')
return
self.transmit(f'AOFF{channel}')
if __name__ == '__main__':
QSR830.example()
__all__ = ['QSR830']