Mercurial > ~darius > hgwebdir.cgi > pyinst
view fsp7_phasenoise.py @ 75:576f112e0aba
Fix read timeout handling so it works over imperfect links.
author | Daniel O'Connor <doconnor@gsoft.com.au> |
---|---|
date | Fri, 27 Sep 2024 09:27:33 +0930 |
parents | b6ebe05f250f |
children | e2bb136bd2ed |
line wrap: on
line source
#!/usr/bin/env python # Copyright (c) 2014 # Daniel O'Connor <darius@dons.net.au>. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # import argparse import math import numpy import rsib import scipy import scpi import sys def setup2(r, centre, span, rbw, vbw): r.write('SENSE1:FREQ:CENT %f Hz' % (centre)) r.write('SENSE1:FREQ:SPAN %f Hz' % (span)) r.write('SENS1:BAND:RES %f Hz' % (rbw)) r.write('SENS1:BAND:VID %f Hz' % (vbw)) def dosweep(r, sweeps): swt = float(r.ask('SWE:TIME?')) tout = swt * 5 * sweeps if tout < 1: tout = 1 #print('Sweep time', swt) # Trigger the sweep r.write('INIT;*WAI') # Wait for it to be done opc = int(r.ask('*OPC?', timeout = tout)) assert(opc == 1) def setup(r, carrier, sweeps, rbw, vbw, ofs, atten): cpwrlim = -30 measurements = ( { 'offset' : 10, 'rbw' : 10, 'vbw' : 10 }, { 'offset' : 100, 'rbw' : 10, 'vbw' : 10 }, { 'offset' : 1e3, 'rbw' : 300, 'vbw' : 300 }, { 'offset' : 10e3, 'rbw' : 300, 'vbw' : 300 }, { 'offset' : 100e3, 'rbw' : 300, 'vbw' : 300 }, { 'offset' : 1e6, 'rbw' : 1e3, 'vbw' : 3e3 }, ) # Reset to defaults r.write('*RST') # Set to single sweep mode r.write('INIT:CONT OFF') # Enable display updates r.write('SYST:DISP:UPD ON') # Set attenuation r.write('INP:ATT %f' % atten) # # Look for carrier # print('Measuring carrier') # Set frequency range etc setup2(r, carrier, 1e3, 300, 300) # Switch marker 1 on in screen A r.write('CALC:MARK1 ON') # 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: raise Exception('Instrument warning, status %s power status %s' % (bin(status), bin(pwrstat))) # Look for the carrier r.write('CALC:MARK1:MAX') cpwr = float(r.ask('CALC:MARK1:Y?')) if cpwr < cpwrlim: raise Exception('Carrier power too low / not found: %.1f dBm vs %.1f dBm' % (cpwr, cpwrlim)) cmeas = float(r.ask('CALC:MARK1:X?')) print('Found carrier at %.7f MHz with power %.1f dBm' % (cmeas / 1e6, cpwr)) # Turn averaging on r.write('AVER:STAT ON') # Set number of sweeps r.write('SWE:COUN %d' % (sweeps)) # Enable phase noise measurement r.write('CALC:DELT1:FUNC:PNO') for idx, m in enumerate(measurements): # Setup measurement setup2(r, carrier, m['offset'] * 2.1, m['rbw'], m['vbw']) # Do the sweep dosweep(r, sweeps) # Set offset of phase noise measurement r.write('CALC:DELT2:FUNC:FIX:RPO:X %f Hz' % (m['offset'])) meas = float(r.ask('CALC:DELT:FUNC:PNO:RES?')) print('Offset %.0fHz %.2f dBc/Hz' % (m['offset'], meas)) return def getphnoise(r): # Trigger the sweep r.write('INIT;*WAI') # Wait for it to be done opc = int(r.ask('*OPC?', timeout = 1000)) #print 'OPC - %d' % (opc) assert(opc == 1) # Set data format r.write('FORM:DATA ASC') # Read phase noise value data = r.ask('CALC:MARK2:FUNC:NOIS:RES?') #print 'Data - ' + data return float(data) if __name__ == '__main__': parser = argparse.ArgumentParser(description = 'Configures a Rhode Schwartz FSP7 spectrum analyser to do a phase noise measurement') parser.add_option('-s', '--span', dest = 'span', default = 1e6, help = 'Span frequency in Hz (default: %default)', type = float) parser.add_option('-w', '--sweeps', dest = 'sweeps', default = 20, help = 'Number of sweeps (default: %default)', type = int) parser.add_option('-r', '--rbw', dest = 'rbw', default = 1000, help = 'Resolution bandwidth in Hz (default: %default)', type = float) parser.add_option('-v', '--vbw', dest = 'vbw', default = 3000, help = 'Video bandwidth in Hz (default: %default)', type = float) parser.add_option('address', '--address', dest = 'address', help = 'Address of analyser', type = str) parser.add_option('centre', '--centre', dest = 'centreq', help = 'Centre frequency of measurement', type = float) parser.add_option('offset', '--offset', dest = 'offset', help = 'Offset frequency to take measurement at', type = float) (options, args) = parser.parse_args() # Connect to the analyser r = rsib.RSIBDevice(addr) # ID instrument print('ID is', r.ask('*IDN?').decode('ascii')) # Setup parameters setup(r, options.centre, options.span, options.sweeps, options.rbw, options.vbw, options.offset) while True: print('Centre: %.1f Mhz, Span %.1f Mhz, RBW %.1f kHz, %d sweeps, Offset %.1fkHz: %.2f dBc/Hz' % ( options.centre / 1e6, options.span / 1e6, options.rbw / 1e3, options.sweeps, options.offset / 1e3, getphnoise(r)))