Source code for qcodes_contrib_drivers.drivers.ZurichInstruments.HF2LI

from typing import Dict, List, Optional, Sequence, Any, Union
import numpy as np
import logging
log = logging.getLogger(__name__)

import zhinst.utils
import qcodes as qc
from qcodes.instrument.base import Instrument
import qcodes.utils.validators as vals

[docs] class HF2LI(Instrument): """Qcodes driver for Zurich Instruments HF2LI lockin amplifier. This driver is meant to emulate a single-channel lockin amplifier, so one instance has a single demodulator, a single sigout channel, and multiple auxout channels (for X, Y, R, Theta, or an arbitrary manual value). Multiple instances can be run simultaneously as independent lockin amplifiers. This instrument has a great deal of additional functionality that is not currently supported by this driver. Args: name: Name of instrument. device: Device name, e.g. "dev204", used to create zhinst API session. demod: Index of the demodulator to use. sigout: Index of the sigout channel to use as excitation source. auxouts: Dict of the form {output: index}, where output is a key of HF2LI.OUTPUT_MAPPING, for example {"X": 0, "Y": 3} to use the instrument as a lockin amplifier in X-Y mode with auxout channels 0 and 3. num_sigout_mixer_channels: Number of mixer channels to enable on the sigouts. Default: 1. """ OUTPUT_MAPPING = {-1: 'manual', 0: 'X', 1: 'Y', 2: 'R', 3: 'Theta'} def __init__(self, name: str, device: str, demod: int, sigout: int, auxouts: Dict[str, int], num_sigout_mixer_channels: int=1, **kwargs) -> None: super().__init__(name, **kwargs) instr = zhinst.utils.create_api_session(device, 1, required_devtype='HF2LI') self.daq, self.dev_id, self.props = instr self.demod = demod self.sigout = sigout self.auxouts = auxouts log.info(f'Successfully connected to {name}.') for ch in self.auxouts: self.add_parameter( name=ch, label=f'Scaled {ch} output value', unit='V', get_cmd=lambda channel=ch: self._get_output_value(channel), get_parser=float, docstring=f'Scaled and demodulated {ch} value.' ) self.add_parameter( name=f'gain_{ch}', label=f'{ch} output gain', unit='V/Vrms', get_cmd=lambda channel=ch: self._get_gain(channel), get_parser=float, set_cmd=lambda gain, channel=ch: self._set_gain(gain, channel), vals=vals.Numbers(), docstring=f'Gain factor for {ch}.' ) self.add_parameter( name=f'offset_{ch}', label=f'{ch} output offset', unit='V', get_cmd=lambda channel=ch: self._get_offset(channel), get_parser=float, set_cmd=lambda offset, channel=ch: self._set_offset(offset, channel), vals=vals.Numbers(-2560, 2560), docstring=f'Manual offset for {ch}, applied after scaling.' ) self.add_parameter( name=f'output_{ch}', label=f'{ch} outptut select', get_cmd=lambda channel=ch: self._get_output_select(channel), get_parser=str ) # Making output select only gettable, since we are # explicitly mapping auxouts to X, Y, R, Theta, etc. self._set_output_select(ch) self.add_parameter( name='phase', label='Phase', unit='deg', get_cmd=self._get_phase, get_parser=float, set_cmd=self._set_phase, vals=vals.Numbers(-180,180) ) self.add_parameter( name='time_constant', label='Time constant', unit='s', get_cmd=self._get_time_constant, get_parser=float, set_cmd=self._set_time_constant, vals=vals.Numbers() ) self.add_parameter( name='frequency', label='Frequency', unit='Hz', get_cmd=self._get_frequency, get_parser=float ) self.add_parameter( name='sigout_range', label='Signal output range', unit='V', get_cmd=self._get_sigout_range, get_parser=float, set_cmd=self._set_sigout_range, vals=vals.Enum(0.01, 0.1, 1, 10) ) self.add_parameter( name='sigout_offset', label='Signal output offset', unit='V', get_cmd=self._get_sigout_offset, get_parser=float, set_cmd=self._set_sigout_offset, vals=vals.Numbers(-1, 1), docstring='Multiply by sigout_range to get actual offset voltage.' ) for i in range(num_sigout_mixer_channels): self.add_parameter( name=f'sigout_enable{i}', label=f'Signal output mixer {i} enable', get_cmd=lambda mixer_channel=i: self._get_sigout_enable(mixer_channel), get_parser=float, set_cmd=lambda amp, mixer_channel=i: self._set_sigout_enable(mixer_channel, amp), vals=vals.Enum(0,1,2,3), docstring="""\ 0: Channel off (unconditionally) 1: Channel on (unconditionally) 2: Channel off (will be turned off on next change of sign from negative to positive) 3: Channel on (will be turned on on next change of sign from negative to positive) """ ) self.add_parameter( name=f'sigout_amplitude{i}', label=f'Signal output mixer {i} amplitude', unit='Gain', get_cmd=lambda mixer_channel=i: self._get_sigout_amplitude(mixer_channel), get_parser=float, set_cmd=lambda amp, mixer_channel=i: self._set_sigout_amplitude(mixer_channel, amp), vals=vals.Numbers(-1, 1), docstring='Multiply by sigout_range to get actual output voltage.' ) def _get_phase(self) -> float: path = f'/{self.dev_id}/demods/{self.demod}/phaseshift/' return self.daq.getDouble(path) def _set_phase(self, phase: float) -> None: path = f'/{self.dev_id}/demods/{self.demod}/phaseshift/' self.daq.setDouble(path, phase) def _get_gain(self, channel: str) -> float: path = f'/{self.dev_id}/auxouts/{self.auxouts[channel]}/scale/' return self.daq.getDouble(path) def _set_gain(self, gain: float, channel: str) -> None: path = f'/{self.dev_id}/auxouts/{self.auxouts[channel]}/scale/' self.daq.setDouble(path, gain) def _get_offset(self, channel: str) -> float: path = f'/{self.dev_id}/auxouts/{self.auxouts[channel]}/offset/' return self.daq.getDouble(path) def _set_offset(self, offset: float, channel: str) -> None: path = f'/{self.dev_id}/auxouts/{self.auxouts[channel]}/offset/' self.daq.setDouble(path, offset) def _get_output_value(self, channel: str) -> float: path = f'/{self.dev_id}/auxouts/{self.auxouts[channel]}/value/' return self.daq.getDouble(path) def _get_output_select(self, channel: str) -> str: path = f'/{self.dev_id}/auxouts/{self.auxouts[channel]}/outputselect/' idx = self.daq.getInt(path) return self.OUTPUT_MAPPING[idx] def _set_output_select(self, channel: str) -> None: path = f'/{self.dev_id}/auxouts/{self.auxouts[channel]}/outputselect/' keys = list(self.OUTPUT_MAPPING.keys()) idx = keys[list(self.OUTPUT_MAPPING.values()).index(channel)] self.daq.setInt(path, idx) def _get_time_constant(self) -> float: path = f'/{self.dev_id}/demods/{self.demod}/timeconstant/' return self.daq.getDouble(path) def _set_time_constant(self, tc: float) -> None: path = f'/{self.dev_id}/demods/{self.demod}/timeconstant/' self.daq.setDouble(path, tc) def _get_sigout_range(self) -> float: path = f'/{self.dev_id}/sigouts/{self.sigout}/range/' return self.daq.getDouble(path) def _set_sigout_range(self, rng: float) -> None: path = f'/{self.dev_id}/sigouts/{self.sigout}/range/' self.daq.setDouble(path, rng) def _get_sigout_offset(self) -> float: path = f'/{self.dev_id}/sigouts/{self.sigout}/offset/' return self.daq.getDouble(path) def _set_sigout_offset(self, offset: float) -> None: path = f'/{self.dev_id}/sigouts/{self.sigout}/offset/' self.daq.setDouble(path, offset) def _get_sigout_amplitude(self, mixer_channel: int) -> float: path = f'/{self.dev_id}/sigouts/{self.sigout}/amplitudes/{mixer_channel}/' return self.daq.getDouble(path) def _set_sigout_amplitude(self, mixer_channel: int, amp: float) -> None: path = f'/{self.dev_id}/sigouts/{self.sigout}/amplitudes/{mixer_channel}/' self.daq.setDouble(path, amp) def _get_sigout_enable(self, mixer_channel: int) -> int: path = f'/{self.dev_id}/sigouts/{self.sigout}/enables/{mixer_channel}/' return self.daq.getInt(path) def _set_sigout_enable(self, mixer_channel: int, val: int) -> None: path = f'/{self.dev_id}/sigouts/{self.sigout}/enables/{mixer_channel}/' self.daq.setInt(path, val) def _get_frequency(self) -> float: path = f'/{self.dev_id}/demods/{self.demod}/freq/' return self.daq.getDouble(path)
[docs] def sample(self) -> dict: path = f'/{self.dev_id}/demods/{self.demod}/sample/' return self.daq.getSample(path)