vna (skrf.vi.vna)

Provides classes to interact with Vector Network Analyzers (VNAs) from numerous manufacturers.

VNA Classes

hp.HP8510C(address[, backend])

HP 8510 driver that is capable of compound sweeps, segmented sweeps, and fast binary transfers.

keysight.FieldFox(address[, backend])

Keysight FieldFox.

keysight.PNA(address[, backend])

Keysight PNA, PNA-L, PNA-X.

nanovna.NanoVNA(address[, backend])

NanoVNA.

The Base Class and Writing an Instrument Driver

All VNAs are derived from the VNA class. This class should never be instantiated directly, but instead serves as a means to run certain setup code depending on the capabilities of the instrument.

When writing a new instrument driver, the following minimum questions should be considered:

  • Does the instrument use SCPI?

  • Does the instrument support multiple channels?

SCPI

For those instruments that use SCPI, default universal SCPI methods are included by default. However, for those instruments that do not support SPCI, do the following:

class VNAWithoutSCPI(VNA):
    _scpi = False
    # ...

Channels

Some instruments support channels and can have multiple measurements with different frequency/power/if bandwidth/etc settings.

For instruments without channel support, the typical properties (freq_start, freq_stop, etc) should be defined on the class. For instruments with channel support, the class should include a subclass of Channel that defines properties specific to channel settings in the channel definition while other instrument properties should be defined in the instrument class. To make this more clear, consider the following example from the Keysight PNA implementation (the property creator VNA.Command is explained later)

class PNA(VNA):
    class Channel(vna.Channel):
        freq_start = VNA.Command(
            get_cmd="SENS<self:cnum>:FREQ:STAR?",
            set_cmd="SENS<self:cnum>:FREQ:STAR <arg>",
            doc="""Start frequency [Hz]""",
            validator=FreqValidator()
        )

    def __init__(self, address, backend='@py'):
        # ...

    nports = VNA.Command(
        get_cmd="SYST:CAP:HARD:PORT:COUN?",
        set_cmd=None,
        doc="""Number of ports""",
        validator=IntValidator()
    )

Here, the start frequency is set per channel whereas the number of ports is related to the instrument itself. Instruments with channel support should create a single channel in __init__() using create_channel

Property Creator

Inspired by PyMeasure, skrf has a property creator to simplify creating properties that are queried and set with commands.

static VNA.command(get_cmd=None, set_cmd=None, doc=None, validator=None, values=False, values_container=<built-in function array>, complex_values=False)[source]

Create a property for the instrument.

This method is used to add a property to an instrument. These properties can be read-only, write-only, read-write, and can validate values before sending to the instrument as well as validate responses from the instrument to return proper types.

Parameters:
  • get_cmd (str | None) – Command sent to the instrument to request data

  • set_cmd (str | None) – Command sent to the instrument to set data

  • doc (str | None) – The docstring for the property

  • validator (Validator | None) – The Validator that will be used to transform data to the proper format before sending and after querying

  • values (bool) – Whether or not this command is using a Sequence to set data, or expects a Sequence in response.

  • values_container (type | None) – If values is true, you set set this to the type of container the values should be returned in. For example, this is np.array by default, meaning instead of return a list, you will get a numpy array.

  • complex_values (bool) – If the values expected from the instrument are complex. If so, the values will be converted from [real[0], imag[0], real[1], imag[1], …] to [complex(real[0], imag[0]), complex(real[1], imag[1]), …]

Returns:

The property constructed from the parameters passed. Should be set to a class variable

Return type:

property

For get_cmd and set_cmd, you can substitute delimiters to be replaced when the call is made. self is always passed, so you can reference any part of “self” when making the call. Additionally, the set_cmd receives an additional parameter arg which is the argument passed to the setter. These can be used in the get/set strings by using angle bracket delimiters and referring to the name. Here are some examples:

Here, we are assuming we are writing a command for an instrument with channels, and writing a command for the internal Channel class. In get_cmd, <self:cnum> gets the self.cnum property of the Channel class at runtime. In set_cmd, <arg> is replaced by the argument passed to the setter.

freq_start = VNA.command(
    get_cmd="CALC<self:cnum>:FREQ:STAR?",
    set_cmd="CALC<self:cnum>:FREQ:STAR <arg>",
)

And here’s an example of calling this function and what the resultant command strings would be after replacement.

_ = instr.ch1.freq_start
# Sends the command CALC1:FREQ:STAR? to the instrument
instr.ch1.freq_start = 100
# Sends the command CALC1:FREQ:STAR 100 to the instrument

Validators

Validators are (optionally, but almost always) passed to VNA.command. When a property is get or set, the appropriate validate command is called to convert input to the proper format expected by the instrument, or convert responses from the instrument to the proper type.

The current validators are:

The documentation for each of these explains more about their functionality, but in essence when writing a VNA.command, consider how the command must be formatted to be sent to the instrument and what the expected response from the instrument will be and how that can be transformed to something more useful than, say, a string.

Here’s an example of using a validator:

freq_start = VNA.command(
    get_cmd="CALC:FREQ:STAR?",
    set_cmd="CALC:FREQ:STAR <arg>",
    validator=FreqValidator()
)

# ...

# This will call FreqValidator.validate_input('100 kHz') to
# transform the string '100 kHz' to '100000'. The full command
# then becomes:
# CALC:FREQ:STAR 100000
instr.ch1.freq_start = '100 Hz'

# This will send the command CALC:FREQ:STAR? to the instrument
# then send the response from the instrument to
# FreqValidator.validate_output() to attempt to transform, as an
# example, the string '100000' to the int 100_000
_ = instr.ch1.freq_start