# This Python file uses the following encoding: utf-8
# Valentin John, spring 2021
# Simon Zihlmannr <zihlmann.simon@gmail.com>, spring 2021
import warnings
from qcodes import Instrument, VisaInstrument
from qcodes.instrument.channel import InstrumentChannel
from qcodes.utils.validators import Numbers, Enum
from qcodes.utils.helpers import create_on_off_val_mapping
[docs]
class HS900Channel(InstrumentChannel):
"""
Class to hold the Holzworth channels, i.e.
CH1, CH2, ...
"""
[docs]
def __init__(self, parent: Instrument, name: str, channel: str) -> None:
"""
Args:
parent: The Instrument instance to which the channel is
to be attached.
name: The 'colloquial' name of the channel
channel: The name used by the Holzworth, i.e. either
'CH1' or 'CH2'
"""
super().__init__(parent, name)
self.channel = channel
self._min_f = self._parse_f_unit(
self.ask_raw(':{}:Freq:MIN?'.format(channel)))
self._max_f = self._parse_f_unit(
self.ask_raw(':{}:Freq:MAX?'.format(channel)))
self._min_pwr = self._parse_pwr_unit(
self.ask_raw(':{}:PWR:MIN?'.format(channel)))
self._max_pwr = self._parse_pwr_unit(
self.ask_raw(':{}:PWR:MAX?'.format(channel)))
self._min_phase = self._parse_phase_unit(
self.ask_raw(':{}:PHASE:MIN?'.format(channel)))
self._max_phase = self._parse_phase_unit(
self.ask_raw(':{}:PHASE:MAX?'.format(channel)))
self.add_parameter(name='state',
label='State',
get_parser=str,
get_cmd=':{}:PWR:RF?'.format(self.channel),
set_cmd=self._set_state,
val_mapping=create_on_off_val_mapping(on_val='ON',
off_val='OFF')
)
self.add_parameter(name='power',
label='Power',
get_parser=float,
get_cmd=':{}:PWR?'.format(self.channel),
set_cmd= self._set_pwr,
unit='dBm',
vals=Numbers(min_value=self._min_pwr,
max_value=self._max_pwr)
)
self.add_parameter(name='frequency',
label='Frequency',
get_parser=float,
get_cmd=self._get_f,
set_cmd= self._set_f,
unit='Hz',
vals=Numbers(min_value=self._min_f,
max_value=self._max_f)
)
self.add_parameter(name='phase',
label='Phase',
get_parser=float,
get_cmd=':{}:PHASE?'.format(self.channel),
set_cmd= self._set_phase,
unit='deg',
vals=Numbers(min_value=self._min_phase,
max_value=self._max_phase)
)
self.add_parameter(name='temp',
label='Temperature',
get_parser=float,
get_cmd=self._get_temp,
unit='C')
def _parse_f_unit(self, raw_str:str) -> float:
"""
Function that converts strings consisting of a number and a unit into
frequencies in Hz and returing it as a float:
Args:
raw_str: String of the form '100 MHz'
"""
f, unit = raw_str.split(' ')
unit_dict = {
'GHz': 1e9,
'MHz': 1e6,
'kHz': 1e3
}
if unit not in unit_dict.keys():
raise RuntimeError('{} is not in {}. Cannot parse {}.'
.format(unit, unit_dict.keys(), unit))
frequency = float(f) * unit_dict[unit]
return frequency
def _parse_pwr_unit(self, raw_str:str) -> float:
"""
Function that converts strings consisting of a number only or
a number plus a unit dBm into a float in dBm:
Args:
raw_str: String of the form '-10' or '-10 dBm'
"""
try:
power = float(raw_str)
except:
pwr, unit = raw_str.split(' ')
power = float(pwr)
return power
def _parse_phase_unit(self, raw_str:str) -> float:
"""
Function that converts strings consisting of a number only or
a number plus a unit deg into a float in deg:
Args:
raw_str: String of the form '90' or '90deg'
"""
try:
phase = float(raw_str)
except:
phase = float(raw_str[:-3])
return phase
def _set_state(self, st:str) -> None:
"""
Function that turns the channel on or off
Args:
st (str): accepts as argument 'ON' or 'OFF', only in CAPITAL
letters
Raises:
RuntimeError: Function compares reply from instrument and raises
RuntimeError if state setting was not performed sucessfully
"""
write_str = ':{}:PWR:RF:'.format(self.channel) + str(st)
read_str = self.ask(write_str)
if read_str != 'RF POWER '+str(st):
raise RuntimeError(
'{} is not \'State Set\'. Setting state did not work'
.format(read_str))
def _get_f(self) -> float:
"""
Getting the fundamental frequency from the RF source channel
in Hz. Instrument gives frequency as a string in the format
'800.0 MHz'.
Returns:
float: frequency in Hz, e.g. 0.8e9
"""
raw_str = self.ask(':{}:FREQ?'.format(self.channel))
return self._parse_f_unit(raw_str)
def _set_f(self, f:float) -> None:
"""Function that sets the frequency of a channel.
Args:
f (float): RF source channel fundamental frequency in Hz
Raises:
RuntimeError: Instrument tells us if frequency has been set
correctly.
Otherwise RuntimeError.
"""
write_str = ':{}:FREQ:'.format(self.channel) + str(f/1e9) + 'GHz'
read_str = self.ask(write_str)
if read_str != 'Frequency Set':
raise RuntimeError(
'{} is not \'Frequency Set\'. Setting frequency did not work'
.format(read_str))
def _set_pwr(self, pwr:float) -> None:
"""Setting the power of the RF source channel in dBm.
Args:
pwr (float): power in dBm
Raises:
RuntimeError: Instrument tells us if frequency has been set
correctly.
Otherwise RuntimeError.
"""
write_str = ':{}:PWR:'.format(self.channel) + str(pwr) + 'dBm'
read_str = self.ask(write_str)
if read_str != 'Power Set':
raise RuntimeError(
'{} is not \'Power Set\'. Setting power did not work'
.format(read_str))
def _set_phase(self, ph:float) -> None:
"""Setting the phase in deg.
Args:
ph (float): phase angle in deg
Raises:
RuntimeError: Instrument tells us if phase has been set correctly.
Otherwise RuntimeError.
"""
write_str = ':{}:PHASE:'.format(self.channel) + str(float(ph)) + 'deg'
read_str = self.ask(write_str)
if read_str != 'Phase Set':
raise RuntimeError(
'{} is not \'Phase Set\'. Setting phase did not work'
.format(read_str))
def _get_temp(self) -> float:
"""Getting the temperature of a channel input/output in deg Celsius.
Instrument returns string in the form 'Temp = 54C'
Returns:
float: Temperature in C, e.g. 54
"""
raw_str = self.ask(':{}:TEMP?'.format(self.channel))
T = raw_str.split(' ')[-1][:-1]
return float(T)
[docs]
class HS900(VisaInstrument):
"""
QCoDeS driver for the Holzworth HS900 RF synthesizer.
"""
[docs]
def __init__(self, name: str, address: str, **kwargs) -> None:
"""
Args:
name: Name to use internally in QCoDeS
address: VISA ressource address
"""
super().__init__(name, address, terminator='\n', **kwargs)
self.add_parameter(name='channel_names',
label='Channels',
get_parser=str,
get_cmd=self._get_channels) # No of ports
self.add_parameter(name='ref',
label='Reference',
get_parser=str,
get_cmd=':REF:STATUS?',
set_cmd=self._set_ref,
vals=Enum('ext10', 'ext100', 'int100'))
self.add_parameter(name='ref_locked',
label='Clock Locked',
get_parser=str,
get_cmd=self._get_ref_locked)
model = self.IDN()['model']
knownmodels = ['HS9001B', 'HS9002B', 'HS9003B', 'HS9004B',
'HS9005B', 'HS9006B', 'HS9007B', 'HS9008B']
# Driver was only tested with 'HS9002B'.
# Other modesl in 'knownmodels' seem to work according to manual.
if model not in knownmodels:
kmstring = ('{}, '*(len(knownmodels)-1)).format(*knownmodels[:-1])
kmstring += 'and {}.'.format(knownmodels[-1])
warnings.warn('This model {} is unknown and might not be'
'compatible with the driver. Known models'
'are: {}'.format(model, kmstring))
# Add the channel to the instrument
channels = self.ask_raw(':ATTACH?').split(':')[2:-1]
for ch_name in channels:
channel = HS900Channel(self, ch_name, ch_name)
self.add_submodule(ch_name, channel)
self.connect_message()
def _get_channels(self) -> list:
"""Getting the available channel names. Instrument returns string
in the form :REF:CH1:CH2:'
Returns:
list: list with available channel names, e.g. ['CH1', 'CH2']
"""
raw_str = self.ask(':ATTACH?')
channels = raw_str.split(':')[2:-1]
return channels
def _set_ref(self, f_ref_str:str) -> None:
"""
Function that sets clock reference
Args:
f_ref_str (str): accepts as argument ext10, ext100 and int100,
for internal and external reference with a clock
reference frequency of 10 or 100 MHz
Raises:
RuntimeError: Function compares reply from instrument and raises
RuntimeError if reference setting was not performed
sucessfully
"""
location = f_ref_str[:3]
f_ref = f_ref_str[3:]
write_str = ':REF:{}:{}MHz'.format(location.swapcase(), str(f_ref))
read_str = self.ask(write_str)
PLL = {'ext10':'PLL Enabled',
'ext100':'PLL Enabled',
'int100':'PLL Disabled'
}
response = 'Reference Set to {}MHz {}ernal, {}.'.format(str(f_ref),
location.capitalize(),
PLL[f_ref_str])
if read_str != response:
raise RuntimeError(
'\'{}\' is not \'Reference Set to {}MHz {}ernal, {}\'.'\
'Setting reference did not work.'
.format(read_str,
str(f_ref),
location.capitalize(),
PLL[f_ref_str]))
def _get_ref_locked(self) -> bool:
"""
Function that checks whether the Holzworth is locked through a PLL
with an external reference
Returns:
bool: True if properly locked via the phase locked loop (PLL),
False if not.
"""
locked = False
read_str = self.ask(':REF:PLL?')
if read_str == '1 PLL Locked, 0 errors':
locked = True
return locked