{ "cells": [ { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. _calibration::\n", "\n", "|\n", "|\n", "\n", "Download This Notebook: :download:`Calibration.ipynb`\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Calibration\n", "\n", "\n", "## Introduction\n", "\n", "This tutorial describes how to use **scikit-rf** to calibrate data taken from a VNA. For an introduction to VNA calibration see this article by Rumiantsev and Ridler [[1](#link_ref1)]., for an outline of different calibration algorithms see Doug Ryttings presentation [[2](#link_ref2)]. If you like to read books, then you may want to checkout [[3](#link_ref3)].\n", "\n", "What follows are examples of how to calibrate a device under test (DUT), assuming you have measured an acceptable set of standards, and have a corresponding set ideal responses. This may be referred to as *offline* calibration, because it is not occurring onboard the VNA itself. One benefit of this technique is that it provides maximum flexibility for non-conventional calibrations, and preserves all raw data.\n", "\n", "There are many calibration algorithms implemented in scikit-rf. The choice between them depends of the media, the available calibration standards and the desired trade-off between accuracy and effort. The calibration algorithms available in scikit-rf are listed in the [Calibration](../api/calibration/index.rst) API's page." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating a Calibration\n", "\n", "Calibrations are performed through a [Calibration](../api/calibration/index.rst) class. In General, [Calibration](../api/calibration/index.rst) objects require two arguments:\n", "\n", "* a list of measured `Network`'s\n", "* a list of ideal `Network`'s\n", "\n", "The `Network` elements in each list must all be similar (same number of ports, frequency info, etc) and must be aligned to each other, meaning the first element of ideals list must correspond to the first element of measured list. Self calibration algorithms, such as TRL, do not require predefined ideal responses." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## One-Port Example\n", "\n", "This example code is written to be instructive, not concise. To construct a one-port calibration you need to have measured at least three standards and have their known *ideal* responses in the form of `Network`s. These `Network` can be created from touchstone files, a format all modern VNA's support.\n", "\n", "In the following script the measured and ideal touchstone files for a conventional short-open-load (SOL) calkit are in folders `measured/` and `ideal/`, respectively. These are used to create a `OnePort` Calibration and correct a measured DUT \n", "\n", "\timport skrf as rf\n", "\tfrom skrf.calibration import OnePort\n", "\t\n", "\t## created necessary data for Calibration class\n", "\t\n", "\t# a list of Network types, holding 'ideal' responses\n", "\tmy_ideals = [\\\n", "\t rf.Network('ideal/short.s1p'),\n", "\t rf.Network('ideal/open.s1p'),\n", "\t rf.Network('ideal/load.s1p'),\n", "\t ]\n", "\t\n", "\t# a list of Network types, holding 'measured' responses\n", "\tmy_measured = [\\\n", "\t rf.Network('measured/short.s1p'),\n", "\t rf.Network('measured/open.s1p'),\n", "\t rf.Network('measured/load.s1p'),\n", "\t ]\n", "\t\n", "\t## create a Calibration instance\n", "\tcal = OnePort(\\\n", "\t ideals = my_ideals,\n", "\t measured = my_measured,\n", "\t )\n", "\t\n", "\t\n", "\t## run, and apply calibration to a DUT\n", "\t\n", "\t# run calibration algorithm\n", "\tcal.run() \n", "\t\n", "\t# apply it to a dut\n", "\tdut = rf.Network('my_dut.s1p')\n", "\tdut_caled = cal.apply_cal(dut)\n", "\tdut_caled.name = dut.name + ' corrected'\n", "\t\n", "\t# plot results\n", "\tdut_caled.plot_s_db()\n", "\t# save results \n", "\tdut_caled.write_touchstone()\n", "\n", "### Concise One-port\n", "\n", "This example achieves the same task as the one above except in a more concise *programmatic* way. \n", "\n", " import skrf as rf\n", " from skrf.calibration import OnePort\n", " \n", " my_ideals = rf.io.load_all_touchstones('ideals/')\n", " my_measured = rf.io.load_all_touchstones('measured/')\n", " \n", " \n", " ## create a Calibration instance\n", " cal = OnePort(\\\n", "\t ideals = [my_ideals[k] for k in ['short','open','load']],\n", "\t measured = [my_measured[k] for k in ['short','open','load']],\n", "\t )\n", " \n", " ## what you do with 'cal' may may be similar to above example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Two-port Calibrations\n", "\n", "Naturally, two-port calibration is more involved than one-port. **skrf** supports a few different two-port algorithms. The traditional `SOLT` algorithm uses the 12-term error model. This algorithms is straightforward, and similar to the OnePort example.\n", "\n", "The `EightTerm` calibration is based on the algorithm described in [[4](#link_ref4)], by R.A.Speciale. It can be constructed from any number of standards, providing that some fundamental constraints are met. In short, you need three two-port standards; one must be transmissive, and one must provide a known impedance and be reflective.\n", "Note, that the word *8-term* is used in the literature to describe a specific error model used by a variety of calibration algorithms, like TRL, LRM, etc. The `EightTerm` class, is an implementation of the algorithm cited above, which does not use any self-calibration. \n", "\n", "One important detail of using the 8-term error model formulation is that switch-terms may need to be measured in order to achieve a high quality calibration (thanks to Dylan Williams for pointing this out). These are described next.\n", "\n", "### Switch-terms\n", "\n", "Originally described by Roger Marks[[5](#link_ref5)], switch-terms account for the fact that the 8-term (aka *error-box* ) model is overly simplified. The two error networks change slightly depending on which port is being excited. This is due to the internal switch within the VNA. Hence, switch terms describe the imperfect load when the source is switch to the other port.\n", "\n", "Switch terms can be directly measured with a low insertion loss transmissive standard (like a thru) connected between the cables connected to the instrument ports. Sometime, a custom measurement configuration is available on the VNA itself.\n", "\n", "**skrf** has support for measuring switch terms in the `skrf.vi.vna` module, see the HP8510's `skrf.vi.vna.hp.HP8510C.get_switch_terms`, or PNA's `skrf.vi.vna.keysight.get_switch_terms` . Without switch-term measurements, your calibration quality will vary depending on properties of you VNA.\n", "\n", "### Using one-port ideals in two-port Calibration\n", "\n", "Commonly, you have data for ideal data for reflective standards in the form of one-port touchstone files (ie `.s1p`). To use this with skrf's two-port calibration method you need to create a two-port network that is a composite of the two networks. The function `skrf.network.two_port_reflect` does this\n", " \n", " short = rf.Network('ideals/short.s1p')\n", " shorts = rf.network.two_port_reflect(short, short)\n", "\n", "### SOLT Example\n", "\n", "Two-port calibration is accomplished in an identical way to one-port, except all the standards are two-port networks. This is even true of reflective and load standards (S21=S12=0). So if you measure reflective or load standards you should measure two of them simultaneously, and store information in a two-port. For example, connect a short to port-1 and a short to port-2, and save a two-port measurement as 'short,short.s2p' or similar.\n", "\n", "Alternatively, for a `SOLT`, `TwelveTerm`, or `EightTerm` calibration, it's also acceptable to measure one port at a time while leaving the other unused port open. Later, two one-port networks can be combined to a two-port network using `skrf.network.two_port_reflect`, explained above.\n", "\n", "By default, no isolation calibration takes place. For reflective and even load standards, $S_{21}$ and $S_{12}$ are ignored. If isolation calibration is needed, one must explicitly tell scikit-rf to do so. Specifically, an optional parameter `isolation` is used for this purpose. To calibrate isolation, set this parameter to a two-port network, representing the measurement result with loads on both ports.\n", "\n", "It should be noted that the less common (but still [supported](../api/calibration/generated/skrf.calibration.calibration.SixteenTerm.rst) `SixteenTerm` calibration is a notable exception: this calibration is specifically designed to eliminate port leakages better than the standard `SOLT` model. Measuring a combination of standards at both port simultaneously is *required*.\n", "\n", "\n", " import skrf as rf\n", " from skrf.calibration import SOLT\n", " \n", " \n", " \n", " # a list of Network types, holding 'ideal' responses\n", " my_ideals = [\n", "\t rf.Network('ideal/short, short.s2p'),\n", "\t rf.Network('ideal/open, open.s2p'),\n", "\t rf.Network('ideal/load, load.s2p'),\n", "\t rf.Network('ideal/thru.s2p'),\n", " ]\n", " \n", " # a list of Network types, holding 'measured' responses\n", " my_measured = [\n", "\t rf.Network('measured/short, short.s2p'),\n", "\t rf.Network('measured/open, open.s2p'),\n", "\t rf.Network('measured/load, load.s2p'),\n", "\t rf.Network('measured/thru.s2p'),\n", "\t ]\n", " \n", " \n", " ## create a SOLT instance\n", " cal = SOLT(\n", "\t ideals = my_ideals,\n", "\t measured = my_measured,\n", " rf.Network('measured/load, load.s2p'),\n", " # isolation calibration is optional, it can be removed.\n", "\t )\n", " \n", " \n", " ## run, and apply calibration to a DUT\n", " \n", " # run calibration algorithm\n", " cal.run() \n", " \n", " # apply it to a dut\n", " dut = rf.Network('my_dut.s2p')\n", " dut_caled = cal.apply_cal(dut)\n", " \n", " # plot results\n", " dut_caled.plot_s_db()\n", " # save results \n", " dut_caled.write_touchstone()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Saving and Recalling a Calibration\n", "\n", "[Calibration](../api/calibration/index.rst)'s can be written-to and \n", "read-from disk using the temporary storage container of pickles. Writing can be accomplished by using `Calibration.write`, or `rf.io.write()`, and reading is done with `rf.io.read()`. Since these functions rely on pickling, they are not recommended for long-term data storage. Currently there is no way to achieve long term storage of a Calibration object, other than saving the script used to generate it." ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "## References\n", "\n", " \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "anaconda-cloud": {}, "celltoolbar": "Raw Cell Format", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.7" } }, "nbformat": 4, "nbformat_minor": 1 }