view rs_fsp7_noisetest.py @ 74:b6ebe05f250f

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 91b476ebc0f2
children 23c96322cfb6
line wrap: on
line source

#!/usr/bin/env python

# Copyright (c) 2012
#      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 math
import numpy
import optparse
import rsib
import scipy
import scpi
import sys

def findenr(frq):
    # ENR values from the noise source
    enrdb = numpy.array([15.55, 15.96, 15.68, 15.11, 15.07, 14.84, 14.77, 14.82, 14.86, 14.79, 14.83, 14.93, 14.93, 15.07, 15.19, 15.08, 15.14, 14.87, 14.97, 14.59])
    enrfrq = numpy.array([0.01e9, 0.1e9, 1.0e9, 2.0e9, 3.0e9, 4.0e9, 5.0e9, 6.0e9, 7.0e9, 8.0e9, 9.0e9, 10.0e9, 11.0e9, 12.0e9, 13.0e9, 14.0e9, 15.0e9, 16.0e9, 17.0e9, 18.0e9])

    # Convert back to linear values
    enr = 10 ** (enrdb / 10)

    # Interpolate
    rtn = scipy.interp([frq], enrfrq, enr)

    # Convert to dB
    rtndb = 10 * math.log10(rtn)

    return rtndb
    
def setup(r, freq, span, sweeps, bw):
    # 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 frequency range
    r.write("SENSE1:FREQ:CENT %f Hz" % (freq))
    r.write("SENSE1:FREQ:SPAN %f Hz" % (span))

    # Switch marker 1 on in screen A
    r.write("CALC:MARK1 ON")

    # Enable noise measurement
    r.write("CALC:MARK1:FUNC:NOIS ON")

    # Turn averaging on
    r.write("AVER:STAT ON")

    # Set number of sweeps
    r.write("SWE:COUN %d" % (sweeps))

    # Set resolution bandwidth
    r.write("SENS1:BAND:RES %f Hz" % (bw))

    # Set video bandwidth (10x res BW)
    r.write("SENS1:BAND:VID %f Hz" % (bw * 10))
    
def getnoise(r):
    # Trigger the sweep
    r.write("INIT;*WAI")

    # Wait for it to be done
    r.write("*OPC?")
    opc = scpi.getdata(r.read(None), int)
    #print "OPC - %d" % (opc)
    assert(opc == 1)
    
    # Set data format
    r.write("FORM:DATA ASC")

    # Read noise value
    r.write("CALC:MARK1:FUNC:NOIS:RES?")
    data = r.read(10)
    #print "Data - " + data

    return float(data)

def setnoise(r, en):
    if en:
        val = "ON"
    else:
        val = "OFF"
    r.write("DIAG:SERV:NSO " + val)

def calcnf(enrdb, offdb, ondb):
    # Not possible but noisy results may result in it happening
    if ondb <= offdb:
        return 0
    ydb = ondb - offdb
    y = 10 ** (ydb / 10)
    enr = 10 ** (enrdb / 10)
    nf = 10 * math.log10(enr / (y - 1))
    return nf
    
def donoisetest(r, enr):
    print("Acquiring with noise off..")
    setnoise(r, False)
    off = getnoise(r)
    print("Acquiring with noise on..")
    setnoise(r, True)
    on = getnoise(r)
    return off, on, calcnf(enr, off, on)

if __name__ == '__main__':
    parser = optparse.OptionParser(usage = '%prog [options] address frequency', 
                                   description = 'Configures a Rhode Schwartz FSP7 spectrum analyser to do a noise figure test',
                                   epilog = 'video bandwidth is set to 10 times the resolution bandwidth')
    parser.add_option('-s', '--span', dest = 'span', default = 1e6, help = 'Span frequency in Hz (default: %default)', type = float)
    parser.add_option('-i', '--input', dest = 'input', default = None, help = 'Frequency used to compute ENR (defaults to frequency)', type = float)
    parser.add_option('-w', '--sweeps', dest = 'sweeps', default = 20, help = 'Number of sweeps (default: %default)', type = int)
    parser.add_option('-b', '--bw', dest = 'bw', default = 1000, help = 'Resolution bandwidth in Hz (default: %default)', type = float)
    parser.add_option('-r', '--repeat', dest = 'repeat', help = 'Number of repetitions, if not specified do one and ask to continue', type = int)

    (options, args) = parser.parse_args()

    if len(args) != 2:
        parser.error('Must supply the specan address and centre frequency')

    addr = args[0]
    try:
        freq = float(args[1])
    except ValueError:
        parser.error('Unable to parse frequency')

    if options.input == None:
        options.input = freq

    # Compute ENR at frequency of interest
    enr = findenr(options.input)

    # Connect to the analyser
    r = rsib.RSIBDevice(addr)

    # ID instrument
    r.write('*IDN?')
    print("ID is " + r.read(5))

    # Setup parameters
    setup(r, freq, options.span, options.sweeps, options.bw)

    r.write("INIT:CONT OFF")

    nfs = []
    print("Centre: %.1f Mhz, Span %.1f Mhz, Input %.1f MHz, BW %.1f kHz, %d sweeps, ENR %.2f dB" % (freq / 1e6, options.span / 1e6, options.input / 1e6, options.bw / 1e3, options.sweeps, enr))
    while options.repeat == None or options.repeat > 0:
        off, on, nf = donoisetest(r, enr)
        print("Off %.3f dBm/Hz, on %.3f dBm/Hz, NF %.2f dB" % (off, on, nf))
        nfs.append(nf)
        if options.repeat == None:
            print("Press enter to perform a new measurement")
            sys.stdin.readline()
        else:
            options.repeat -= 1

    if len(nfs) > 1:
        nfs = numpy.array(nfs)
        print("NF min: %.1f dBm/Hz, max: %.1f dBm/Hz, avg: %.1f dBm/hz, stddev: %.1f" % (
            nfs.min(), nfs.max(), nfs.sum() / len(nfs), numpy.std(nfs)))