VNA Automatic Calibration via SCPI#

Introduction#

This chapter describes how to automate the calibration process of a Vector Network Analyzer (VNA) using Standard Commands for Programmable Instruments (SCPI) and Python.

Calibration is the most critical step in RF measurements. It mathematically removes the systematic errors (Directivity, Source Match, and Reflection Tracking) introduced by the VNA hardware and cables, shifting the measurement plane to the end of the test port.

Objectives#

This tutorial serves two primary purposes:

  1. Practical Application: To perform a “Full One-Port” (FOPort) automatic calibration required for accurate reflection measurements (S11) up to 18 GHz.

  2. Skill Development: To provide a hands-on example of how to communicate with high-end test equipment using the PyVISA library and raw SCPI strings.

System Requirements#

  • Hardware: Rohde & Schwarz ZNL series VNA and a compatible Automatic Calibration Unit (e.g., ZN-Z151).

  • Connection: Ethernet (TCP/IP) via HiSLIP protocol.

  • Libraries: pyvisa, numpy.

Implementation#

The following Python script initializes the instrument, sets the frequency sweep from 100 MHz to 18 GHz, and triggers the internal AutoCal routine.

 1import pyvisa
 2
 3# Initialize communication
 4rm = pyvisa.ResourceManager()
 5vna = rm.open_resource('TCPIP0::10.0.0.5::hislip0::INSTR')
 6vna.timeout = 120000  # Calibration hardware takes time to switch internal states
 7
 8# Reset the instrument to default state
 9vna.write("*RST")
10
11# Set frequency range: 100 MHz to 18 GHz
12vna.write("SENSe1:FREQuency:STARt 100e6")
13vna.write("SENSe1:FREQuency:STOP 18e9")
14vna.write("SENSe1:SWEep:POINts 1601")
15
16# Run autocal on port 1 using factory characterization ('')
17# The VNA automatically detects the CalUnit connected to Port 1
18vna.write("SENSe1:CORRection:COLLect:AUTO '', 1")
19
20# *OPC? is vital: it pauses the script until the VNA hardware is finished
21vna.query("*OPC?")
22
23# Verify calibration status
24state = vna.query("SENSe1:CORRection:STATe?").strip()
25print(f"Calibration active: {state}")  # 1 = Success/Active
26
27# Save the resulting calibration data to the internal storage
28vna.write("MMEMory:STORe:CORRection 1, 'AutoCal_Port1.cal'")
29
30vna.close()
31rm.close()
32
33# Later load with
34#vna.write("MMEMory:LOAD:CORRection 1, 'AutoCal_Port1.cal'")

Command Breakdown#

  • SENSe1:CORRection:COLLect:AUTO '', 1: The empty string tells the VNA to use the factory data stored inside the CalUnit. The 1 specifies the physical Port 1 of the VNA.

  • *OPC?: Stands for “Operation Complete.” It prevents the script from closing the session before the physical switches inside the CalUnit have finished cycling.

Extracting and Verifying Calibration Error Terms#

Once a calibration is performed and saved, it is often necessary to verify the quality of the calibration or extract the error terms for offline analysis. The following script demonstrates how to load a previously saved calibration file and retrieve the Directivity, Source Match, and Reflection Tracking coefficients.

 1import pyvisa
 2import numpy as np
 3import skrf as rf
 4
 5rm = pyvisa.ResourceManager()
 6vna = rm.open_resource('TCPIP0::10.0.0.5::hislip0::INSTR')
 7vna.timeout = 100000
 8
 9# Reset and load the specific calibration file
10vna.write("*RST")
11vna.write("MMEMory:LOAD:CORRection 1, 'AutoCal_Port1.cal'")
12
13# Verification Queries
14print(f"Is AutoCal: {vna.query('SENSe1:CORRection:DATA:PARameter? ACAL')}")
15print(f"Cal Date: {vna.query('SENSe1:CORRection:DATE?')}")
16
17# Set to HOLD mode to ensure data consistency during readout
18vna.write("SENSe1:SWEep:MODE HOLD")
19
20# Request raw error term data
21D_raw = vna.query("SENSe1:CORRection:CDATa? 'DIRECTIVITY', 1, 0")
22S_raw = vna.query("SENSe1:CORRection:CDATa? 'SRCMATCH', 1, 0")
23R_raw = vna.query("SENSe1:CORRection:CDATa? 'REFLTRACK', 1, 0")
24
25# Data Transformation: From String to Complex Array
26D_complex = np.array(D_raw.split(','), dtype=float)
27D_complex = D_complex[0::2] + 1j * D_complex[1::2]
28
29S_complex = np.array(S_raw.split(','), dtype=float)
30S_complex = S_complex[0::2] + 1j * S_complex[1::2]
31
32R_complex = np.array(R_raw.split(','), dtype=float)
33R_complex = R_complex[0::2] + 1j * R_complex[1::2]
34
35print(f"Points Extracted: {len(D_complex)}")
36print(f"First Directivity Point: {D_complex[0]}")
37
38vna.write("SENSe1:SWEep:MODE CONTinuous")
39
40#Converting error terms to Network objects for visualizing them using Sci kit RF
41
42freq = rf.Frequency(start=100e6, stop=18e9, npoints=1601, unit='Hz')
43ntwk_D = rf.Network(frequency=freq, s=D_complex, name='Directivity')
44ntwk_S = rf.Network(frequency=freq, s=S_complex, name='Source Match')
45ntwk_R = rf.Network(frequency=freq, s=R_complex, name='Refl Tracking')
46
47
48plt.figure(figsize=(15, 5))
49
50plt.subplot(1, 3, 1)
51ntwk_D.plot_s_db()
52plt.title('Directivity (ED)')
53
54plt.subplot(1, 3, 2)
55ntwk_S.plot_s_db()
56plt.title('Source Match (ES)')
57
58plt.subplot(1, 3, 3)
59ntwk_R.plot_s_smith()
60plt.title('Reflection Tracking (ER)\n(Smith Chart)')
61
62plt.tight_layout()
63plt.show()
64
65
66vna.close()
67rm.close()

Understanding the Data Transition: D_raw to D_complex#

The transition from the raw string returned by the VNA to a usable complex mathematical array in Python happens in three distinct stages:

1. String Splitting and Type Casting#

When the VNA executes CDATa?, it returns a single, massive string of comma-separated values (e.g., "0.012,-0.005,0.011,...").

  • D_raw.split(','): Breaks the string into a Python list of individual substrings.

  • np.array(..., dtype=float): Converts those substrings into 64-bit floating-point numbers.

2. De-interleaving with Slicing#

VNA hardware communicates complex data in an interleaved format: [Real_1, Imag_1, Real_2, Imag_2, ...]. To perform vector math, we must separate these.

  • D_complex[0::2]: This uses NumPy slicing to start at index 0 and take every 2nd element. This captures all Real components.

  • D_complex[1::2]: This starts at index 1 and takes every 2nd element. This captures all Imaginary components.

3. Complex Vector Reconstruction#

The final step uses the imaginary unit 1j (the Python equivalent of \(i\)) to rebuild the vectors:

\[D_{complex} = Real + j \cdot Imaginary\]

By adding the two sliced arrays together with the 1j multiplier, NumPy creates a single array of complex128 objects. This allows you to calculate Magnitude (np.abs()) and Phase (np.angle()) directly, which is essential for analyzing systematic errors at high frequencies like 18 GHz.

Visualization of Error Terms#

After extracting the complex coefficients, we can visualize the VNA’s internal state. These plots provide insight into the quality of the calibration and the physical characteristics of the test setup.

VNA Calibration Error Terms

Figure 1: Log-Magnitude plots of Directivity and Source Match, alongside a Smith Chart representation of Reflection Tracking.#

Data Interpretation#

  • Directivity (ED): Represents the leakage within the VNA. A value moving from -50 dB toward -15 dB at 18 GHz is typical, showing reduced isolation at higher frequencies.

  • Source Match (ES): Shows the reflections looking back into the VNA port. The values between -35 dB and -18 dB indicate a healthy match for a standard cable assembly.

  • Reflection Tracking (ER): The “spiral” seen on the Smith Chart is the result of the phase delay introduced by the cables and PCB traces. At 18 GHz, the signal undergoes multiple full rotations of phase, which the VNA mathematically tracks to shift the measurement plane to your device.