comparison fsp7_phasenoise.py @ 74:b6ebe05f250f default tip

Add some commentry about what it works with
author Daniel O'Connor <doconnor@gsoft.com.au>
date Wed, 25 Sep 2024 21:10:01 +0930
parents
children
comparison
equal deleted inserted replaced
73:ca5a822c550a 74:b6ebe05f250f
1 #!/usr/bin/env python
2
3 # Copyright (c) 2014
4 # Daniel O'Connor <darius@dons.net.au>. All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
14 #
15 # THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 # ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 # SUCH DAMAGE.
26 #
27
28 import argparse
29 import math
30 import numpy
31 import rsib
32 import scipy
33 import scpi
34 import sys
35
36 def setup2(r, centre, span, rbw, vbw):
37 r.write('SENSE1:FREQ:CENT %f Hz' % (centre))
38 r.write('SENSE1:FREQ:SPAN %f Hz' % (span))
39 r.write('SENS1:BAND:RES %f Hz' % (rbw))
40 r.write('SENS1:BAND:VID %f Hz' % (vbw))
41
42 def dosweep(r, sweeps):
43 swt = float(r.ask('SWE:TIME?'))
44 tout = swt * 5 * sweeps
45 if tout < 1:
46 tout = 1
47 #print('Sweep time', swt)
48
49 # Trigger the sweep
50 r.write('INIT;*WAI')
51
52 # Wait for it to be done
53 opc = int(r.ask('*OPC?', timeout = tout))
54 assert(opc == 1)
55
56
57 def setup(r, carrier, sweeps, rbw, vbw, ofs, atten):
58 cpwrlim = -30
59 measurements = (
60 { 'offset' : 10, 'rbw' : 10, 'vbw' : 10 },
61 { 'offset' : 100, 'rbw' : 10, 'vbw' : 10 },
62 { 'offset' : 1e3, 'rbw' : 300, 'vbw' : 300 },
63 { 'offset' : 10e3, 'rbw' : 300, 'vbw' : 300 },
64 { 'offset' : 100e3, 'rbw' : 300, 'vbw' : 300 },
65 { 'offset' : 1e6, 'rbw' : 1e3, 'vbw' : 3e3 },
66 )
67 # Reset to defaults
68 r.write('*RST')
69
70 # Set to single sweep mode
71 r.write('INIT:CONT OFF')
72
73 # Enable display updates
74 r.write('SYST:DISP:UPD ON')
75
76 # Set attenuation
77 r.write('INP:ATT %f' % atten)
78
79 #
80 # Look for carrier
81 #
82 print('Measuring carrier')
83 # Set frequency range etc
84 setup2(r, carrier, 1e3, 300, 300)
85
86 # Switch marker 1 on in screen A
87 r.write('CALC:MARK1 ON')
88
89 # Do the sweep
90 dosweep(r, 1)
91
92 # Check the instrument is happy
93 status = int(r.ask('STAT:QUES:COND?'))
94 pwrstat = int(r.ask('STAT:QUES:POW:COND?'))
95 if status != 0 or pwrstat != 0:
96 raise Exception('Instrument warning, status %s power status %s' %
97 (bin(status), bin(pwrstat)))
98 # Look for the carrier
99 r.write('CALC:MARK1:MAX')
100 cpwr = float(r.ask('CALC:MARK1:Y?'))
101 if cpwr < cpwrlim:
102 raise Exception('Carrier power too low / not found: %.1f dBm vs %.1f dBm' % (cpwr, cpwrlim))
103 cmeas = float(r.ask('CALC:MARK1:X?'))
104 print('Found carrier at %.7f MHz with power %.1f dBm' % (cmeas / 1e6, cpwr))
105
106 # Turn averaging on
107 r.write('AVER:STAT ON')
108
109 # Set number of sweeps
110 r.write('SWE:COUN %d' % (sweeps))
111
112 # Enable phase noise measurement
113 r.write('CALC:DELT1:FUNC:PNO')
114
115 for idx, m in enumerate(measurements):
116 # Setup measurement
117 setup2(r, carrier, m['offset'] * 2.1, m['rbw'], m['vbw'])
118
119 # Do the sweep
120 dosweep(r, sweeps)
121
122 # Set offset of phase noise measurement
123 r.write('CALC:DELT2:FUNC:FIX:RPO:X %f Hz' % (m['offset']))
124 meas = float(r.ask('CALC:DELT:FUNC:PNO:RES?'))
125 print('Offset %.0fHz %.2f dBc/Hz' % (m['offset'], meas))
126 return
127
128 def getphnoise(r):
129 # Trigger the sweep
130 r.write('INIT;*WAI')
131
132 # Wait for it to be done
133 opc = int(r.ask('*OPC?', timeout = 1000))
134 #print 'OPC - %d' % (opc)
135 assert(opc == 1)
136
137 # Set data format
138 r.write('FORM:DATA ASC')
139
140 # Read phase noise value
141 data = r.ask('CALC:MARK2:FUNC:NOIS:RES?')
142 #print 'Data - ' + data
143
144 return float(data)
145
146 if __name__ == '__main__':
147 parser = argparse.ArgumentParser(description = 'Configures a Rhode Schwartz FSP7 spectrum analyser to do a phase noise measurement')
148 parser.add_option('-s', '--span', dest = 'span', default = 1e6, help = 'Span frequency in Hz (default: %default)', type = float)
149 parser.add_option('-w', '--sweeps', dest = 'sweeps', default = 20, help = 'Number of sweeps (default: %default)', type = int)
150 parser.add_option('-r', '--rbw', dest = 'rbw', default = 1000, help = 'Resolution bandwidth in Hz (default: %default)', type = float)
151 parser.add_option('-v', '--vbw', dest = 'vbw', default = 3000, help = 'Video bandwidth in Hz (default: %default)', type = float)
152 parser.add_option('address', '--address', dest = 'address', help = 'Address of analyser', type = str)
153 parser.add_option('centre', '--centre', dest = 'centreq', help = 'Centre frequency of measurement', type = float)
154 parser.add_option('offset', '--offset', dest = 'offset', help = 'Offset frequency to take measurement at', type = float)
155
156 (options, args) = parser.parse_args()
157
158 # Connect to the analyser
159 r = rsib.RSIBDevice(addr)
160
161 # ID instrument
162 print('ID is', r.ask('*IDN?').decode('ascii'))
163
164 # Setup parameters
165 setup(r, options.centre, options.span, options.sweeps, options.rbw, options.vbw, options.offset)
166
167 while True:
168 print('Centre: %.1f Mhz, Span %.1f Mhz, RBW %.1f kHz, %d sweeps, Offset %.1fkHz: %.2f dBc/Hz' % (
169 options.centre / 1e6, options.span / 1e6, options.rbw / 1e3, options.sweeps,
170 options.offset / 1e3, getphnoise(r)))