#
import time
from .visa import VisaDevice
[docs]class DSA815(VisaDevice):
MIN_FREQUENCY = 0.0
MAX_FREQUENCY = 1500.0
MIN_SPAN = 0.0001
MAX_REF_LEVEL = 20.0
MIN_REF_LEVEL = -100.0
REF_LEVEL_STEP = 1.0
MIN_ATT = 0.0
MAX_ATT = 30.0
ATT_STEP = 1.0
MIN_MAXMIXER_LEVEL = -30.0
MAX_MAXMIXER_LEVEL = 0.0
MAXMIXER_LEVEL_STEP = 1.0
DETECTOR_MODES = {'POS': 0, 'NORM': 1, 'NEG': 2, 'SAMP': 3}
def __init__(self, visa_rm, visa_id):
"""Create an instance of :py:class:`DSA815`.
:param visa_rm: A reference to a Visa resource manager.
As returned (for example) by a call to
:py:`pyvisa.ResourceManager()`.
:type visa_rm:
:param visa_id: The ID of the analyzer to connect with.
:type visa_id: str
"""
super().__init__(visa_rm, visa_id)
def local(self):
if self.inst:
self.inst.control_ren(0)
def clear(self):
pass
@property
def ident(self):
return self.inst.query("IDN?")
@property
def vavg(self):
"""The number of sweeps to average over.
"""
return int(self.inst.query(":TRAC:AVER:COUN?"))
@property
def ref_level(self):
"""The analyzer reference level in dBm.
"""
return float(self.inst.query(":DISP:WIN:TRAC:Y:SCAL:RLEV?"))
@property
def freq(self):
"""The analyzer sweep centre frequency in MHz.
"""
return float(self.inst.query(":SENS:FREQ:CENT?")) / 1e6
@property
def start_freq(self):
"""The analyzer sweep start frequency in MHz.
"""
return float(self.inst.query(":SENS:FREQ:STAR?")) / 1e6
@property
def stop_freq(self):
"""The analyzer sweep stop frequency in MHz.
"""
return float(self.inst.query(":SENS:FREQ:STOP?")) / 1e6
@property
def fspan(self):
"""The analyzer sweep frequency span in MHz.
"""
return float(self.inst.query(":SENS:FREQ:SPAN?")) / 1e6
@property
def rbw(self):
"""The analyzer resolution bandwidth in Hz.
"""
return float(self.inst.query(":SENS:BAND:RES?"))
@property
def rbw_auto(self):
"""The auto setting mode for the resolution bandwidth.
"""
return bool(self.inst.query(":SENS:BAND:RES:AUTO?"))
@property
def sweep_time(self):
"""The analyzer sweep time in seconds.
"""
return float(self.inst.query(":SENS:SWEEP:TIME?"))
@property
def vbw(self):
"""The analyzer video bandwidth in Hz.
"""
return float(self.inst.query(":SENS:BAND:VID?"))
@property
def atten(self):
"""The analyzer input attenuator setting in dB.
"""
return float(self.inst.query(":SENS:POW:RF:ATT?"))
@property
def atten_auto(self):
"""The auto setting mode for the analyzer input attenuation.
"""
return bool(self.inst.query(":SENS:POW:RF:ATT:AUTO?"))
@property
def scale(self):
"""The scale of the analyzer power axis in dB. The valid range
of values is 0.1 to 20 dB.
"""
return float(self.inst.query(":DISP:WIN:TRAC:Y:SCAL:PDIV?"))
@property
def unit(self):
"""The unit of measurement for the analyzer y-axis. This should
be one of :py:`[DBM, DBMV, DBUV, V, W]`.
"""
return self.inst.query(":UNIT:POW?")
@property
def mixer_level(self):
"""The maximum power (in dBm) at the input mixer. The valid range
of values is -30 to 0 dBm in increments of 10 dB.
"""
return float(self.inst.query(":SENS:POW:RF:MIX:RANG:UPP?"))
@property
def detector_mode(self):
"""The analyzer detector type. This should be one of
:py:`['NEG', 'NORM', 'POS', 'RMS', 'SAMP', 'VAV', 'QPE']`
"""
return self.query(":SENS:DET:FUNC?")
@vavg.setter
def vavg(self, vavg):
if self.inst:
self.write(":TRAC:AVER:COUN {}".format(vavg))
@ref_level.setter
def ref_level(self, level):
if self.inst:
self.write(":DISP:WIN:TRAC:Y:SCAL:RLEV {}".format(
level))
@freq.setter
def freq(self, f):
if self.inst:
self.write(":SENS:FREQ:CENT {}".format(f * 1e6))
@start_freq.setter
def start_freq(self, f):
if self.inst:
self.write(":SENS:FREQ:STAR {}".format(f * 1e6))
@stop_freq.setter
def stop_freq(self, f):
if self.inst:
self.write(":SENS:FREQ:STOP {}".format(f * 1e6))
@fspan.setter
def fspan(self, span):
if self.inst:
self.write(":SENS:FREQ:SPAN {}".format(span * 1e6))
@rbw.setter
def rbw(self, res):
if self.inst:
self.write(":SENS:BAND:RES {}".format(res))
@rbw_auto.setter
def rbw_auto(self, auto):
if auto:
self.write(":SENS:BAND:RES:AUTO ON")
else:
self.write(":SENS:BAND:RES:AUTO OFF")
@sweep_time.setter
def sweep_time(self, t):
if self.inst:
self.write(":SENS:SWEEP:TIME {}".format(t))
@vbw.setter
def vbw(self, v):
if self.inst:
self.write(":SENS:BAND:VID {}".format(v))
@atten.setter
def atten(self, att):
if self.inst:
self.write(":SENS:POW:RF:ATT {}".format(att))
@atten_auto.setter
def atten_auto(self, auto):
if auto:
self.write(":SENS:POW:RF:AUTO ON")
else:
self.write(":SENS:POW:RF:AUTO OFF")
@scale.setter
def scale(self, s):
if self.inst:
self.write(":DISP:WIN:TRAC:Y:SCAL:PDIV {}".format(s))
@unit.setter
def unit(self, u):
if self.inst:
self.write(":UNIT:POW {}".format(u))
@mixer_level.setter
def mixer_level(self, lvl):
if self.inst:
self.write(":SENS:POW:RF:MIX:RANG:UPP {}".format(lvl))
@detector_mode.setter
def detector_mode(self, det):
if self.inst:
self.write(":SENS:DET:FUNC {}".format(det))
[docs] def initialize(self):
"""Initialize the analyzer state.
Sets the analyzer to single sweep mode, sets the
sweep averaging mode and number of sweeps to average
over. Also sets the reference level to the default
value.
"""
super().initialize()
if self.inst is not None:
self.write(":SYST:PRES")
self.write(":TRAC1:AVER:TYPE VID")
self.write(":INIT:CONT OFF")
def reset(self):
self.write(":SYST:PRES")
self.write(":INIT:CONT ON")
[docs] def sweep(self):
"""Carry out a sweep using the currently set analyzer settings.
"""
self.write(":TRAC:AVER:CLE")
avg_count = int(self.query(":TRAC:AVER:COUN?"))
curr_avg_count = 0
sweep_time = float(self.query(":SENS:SWE:TIME?"))
while avg_count > curr_avg_count:
self.write(":INIT:IMM")
time.sleep(sweep_time * 1.1)
curr_avg_count = int(self.query(":TRAC:AVER:COUN:CURR?"))
[docs] def trace_data(self):
"""Retrieve the current analyzer trace data.
:return: A list of :py:`float` values.
"""
tstr = self.query(":TRAC:DATA? TRACE1")
count_len = int(tstr[1])
tdata = [float(tval) for tval in tstr[count_len+3:-1].split(', ')]
return tdata
[docs] def measure_pwr(self, freq):
"""Measure the signal power of the peak near the specified
frequency.
This makes use of the spectrum analyzer peak search function
to find the maximum signal value in the currently set span.
This assumes that the relevant spectrum analyzer configuration
has already been set. For example:
.. code-block:: python
>>> sa.vavg = 5
>>> sa.fspan = 0.1
>>> sa.ref_level = 10.0
>>> sa.measure_pwr(250.0)
(2.732128, 250.000166)
:param freq: The centre frequency to make the measurement (in MHz).
:type freq: float
:return: A tuple of :py:`(pwr, freq)` where :py:`pwr` is the
measured power (in dBm) of the signal at frequency :py:`freq`
(in MHz).
"""
self.freq = freq
self.write(":TRAC:AVER:CLE")
avg_count = int(self.query(":TRAC:AVER:COUN?"))
curr_avg_count = int(self.query(":TRAC:AVER:COUN:CURR?"))
sweep_time = float(self.query(":SENS:SWE:TIME?"))
while curr_avg_count < avg_count:
self.write(":INIT:IMM")
time.sleep(sweep_time * 1.1)
curr_avg_count = int(self.query(":TRAC:AVER:COUN:CURR?"))
self.write(":CALC:MARK1:MAX:MAX")
mkr_pwr = float(self.query(":CALC:MARK1:Y?"))
mkr_freq = float(self.query(":CALC:MARK1:X?")) / 1e6
return mkr_pwr, mkr_freq
[docs] def measure_next_pwr(self):
"""Measure the power and freq. of the next highest peak.
This assumes that the :py:`measure_pwr` method has already
been called. For example:
.. code-block:: python
>>> sa.vavg = 5
>>> sa.fspan = 0.1
>>> sa.ref_level = 10.0
>>> sa.measure_pwr(250.0)
(2.732128, 250.000166)
>>> sa.measure_next_pwr()
(1.986321, 250.050134)
Note that if the analyzer cannot find another peak the
cal to :py:`measure_next_pwr` will return the power and
frequency values for the first peak.
:return: A tuple of two floats with the power (in dBm) of
next highest peak being first and the second being the
frequency (in MHz) of the peak.
"""
self.write(":CALC:MARK1:MAX:NEXT")
mkr_pwr = float(self.query(":CALC:MARK1:Y?"))
mkr_freq = float(self.query(":CALC:MARK1:X?")) / 1e6
return mkr_pwr, mkr_freq