diff fsp7_phasenoise.py @ 81:1947d10f9395

- 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.
author Daniel O'Connor <doconnor@gsoft.com.au>
date Fri, 27 Sep 2024 16:56:44 +0930
parents 23c96322cfb6
children 6ce8ed5ba76d
line wrap: on
line diff
--- 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')