# HG changeset patch # User Daniel O'Connor # Date 1727422004 -34200 # Node ID 1947d10f93958a1fa9b9b3af2244063a381a81a6 # Parent b280fe3b696afd496e731fd1351b2e875155e32a - Search for signal frequency iteratively to improve accuracy. - Use narrower bandwidths (via FFT filter) for close in measurements. - Work around bug in firmware when using FFT filter. - Actually _raise_ the exception when the signal power is too low. diff -r b280fe3b696a -r 1947d10f9395 fsp7_phasenoise.py --- a/fsp7_phasenoise.py Fri Sep 27 16:55:01 2024 +0930 +++ b/fsp7_phasenoise.py Fri Sep 27 16:56:44 2024 +0930 @@ -27,6 +27,7 @@ import argparse import math +import misc import numpy import os import PIL @@ -36,20 +37,6 @@ import sys import time -def sifmt(_v, dp = 3, unit = 'Hz', sp = ' '): - '''Format a number using SI prefixes''' - si_prefixes = ('T', 'G', 'M', 'k', '', 'm', 'ยต', 'n', 'p') - scale = 10 ** 12 - v = abs(_v) - if v == 0: - sip = "" - scale = 0 - for i, sip in enumerate(si_prefixes): - if v >= scale: - break - scale /= 1e3 - return ('%.' + str(dp) + 'f%s%s%s') % (_v / scale, sp, si_prefixes[i], unit) - def setupsweep(r, rbw, vbw, centre = None, span = None, start = None, stop = None): '''Helper function to set various sweep parameters''' if centre is not None: @@ -61,14 +48,18 @@ if stop is not None: r.write('SENSE1:FREQ:STOP %f Hz' % (stop)) if rbw is not None: + # Need to use FFT filters below 10Hz BW + if rbw < 10: + r.write('BAND:TYPE FFT') + else: + r.write('BAND:TYPE NORM') r.write('SENS1:BAND:RES %f Hz' % (rbw)) if vbw is not None: r.write('SENS1:BAND:VID %f Hz' % (vbw)) def dosweep(r, sweeps): '''Helper function to trigger a sweep and wait for it to finish''' - swt = float(r.ask('SWE:TIME?')) - tout = swt * 5 * sweeps + tout = getsweeptime(r) * 5 * sweeps if tout < 1: tout = 1 #print('Sweep time', swt) @@ -99,19 +90,30 @@ return peaks_f, peaks_pwr +def getsweeptime(r): + '''Return sweep time in seconds''' + # According to the manual SWE:TIME works for both normal and FFT filters + # however in practise it doesn't get a response for FFT ones so we fake it + + if r.ask('BAND:TYPE?').decode('ascii') == 'FFT': + return 5 + else: + return float(r.ask('SWE:TIME?')) + def phasenoise(r, nominal, sweeps, atten, rlev, yscale, ssprefix): '''Main function to find a signal, do some phase noise measurements and wideband sweeps''' cpwrlim = -30 measurements = ( - { 'offset' : 10, 'span' : 100, 'rbw' : 10, 'vbw' : 10 }, - { 'offset' : 100, 'span' : 250, 'rbw' : 10, 'vbw' : 10 }, - { 'offset' : 1e3, 'span' : 2.5e3, 'rbw' : 10, 'vbw' : 30 }, + { 'offset' : 10, 'span' : 100, 'rbw' : 1, 'vbw' : 3 }, + { 'offset' : 100, 'span' : 250, 'rbw' : 1, 'vbw' : 3 }, + { 'offset' : 1e3, 'span' : 2.5e3, 'rbw' : 1, 'vbw' : 3 }, { 'offset' : 10e3, 'span' : 25e3, 'rbw' : 100, 'vbw' : 300 }, { 'offset' : 100e3, 'span' : 250e3, 'rbw' : 100, 'vbw' : 300 }, { 'offset' : 1e6, 'span' : 2.1e6, 'rbw' : 1e3, 'vbw' : 3e3 }, ) # Reset to defaults r.write('*RST') + r.write('*CLS') # Set to single sweep mode r.write('INIT:CONT OFF') @@ -136,32 +138,41 @@ r.write('DISP:WIND:TRAC:Y:RLEV %f' % (rlev)) # - # Look for signal + # Look from 100kHz down to 100Hz to narrow down the exact frequency # - # Set frequency range etc - setupsweep(r, 10, 30, centre = nominal, span = 1e3) - - # Switch marker 1 on in screen A - r.write('CALC:MARK1 ON') - print('Looking for signal (%.1f seconds)' % (float(r.ask('SWE:TIME?')))) - - # Do the sweep - dosweep(r, 1) + found = False + cmeas = nominal + for idx, span in enumerate((100e3, 10e3, 1e3, 100)): + setupsweep(r, span / 50, span / 50 * 3, centre = cmeas, span = span) + print('Looking for signal at %s+/-%s (%.1f seconds)' % ( + misc.sifmt(nominal), misc.sifmt(span, dp = 1), + getsweeptime(r))) + # Do the sweep + dosweep(r, 1) - # Check the instrument is happy - status = int(r.ask('STAT:QUES:COND?')) - pwrstat = int(r.ask('STAT:QUES:POW:COND?')) - if status != 0 or pwrstat != 0: - print('Instrument warning, status %s power status %s' % - (bin(status), bin(pwrstat))) - # Find the peak - r.write('CALC:MARK1:MAX') - cpwr = float(r.ask('CALC:MARK1:Y?')) - if cpwr < cpwrlim: - Exception('Power too low / not found: %.1f dBm vs %.1f dBm' % (cpwr, cpwrlim)) - cmeas = float(r.ask('CALC:MARK1:X?')) - print('Found signal at %s with power %.1f dBm' % (sifmt(cmeas, dp = 6), cpwr)) + # Check the instrument is happy + status = int(r.ask('STAT:QUES:COND?')) + pwrstat = int(r.ask('STAT:QUES:POW:COND?')) + if status != 0 or pwrstat != 0: + # Dump a screenshot for debugging + screenshot(r, '%s-sweep-inst-warnings.png' % (ssprefix)) + raise Exception('Instrument warning, status %s power status %s' % + (bin(status), bin(pwrstat))) + # Find the peak + r.write('CALC:MARK1 ON') + r.write('CALC:MARK1:MAX') + cpwr = float(r.ask('CALC:MARK1:Y?')) + if cpwr < cpwrlim: + # Dump a screenshot for debugging + screenshot(r, '%s-sweep-no-power.png' % (ssprefix)) + raise Exception('Power too low / not found: %.1f dBm vs %.1f dBm' % (cpwr, cpwrlim)) + else: + found = True + cmeas = float(r.ask('CALC:MARK1:X?')) + print('Found signal at %s with power %.1f dBm' % (misc.sifmt(cmeas, dp = 6), cpwr)) + if not found: + print('Unable to find signal') # Centre peak r.write('CALC:MARK1:FUNC:CENT') @@ -175,7 +186,6 @@ for idx, m in enumerate(measurements): # Setup measurement setupsweep(r, m['rbw'], m['vbw'], span = m['span']) - # Do the sweep dosweep(r, sweeps) @@ -184,20 +194,20 @@ r.write('CALC:DELT1:MOD REL') r.write('CALC:DELT1:X %f Hz' % (m['offset'])) meas = float(r.ask('CALC:DELT:FUNC:PNO:RES?')) - print('Offset %7s %7.2f dBc/Hz' % (sifmt(m['offset'], dp = 0), meas)) + print('Offset %7s %7.2f dBc/Hz' % (misc.sifmt(m['offset'], dp = 0), meas)) # Take screen shot and save it if ssprefix is not None: screenshot(r, '%s-%d_%s_offset.png' % ( - ssprefix, idx, sifmt(m['offset'], dp = 0, sp = ''))) + ssprefix, idx, misc.sifmt(m['offset'], dp = 0, sp = ''))) # Do a wide band scan if ssprefix is not None: start = 1e6 stop = 100e6 setupsweep(r, 1e3, 3e3, start = start, stop = stop) - print('Scanning %s - %s (%.1f seconds)' % (sifmt(start, dp = 0), sifmt(stop, dp = 0), - float(r.ask('SWE:TIME?')))) + print('Scanning %s - %s (%.1f seconds)' % (misc.sifmt(start, dp = 0), misc.sifmt(stop, dp = 0), + getsweeptime(r))) dosweep(r, sweeps) # Show peaks r.write('CALC:MARK:AOFF') @@ -210,8 +220,8 @@ start = nominal * 0.9 stop = 2 * nominal + nominal * 0.1 setupsweep(r, 1e3, 3e3, start = start, stop = stop) - print('Scanning %s - %s (%.1f seconds)' % (sifmt(start, dp = 0), sifmt(stop, dp = 0), - float(r.ask('SWE:TIME?')))) + print('Scanning %s - %s (%.1f seconds)' % (misc.sifmt(start, dp = 0), misc.sifmt(stop, dp = 0), + getsweeptime(r))) dosweep(r, sweeps) # Show peaks r.write('CALC:MARK:AOFF')