view rs_fsp7_noisetest.py @ 79:84f96c5fe791

Use different message ID that does not result in "Query UNTERMINATE" messages in the error log. Fix testsrq. Rename queryrsb to querystb as that matches the operating manual.
author Daniel O'Connor <doconnor@gsoft.com.au>
date Fri, 27 Sep 2024 16:53:43 +0930
parents 23c96322cfb6
children 4b4ae555067b
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 Rohde Schwarz 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)))