Introduction
I use Spectrum Lab to do automated testing of my TCXO boards as described here.
I use the Conditional Actions scripting to collect data on Frequency Stability and Phase Noise and then create graphs and reports from the data.
Frequency Stability
I use this script below to collect data. From this data, Allan Deviation, a standard measure of frequency stability, is calculated. An explanation of Allan Deviation is beyond the scope of this site. Search for "Allan Deviation" on the web for descriptions. Be prepared to see a lot of math!
I use two programs to calculate Allan Deviation. Each has it's strengths.
Alavar - Link is no longer valid.
Timelab - Found here.
Results of this testing may be found here.
Allan Deviation Data Script
; Exported "Conditional Actions" for Spectrum Lab
if( initializing ) then ScanStep=0:TestSerNum="Test":NominalFreq="15600000":ScanTime="1800":NumScans="1"
if( continuation ) then edit(TestSerNum,"Unit Under Test", "Enter the serial number of the", "unit under test")
if( continuation ) then edit(NominalFreq,"Nominal Frequency", "Enter the nominal", "frequency")
if( continuation ) then edit(ScanTime,"Scan Time", "Enter the scan", "time")
if( continuation ) then edit(NumScans,"Number Of Scans", "Enter the number of scans", "1=Fast Only, 2 = Fast and Slow")
if( continuation ) then sdr.freq = val(NominalFreq):exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):cfg.FFTSize=32768:cfg.Wat1ScrollTime=0.6816
if( continuation ) then cfg.FFTInputDec=1:cfg.SpecFreqMin=-5:cfg.SpecFreqMax=5:InputSel=-1:AorB="B":exec("\"D:\\Ham Radio\\Teraterm_"+AorB+".exe\"")
if( continuation ) then fopen0("D:\\Ham Radio\\Initializing.txt"):fopen1("D:\\Ham Radio\\Initializing.dat"):fp0("Init"):fp1("Init"):fclose0:fclose1
if( continuation ) then fopen0("D:\\Ham Radio\\AllanText_0.6816_SN_" + TestSerNum + ".txt"):fopen1("D:\\Ham Radio\\AllanData_0.6816_SN_" + TestSerNum + ".txt")
if( continuation ) then fp0("UUT Serial Number: " + TestSerNum +" Test Version: 1"):fp1("#UUT Serial Number: " + TestSerNum +" Test Version: 1")
if( continuation ) then fp1("#Sample Time: 0.6816 Sec"):fp1("#Frequency"):fp1("#(Hz)"):ScanStep=1:timer1.start(30)
if( timer1.expired(1) && ScanStep=1 ) then ScanStep=2:timer2.start(val(ScanTime)):
if( new_spectrum && ScanStep=2 ) then Peak_Freq= sdr.freq + (peak_f((sdr.freq - 5), (sdr.freq + 5)))
if( continuation ) then fp0("Date = "+str("MM-DD-YYYY",now), "Time = "+str("hh:mm:ss",now)+ " Peak Frequency " + AorB + " = " + Peak_Freq):fp1(Peak_Freq)
if( timer2.expired(1) && ScanStep=2 ) then capture("D:\\Ham Radio\\AllanPic_0.6816_SN_" + TestSerNum + ".jpg"):fclose0:fclose1:ScanStep=3
if( ScanStep=3 && NumScans="2" ) then cfg.FFTSize=131072:cfg.Wat1ScrollTime=2.726
if( continuation ) then cfg.FFTInputDec=1:cfg.SpecFreqMin=-5:cfg.SpecFreqMax=5:InputSel=-1:AorB="B":exec("\"D:\\Ham Radio\\Teraterm_"+AorB+".exe\"")
if( continuation ) then fopen0("D:\\Ham Radio\\AllanText_2.726_SN_" + TestSerNum + ".txt"):fopen1("D:\\Ham Radio\\AllanData_2.726_SN_" + TestSerNum + ".txt")
if( continuation ) then fp0("UUT Serial Number: " + TestSerNum +" Test Version: 1"):fp1("#UUT Serial Number: " + TestSerNum +" Test Version: 1")
if( continuation ) then fp1("#Sample Time: 2.726 Sec"):fp1("#Frequency"):fp1("#(Hz)"):ScanStep=4:timer3.start(30)
if( timer3.expired(1) && ScanStep=4 ) then ScanStep=5:timer4.start(val(ScanTime))
if( new_spectrum && ScanStep=5 ) then Peak_Freq= sdr.freq + (peak_f((sdr.freq - 5), (sdr.freq + 5)))
if( continuation ) then fp0("Date = "+str("MM-DD-YYYY",now), "Time = "+str("hh:mm:ss",now)+ " Peak Frequency " + AorB + " = " + Peak_Freq):fp1(Peak_Freq)
if( timer4.expired(1) && ScanStep=5 ) then capture("D:\\Ham Radio\\AllanPic_2.726_" + TestSerNum + ".jpg"):fclose0:fclose1:ScanStep=6
Explanation Of The Allan Deviation Data Script
Please review the conditional action scripting in Spectrum Lab. I won't attempt to describe all the details of how the scripting works here. I use many of the functions in the scripting language to implement the data collection, triggered by a combination of timers and when a new FFT is calculated.
Upon Initialization, the script first sets a bunch of variables to initial values. ScanStep is a variable used for flow control, as the scripting language does not really have flow control structures.
Next, the edit function is called multiple times to get input from the user.
Then, the measurement parameters are set up. ResetPerseus.exe is a program that selects buttons on the Perseus Extio.dll control panel. I set it up to use no attenuation, no dither, and turn on the preamp. Then it selects a 96 kilosamples/second sampling rate, waits a few seconds, then selects a 48 kilosamples/second sampling rate. This seems to allow the Perseus to correctly pick up all of the parameters, including the correct center frequency as entered by the user. ResetPerseus.exe is described here.
Then, FFT parameters are set up to measure every 0.6816 seconds. After that, TeratermB.exe is run to switch an external RF switch to the "B" input. The RF switch is described here.
After that, two files are created and closed. This seems to be necessary to correctly track two open files in the Spectrum Lab Interpreter.
After that, the actual data files are created with names dependent on the user entries. Headers are written to the files and timer1 is started for 30 seconds to allow everything to settle before taking data.
After timer1 expires, timer2 is started for the time the data is going to be collected. The default is 1800 seconds, or 1/2 an hour, but it can be whatever the user selected.
Then, each time a new FFT is calculated by Spectrum Lab, it calculates the peak frequency and writes it to the files. The file with "data" in it's name is formatted to be input directly to the two programs to calculate Allan Deviation. The other file is more human readable.
When timer2 expires, it saves a jpg image of the waterfall and closes the data files.
If the user selected to save two sets of data, the process is repeated, measuring every 2.726 seconds. This will use narrower FFT bins and probably is not often needed, but may be better for certain signals. Timer3 and Timer4 are used and the data is saved in different files.
Phase Noise
I use this script below to collect phase noise data. From this data, I use a custom spreadsheet to graph the phase noise vs offset frequency from the carrier.
Results of this testing may be found here.
Phase Noise Measurement Script
; Exported "Conditional Actions" for Spectrum Lab
if( initializing ) then exec("\"D:\\Ham Radio\\Teraterm_B.exe\""):ScanStep=0:CalSernum="":Nominal_Freq = 15600000:Nominal_Freq_Str = "15600000":Sernum="":LevelCal="-6"
if( continuation ) then edit(Nominal_Freq_Str,"Nominal Frequency", "Enter the nominal frequency of the", "sources in Hz (> 1500000 , < 28500000)")
if( continuation ) then Nominal_Freq = min(28500000 , max(1500000,val(Nominal_Freq_Str))):edit(CalSernum,"Calibrated Source", "Enter the serial number of the", "calibrated source")
if( continuation ) then edit(Sernum,"UUT", "Enter the serial number" , "of the UUT")
if( continuation ) then edit(LevelCal,"Level Calibration","Enter the level calibration","constant for the setup in dB.")
if( continuation ) then cfg.FFTSize=524288:cfg.FFTInputDec=1:cfg.Wat1ScrollTime=10.91:cfg.FFTAverage=1:cfg.FFTWindowFunc=2:cfg.SpecFreqMin=-22000:cfg.SpecFreqMax=22000
if( continuation ) then sdr.freq = Nominal_Freq:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\"")
if( continuation ) then fopen0("D:\\Ham Radio\\Initializing.txt"):fopen1("D:\\Ham Radio\\Initializing.dat"):fp0("Init"):fp1("Init"):fclose0:fclose1:timer3.start(40)
if( timer3.expired(1) ) then Cal_Freq= Nominal_Freq + (peak_f((Nominal_Freq - 500), (Nominal_Freq + 500))):fopen0("D:\\Ham Radio\\Sernum" + Sernum + ".txt")
if( continuation ) then fopen1("D:\\Ham Radio\\Sernum" + Sernum + ".dat"):fp0("UUT Serial Number: " + Sernum + " Cal Serial Number: " + CalSernum + " Version: 16")
if( continuation ) then fp1("SN: , " + Sernum + " , CN: , " + CalSernum + " , VN: , 16"):timer0.start(15)
if( timer0.expired(1) ) then sdr.freq = Nominal_Freq:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):timer1.start(40)
if( timer1.expired(1) ) then ScanStep=10
if( new_spectrum && ScanStep=10 ) then ScanStep=1:fp0("Nominal VFO Frequency = " , Nominal_Freq , " Hz"):fp0("Nominal Center Frequency = " , Nominal_Freq, " Hz")
if( continuation ) then Peak_Freq= Nominal_Freq + (peak_f((Nominal_Freq - 500), (Nominal_Freq + 500))):Peak_Amplitude = (peak_a((Nominal_Freq - 500), (Nominal_Freq + 500)))
if( continuation ) then fp0("Measured Peak Amplitude = ", (Peak_Amplitude + val(LevelCal)) , " dB at " , Peak_Freq ," Hz"):fp0("Measured Cal Frequency = ", Cal_Freq ," Hz")
if( continuation ) then fp1("PA: , ", (Peak_Amplitude + val(LevelCal)) , ", PF: ," , Peak_Freq, ", CF: ," , Cal_Freq)
if( continuation ) then Offset = 10:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -10:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 20:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -20:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 50:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -50:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 100:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -100:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 200:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -200:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 500:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -500:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 1000:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -1000:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 2000:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -2000:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 5000:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -5000:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 10000:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -10000:Abs_Offset = (Offset > 0)?Offset:-1*Offset:Measure_Span = min(Abs_Offset*.1, 10):fp0("Offset = " , Offset , " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then ScanStep=2:Offset = 20000:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):timer2.start(40)
if( timer2.expired && ScanStep=2 && new_spectrum ) then Measure_Span = 10:fp0("Offset = " , Offset , " Hz, Measure Span = ", Measure_Span, " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz"
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -20000:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):ScanStep=3:timer2.start(40)
if( timer2.expired && ScanStep=3 && new_spectrum ) then Measure_Span = 10:fp0("Offset = " , Offset , " Hz, Measure Span = ", Measure_Span, " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz"
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 50000:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):ScanStep=4:timer2.start(40)
if( timer2.expired && ScanStep=4 && new_spectrum ) then Measure_Span = 10:fp0("Offset = " , Offset , " Hz, Measure Span = ", Measure_Span, " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz"
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -50000:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):ScanStep=5:timer2.start(40)
if( timer2.expired && ScanStep=5 && new_spectrum ) then Measure_Span = 10:fp0("Offset = " , Offset , " Hz, Measure Span = ", Measure_Span, " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz"
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 100000:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):ScanStep=6:timer2.start(40)
if( timer2.expired && ScanStep=6 && new_spectrum ) then Measure_Span = 10:fp0("Offset = " , Offset , " Hz, Measure Span = ", Measure_Span, " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz"
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -100000:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):ScanStep=7:timer2.start(40)
if( timer2.expired && ScanStep=7 && new_spectrum ) then Measure_Span = 10:fp0("Offset = " , Offset , " Hz, Measure Span = ", Measure_Span, " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz"
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = 1000000:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):ScanStep=8:timer2.start(40)
if( timer2.expired && ScanStep=8 && new_spectrum ) then Measure_Span = 10:fp0("Offset = " , Offset , " Hz, Measure Span = ", Measure_Span, " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz"
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then Offset = -1000000:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\""):ScanStep=9:timer2.start(40)
if( timer2.expired && ScanStep=9 && new_spectrum ) then ScanStep=99:Measure_Span = 10:fp0("Offset = " , Offset , " Hz, Measure Span = ", Measure_Span, " Hz")
if( continuation ) then Noise_Measured = noise_n((Peak_Freq + Offset - Measure_Span), (Peak_Freq + Offset + Measure_Span))
if( continuation ) then fp0("Measured Noise = ", Noise_Measured , " dBc/Hz at Offset " , Offset , " Hz")
if( continuation ) then fp0("Phase Noise = ", Noise_Measured - Peak_Amplitude , " dBc/Hz at Offset " , Offset , " Hz"
if( continuation ) then fp1("PN: , ", Noise_Measured - Peak_Amplitude , ", OFS: , " , Offset)
if( continuation ) then fp0("Date = "+str("MM-DD-YYYY",now), "Time = "+str("hh:mm:ss",now)):fp1("DT: , "+str("MM-DD-YYYY",now) + ", TM: , "+str("hh:mm:ss",now)):fclose0:fclose1
if( continuation ) then Offset = 0:sdr.freq = Nominal_Freq+Offset:Offset_Freq = 0:exec("\"D:\\Ham Radio\\ResetPerseus.exe\"")
Explanation Of The Phase Noise Measurement Script
I use many of the functions in the scripting language to implement the data collection, triggered by a combination of timers. I allow things to settle between configuration changes before taking the next measurement. I also wait for the next FFT to be calculated before taking readings.
Upon Initialization, the script first runs TeratermB.exe is run to switch an external RF switch to the "B" input and sets a bunch of variables to initial values. ScanStep is a variable used for flow control, as the scripting language does not really have flow control structures.
Next, the edit function is called multiple times to get input from the user. Since the phase noise is measured at 1 MHz above and below the test frequency, and there is a dropoff in response at the edges, the test center frequency (nominal frequency) is limited to between 1.5 and 28.5 MHz to hopefully get accurate data.
Then, the measurement parameters are set up. ResetPerseus.exe is a program that selects buttons on the Perseus Extio.dll control panel. I set it up to use no attenuation, no dither, and turn on the preamp. Then it selects a 96 kilosamples/second sampling rate, waits a few seconds, then selects a 48 kilosamples/second sampling rate. This seems to allow the Perseus to correctly pick up all of the parameters, including the correct center frequency as entered by the user.
FFT parameters are set up to measure every 10.91 seconds over a frequency range of +/- 22 kHz from the center frequency.
After that, two files are created and closed. This seems to be necessary to correctly track two open files in the Spectrum Lab Interpreter. Timer3 is started to let everything settle down.
After that, the actual data files are created with names dependent on the user entries. The true peak frequency is measured and saved. Headers are written to the files and timer0 is started for 15 seconds to allow everything to settle before taking data.
After timer0 expires, the center frequency is set to the measured center frequency, Reset:Perseus.exe is run again and timer1 is started for 40 seconds to allow things to settle down.
After timer1 expires, it waits for the next FFT to be calculated, then the peak frequency is measured again and stored. Nominally it will be about the same as the previous measurement. If the rest of the data is suspect, the two can be compared to see if the center frequency changed between the two readings.
Then, the peak amplitude is measured and saved. A calibration constant is provided in case there is gain or loss before the signal is measured by the Perseus. The constant is used for reporting only. For the Phase Noise measurement, each measurement is relative to the peak amplitude, so the constant is not used.
After that a series of measurements are made at between +/- 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, and 10000 Hz. These may be done without changing any configuration. The spectrum lab function noise_n is used as it normalizes to a 1 Hz bandwidth, which it the definition of phase noise. It also measures the average noise, mostly ignoring spurs. The noise is measured in a variable bandwidth, depending on how far from the center frequency it is. Timers are used to control the process but all measurements are made after the next FFT is calculated after the timer expires. The difference between the peak amplitude and the noise is saved.
After that, measurements are made at +/- 20000, 50000, 100000 and 1000000 MHz from the center frequency. It then closes the data files.
Copyright Note:
All content Copyright (C) 2017 by Mark Goldberg.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Here are some of my other sites:
Modifications to my RV, including solar power and extra storage: https://sites.google.com/site/marksrvmods/
My Controleo2 based SMT Reflow Oven: https://sites.google.com/site/markscontroleo2build/
My TS-590S MODs including a buffer board install for a panadapter: https://sites.google.com/site/marksts590smods/
My TCXO Boards to replace the SO-3 in Kenwood TS-590 radios: https://sites.google.com/site/markstcxo/
An explanation of various TCXO Characteristics in Kenwood TS-590 Radios: https://sites.google.com/site/markstcxomeasurements/
Modifications to allow use of an external clock in a Perseus SDR: https://sites.google.com/site/perseusmods/
Pictures I took of the 2017 Total Solar Eclipse from Menan Butte, Idaho: https://sites.google.com/site/marks2017eclipsephotos/
Revised December 15, 2017