# HG changeset patch # User Daniel O'Connor # Date 1727264401 -34200 # Node ID b6ebe05f250f8699b84e7781c755724ae2dbeb87 # Parent ca5a822c550ad63546690b89db2066060687eaf8 Add some commentry about what it works with diff -r ca5a822c550a -r b6ebe05f250f fsp7_phasenoise.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fsp7_phasenoise.py Wed Sep 25 21:10:01 2024 +0930 @@ -0,0 +1,170 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 +# Daniel O'Connor . 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))) diff -r ca5a822c550a -r b6ebe05f250f rs_fsp7_logmarker.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rs_fsp7_logmarker.py Wed Sep 25 21:10:01 2024 +0930 @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2023 +# Daniel O'Connor . 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 rsib +import time + +if __name__ == '__main__': + r = rsib.RSIBDevice('analyzer') + + # ID instrument + r.write('*IDN?') + print("ID is " + r.read(5).decode('ascii')) + + while True: + r.write('CALC1:MARK1:Y?') + print(r.read(5).decode('ascii')) + time.sleep(1) + diff -r ca5a822c550a -r b6ebe05f250f rsib.py --- a/rsib.py Wed Sep 25 20:57:26 2024 +0930 +++ b/rsib.py Wed Sep 25 21:10:01 2024 +0930 @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2009 +# Copyright (c) 2024 # Daniel O'Connor . All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -25,6 +25,11 @@ # SUCH DAMAGE. # +# Helper functions to talk to Rohde & Schwarz test equipment over the +# RSIB protocol. +# +# This is for older test equipment (tested on an FSP7 Windows NT 3.51..) +# # Reverse engineered from the Linux library & example program at # http://epsrv.astro.umk.pl/~ep/irbene/rsib/library/RSIB-Linux.zip # diff -r ca5a822c550a -r b6ebe05f250f scpisock.py --- a/scpisock.py Wed Sep 25 20:57:26 2024 +0930 +++ b/scpisock.py Wed Sep 25 21:10:01 2024 +0930 @@ -36,9 +36,7 @@ SCPI_PORT = 5025 class SCPISockDevice(object): - def __init__(self, host, port = None): - if port == None: - port = SCPI_PORT + def __init__(self, host, port = SCPI_PORT): self.sock = socket.create_connection((host, port)) def flush(self): @@ -50,23 +48,23 @@ def write(self, data): trail = '' - if data[-1] != '\n': - trail = '\n' - + if data[-1] != b'\n': + trail = b'\n' + self.sock.send(data + trail) def read(self, timeout = None): - res = '' + res = b'' if timeout == None: timeout = 0.1 - + while True: r, w, x = select.select([self.sock], [], [], timeout) if len(r) == 0: break res = res + self.sock.recv(1024) - if res[-1] == '\n': + if res[-1] == b'\n': break - return res.rstrip('\n') - + return res.rstrip(b'\n') +