Open RF Prototyping

measure-accuracy.py

Initially, the script processes the command line and then invokes the main function passing the specified command arguments or their defaults.

<<imports>>

<<globals>>

<<plot>>

<<main>>

if __name__ == '__main__':

    defaultDevice = '/dev/tty.usbmodem14101'
    defaultBaud = '1500000'
    defaultOutfile = 'pwrdetector-accuracy.svg'

    parser = ArgumentParser(description=
      '''Measure power detector module accuracy.''')

    parser.add_argument("-d", "--device",
                      default=defaultDevice,
                      help="The serial device (default: {})".format(defaultDevice))
    parser.add_argument("-C", "--cspin", default='D0',
                        help="Chip select controller pin (default: {})".format('D0'))
    parser.add_argument("-G", "--gen", choices=['DSG815', 'SMHU58'],
                        default='SMHU58',
                        help="""The signal generator in use for
                        the calibration. Default: {}""".format('SMHU58'))
    parser.add_argument("-O", "--outfile",
                        default=defaultOutfile,
                        help="""Output file for plot.  Default: {}""".format(
                            defaultOutfile))

    args = parser.parse_args()

    main(args.device, args.cspin, args.gen, args.outfile)

The main Function: <<main>>

The main function proceeds in four stages:

  1. Initialize the RF signal generator.

  2. Initialize the detector under test.

  3. Take the detector measurements.

  4. Plot the measured power levels.

def main(serdev, cspin, gen, outfile):
    measured_levels = {}

    <<initialize-detector>>

    <<initialize-siggen>>

    <<take-measurements>>

    sig_gen.level = -40.0
    sig_gen.reset()

    if gen == 'DSG815':
        visa_rm.close()
    else:
        gpib.close()

    plot(det, measured_levels, outfile)

Initialize the signal generator: <<initialize-siggen>>

The script currently supports either the R&S SMHU and the Rigol DSG815. Depending on the which one is being used either GPIB or VISA is used for instrument communication and command. Note that the frequencies list is set to the frequency range appropriate for the selected signal generator.

global frequencies

if gen == 'DSG815':
    visa_rm = pyvisa.ResourceManager('@py')
    sig_gen = DSG815(visa_rm, DSG815_ID)
    sig_gen.initialize()
    frequencies = dsg815_frequencies[det.detector_type]
elif gen == 'SMHU58':
    gpib = GPIB()
    gpib.initialize()
    sig_gen = SMHU58(gpib, SMHU58_GPIBID)
    sig_gen.initialize()
    frequencies = smhu_frequencies[det.detector_type]
else:
    print("ERROR: Unknown signal generator: {}".format(gen))
    sys.exit(-1)

Initialize the detector under test: <<initialize-detector>>

A serial communications link is first established with the atmega USB control board. An instance of the rfblocks LogDetector is then created and initialized. Note that as part of the initialization of the detector calibration information will be loaded from the detector's EEPROM.

try:
    ser = create_serial(serdev)
except OSError as error:
    print('No serial port ({})'.format(serdev))
    sys.exit(-1)

det = LogDetector(cspin)
write_cmd(ser, det.pin_config())
try:
    det.initialize(ser)
except InvalidCalibrationDataError:
    print("Detector has no valid calibration data.")

Take the measurements: <<take-measurements>>

For each of the input signal power levels specified in the pwr_levels list detector power levels are measured at each frequency specified in the frequencies list. The power levels reported by the detector under test are saved in measured_levels.

for pwr in pwr_levels[det.detector_type]:
    measured_levels[pwr] = []

    print('{:6.1f}: '.format(pwr), end='', flush=True)

    sig_gen.level = pwr
    sig_gen.output = True
    sleep(2.0)

    for freq in frequencies:
        sig_gen.freq = freq
        sleep(1.0)
        measured_pwr = det.power(ser, freq, avg=16)
        print(' {:5.2f}'.format(measured_pwr), end='', flush=True)
        measured_levels[pwr].append(measured_pwr)

    print()

The plot function: <<plot>>

The measured detector power levels are plotted as a function of input signal frequency. An example plot is shown in Figure 1.

def plot(detector, measured_levels, outfile):
    init_style()
    last_freq = frequencies[-1]
    fig = plt.figure(num=None, figsize=(6.0, 4.0), dpi=72)
    ax = fig.add_subplot(111)
    ax.set_xlabel('Frequency (MHz)')
    ax.set_ylabel('RF Input Power (dBm)')
    ax.set_xlim(0.0, last_freq*1.1)
    ax.grid(linestyle=':')
    ax.yaxis.set_minor_locator(plt.MultipleLocator(5.0))
    ax.grid(which='both', axis='y', linestyle=':')
    for pwr in pwr_levels[detector.detector_type]:
        ax.plot(frequencies, measured_levels[pwr])
        ax.text(last_freq*1.01, pwr-1.0, '{:4.1f}'.format(pwr))
    fig.tight_layout()
    plt.savefig(outfile)

Global variables: <<globals>>

Since the different signal generator models have differing frequency capabilities the measured frequency steps will depend on the selected signal generator. These steps are stored in the frequencies list for later use.

pwr_levels

the test signal power levels to use.

smhu_frequencies

the test signal frequencies to use for the R&S SMHU58 signal generator

dsg815_frequencies

the test signal frequencies to with the Rigol DSG815 signal generator

frequencies

the actual test signal frequencies to use during the measurement process. The default signal generator is the SMHU58 and so the frequencies variable is initialized with smhu_frequencies.

pwr_levels = [
    [-60.0, -55.0, -50.0, -45.0, -40.0, -35.0,
     -30.0, -25.0, -20.0, -15.0, -10.0, -5.0, 0.0, 5.0],
    [-70.0, -65.0, -60.0, -55.0, -50.0, -45.0, -40.0,
     -35.0, -30.0, -25.0, -20.0, -15.0, -10.0, -5.0, 0.0, 5.0],
]

smhu_frequencies = [
    [250.0, 500.0, 750.0, 1000, 1250, 1500, 1750, 2000, 2250,
     2500, 2750, 3000, 3250, 3500, 3750, 4000, 4250],
    [5.0, 50.0, 100.0, 150.0, 200.0, 250.0, 300.0, 350.0,
     400.0, 425.0, 450.0, 475.0, 500.0, 525.0, 550.0, 575.0, 600.0]
]

dsg815_frequencies = [
    [250.0, 500.0, 750.0, 1000, 1250, 1500],
    [5.0, 50.0, 100.0, 150.0, 200.0, 250.0, 300.0, 350.0,
     400.0, 425.0, 450.0, 475.0, 500.0, 525.0, 550.0, 575.0, 600.0]
]

frequencies = smhu_frequencies

Imports: <<imports>>

import sys
from time import sleep
from argparse import ArgumentParser
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('Agg')
import pyvisa
from tam import (GPIB, SMHU58, SMHU58_GPIBID, DSG815, DSG815_ID)
from rfblocks import LogDetector, create_serial, write_cmd, query_cmd
from dyadic.splot import init_style
import dyadic.orgmode as org