diff --git a/AssembleFirmware.py b/AssembleFirmware.py
new file mode 100755
index 0000000..01f1e52
--- /dev/null
+++ b/AssembleFirmware.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+
+import os
+import binascii
+
+FPGA_BITSTREAM = "FPGA/VNA/top.bin"
+MCU_FW = "Software/VNA_embedded/Debug/VNA_embedded.bin"
+
+HEADER_SIZE = 24
+
+f = open("test.vnafw", "wb")
+f.write(bytes("VNA!", 'utf-8'))
+
+bitstream = open(FPGA_BITSTREAM, "rb")
+firmware = open(MCU_FW, "rb")
+
+size_FPGA = os.path.getsize(FPGA_BITSTREAM)
+size_MCU = os.path.getsize(MCU_FW)
+print("Got FPGA bitstream of size "+str(size_FPGA))
+print("Got MCU firmware of size "+str(size_MCU))
+
+#Create header
+# Start address of FPGA bitstream
+f.write((HEADER_SIZE).to_bytes(4, byteorder='little'))
+# Size of FPGA bitstream
+f.write(size_FPGA.to_bytes(4, byteorder='little'))
+# Start address of MCU firmware
+f.write((HEADER_SIZE + size_FPGA).to_bytes(4, byteorder='little'))
+# Size of MCU firmware
+f.write(size_MCU.to_bytes(4, byteorder='little'))
+
+# Calculate CRC
+def CRC32_from_file(filename, initial_CRC):
+ buf = open(filename,'rb').read()
+ buf = (binascii.crc32(buf, initial_CRC) & 0xFFFFFFFF)
+ return buf
+
+print("Calculating CRC...", end="")
+CRC = CRC32_from_file(FPGA_BITSTREAM, 0xFFFFFFFF)
+CRC = CRC32_from_file(MCU_FW, CRC)
+print(":"+hex(CRC))
+f.write(CRC.to_bytes(4, byteorder='little'))
+
+# Check that we used the correct header size
+if f.tell() != HEADER_SIZE:
+ print("Incorrect header size (defined as "+str(HEADER_SIZE)+" but actual header is of size "+str(f.tell())+")")
+ exit(-1)
+
+f.write(bitstream.read())
+f.write(firmware.read())
+
+if f.tell() % 256 != 0:
+ padding = 256 - f.tell() % 256
+ f.write(bytearray(padding))
+
+print("Created combined firmware file of size "+str(f.tell()))
+
diff --git a/FPGA/VNA/VNA.gise b/FPGA/VNA/VNA.gise
index de67d02..18d9233 100644
--- a/FPGA/VNA/VNA.gise
+++ b/FPGA/VNA/VNA.gise
@@ -254,6 +254,8 @@
+
+
diff --git a/Software/PC_Application/.gitignore b/Software/PC_Application/.gitignore
new file mode 100644
index 0000000..6089be0
--- /dev/null
+++ b/Software/PC_Application/.gitignore
@@ -0,0 +1,74 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+moc_*.h
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+
diff --git a/Software/PC_Application/51-vna.rules b/Software/PC_Application/51-vna.rules
new file mode 100644
index 0000000..6da541f
--- /dev/null
+++ b/Software/PC_Application/51-vna.rules
@@ -0,0 +1 @@
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="564e", MODE:="0666"
diff --git a/Software/PC_Application/Application b/Software/PC_Application/Application
new file mode 100755
index 0000000..0faca95
Binary files /dev/null and b/Software/PC_Application/Application differ
diff --git a/Software/PC_Application/Application.pro b/Software/PC_Application/Application.pro
new file mode 100644
index 0000000..ebc92fb
--- /dev/null
+++ b/Software/PC_Application/Application.pro
@@ -0,0 +1,116 @@
+HEADERS += \
+ ../VNA_embedded/Application/Communication/Protocol.hpp \
+ Calibration/calibration.h \
+ Calibration/calibrationtracedialog.h \
+ Calibration/calkit.h \
+ Calibration/calkitdialog.h \
+ Calibration/measurementmodel.h \
+ CustomWidgets/siunitedit.h \
+ CustomWidgets/tilewidget.h \
+ CustomWidgets/toggleswitch.h \
+ CustomWidgets/touchstoneimport.h \
+ Device/device.h \
+ Device/devicelog.h \
+ Device/firmwareupdatedialog.h \
+ Device/manualcontroldialog.h \
+ Menu/menu.h \
+ Menu/menuaction.h \
+ Menu/menubool.h \
+ Menu/menuitem.h \
+ Menu/menuvalue.h \
+ Tools/eseries.h \
+ Tools/impedancematchdialog.h \
+ Traces/bodeplotaxisdialog.h \
+ Traces/markerwidget.h \
+ Traces/trace.h \
+ Traces/tracebodeplot.h \
+ Traces/traceeditdialog.h \
+ Traces/traceexportdialog.h \
+ Traces/traceimportdialog.h \
+ Traces/tracemarker.h \
+ Traces/tracemarkermodel.h \
+ Traces/tracemodel.h \
+ Traces/traceplot.h \
+ Traces/tracesmithchart.h \
+ Traces/tracewidget.h \
+ averaging.h \
+ qwtplotpiecewisecurve.h \
+ touchstone.h \
+ unit.h \
+ valueinput.h \
+ vna.h
+
+SOURCES += \
+ ../VNA_embedded/Application/Communication/Protocol.cpp \
+ Calibration/calibration.cpp \
+ Calibration/calibrationtracedialog.cpp \
+ Calibration/calkit.cpp \
+ Calibration/calkitdialog.cpp \
+ Calibration/measurementmodel.cpp \
+ CustomWidgets/siunitedit.cpp \
+ CustomWidgets/tilewidget.cpp \
+ CustomWidgets/toggleswitch.cpp \
+ CustomWidgets/touchstoneimport.cpp \
+ Device/device.cpp \
+ Device/devicelog.cpp \
+ Device/firmwareupdatedialog.cpp \
+ Device/manualcontroldialog.cpp \
+ Menu/menu.cpp \
+ Menu/menuaction.cpp \
+ Menu/menubool.cpp \
+ Menu/menuitem.cpp \
+ Menu/menuvalue.cpp \
+ Tools/eseries.cpp \
+ Tools/impedancematchdialog.cpp \
+ Traces/bodeplotaxisdialog.cpp \
+ Traces/markerwidget.cpp \
+ Traces/trace.cpp \
+ Traces/tracebodeplot.cpp \
+ Traces/traceeditdialog.cpp \
+ Traces/traceexportdialog.cpp \
+ Traces/traceimportdialog.cpp \
+ Traces/tracemarker.cpp \
+ Traces/tracemarkermodel.cpp \
+ Traces/tracemodel.cpp \
+ Traces/traceplot.cpp \
+ Traces/tracesmithchart.cpp \
+ Traces/tracewidget.cpp \
+ averaging.cpp \
+ main.cpp \
+ qwtplotpiecewisecurve.cpp \
+ touchstone.cpp \
+ unit.cpp \
+ valueinput.cpp \
+ vna.cpp
+
+LIBS += -lusb-1.0
+unix:INCLUDEPATH += /usr/include/qwt
+unix:LIBS += -L/usr/lib/ -lqwt-qt5
+win32:INCLUDEPATH += C:\Qwt-6.1.4\include
+win32:LIBS += -LC:\Qwt-6.1.4\lib -lqwt
+
+QT += widgets
+
+FORMS += \
+ Calibration/calibrationtracedialog.ui \
+ Calibration/calkitdialog.ui \
+ CustomWidgets/tilewidget.ui \
+ CustomWidgets/touchstoneimport.ui \
+ Device/devicelog.ui \
+ Device/firmwareupdatedialog.ui \
+ Device/manualcontroldialog.ui \
+ Tools/impedancematchdialog.ui \
+ Traces/bodeplotaxisdialog.ui \
+ Traces/markerwidget.ui \
+ Traces/traceeditdialog.ui \
+ Traces/traceexportdialog.ui \
+ Traces/traceimportdialog.ui \
+ Traces/tracewidget.ui \
+ main.ui
+
+DISTFILES +=
+
+RESOURCES += \
+ icons.qrc
+
+CONFIG += c++14
diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp
new file mode 100644
index 0000000..2621355
--- /dev/null
+++ b/Software/PC_Application/Calibration/calibration.cpp
@@ -0,0 +1,666 @@
+#include "calibration.h"
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+Calibration::Calibration()
+{
+ // Creator vectors for measurements
+ measurements[Measurement::Port1Open].datapoints = vector();
+ measurements[Measurement::Port1Short].datapoints = vector();
+ measurements[Measurement::Port1Load].datapoints = vector();
+ measurements[Measurement::Port2Open].datapoints = vector();
+ measurements[Measurement::Port2Short].datapoints = vector();
+ measurements[Measurement::Port2Load].datapoints = vector();
+ measurements[Measurement::Isolation].datapoints = vector();
+ measurements[Measurement::Through].datapoints = vector();
+
+ type = Type::None;
+}
+
+void Calibration::clearMeasurements()
+{
+ for(auto m : measurements) {
+ m.second.datapoints.clear();
+ }
+}
+
+void Calibration::clearMeasurement(Calibration::Measurement type)
+{
+ measurements[type].datapoints.clear();
+ measurements[type].timestamp = QDateTime();
+}
+
+void Calibration::addMeasurement(Calibration::Measurement type, Protocol::Datapoint &d)
+{
+ measurements[type].datapoints.push_back(d);
+ measurements[type].timestamp = QDateTime::currentDateTime();
+}
+
+bool Calibration::calculationPossible(Calibration::Type type)
+{
+ std::vector requiredMeasurements;
+ switch(type) {
+ case Type::Port1SOL:
+ requiredMeasurements.push_back(Measurement::Port1Open);
+ requiredMeasurements.push_back(Measurement::Port1Short);
+ requiredMeasurements.push_back(Measurement::Port1Load);
+ break;
+ case Type::Port2SOL:
+ requiredMeasurements.push_back(Measurement::Port2Open);
+ requiredMeasurements.push_back(Measurement::Port2Short);
+ requiredMeasurements.push_back(Measurement::Port2Load);
+ break;
+ case Type::FullSOLT:
+ requiredMeasurements.push_back(Measurement::Port1Open);
+ requiredMeasurements.push_back(Measurement::Port1Short);
+ requiredMeasurements.push_back(Measurement::Port1Load);
+ requiredMeasurements.push_back(Measurement::Port2Open);
+ requiredMeasurements.push_back(Measurement::Port2Short);
+ requiredMeasurements.push_back(Measurement::Port2Load);
+ requiredMeasurements.push_back(Measurement::Through);
+ break;
+ case Type::None:
+ return false;
+ }
+ return SanityCheckSamples(requiredMeasurements);
+}
+
+bool Calibration::constructErrorTerms(Calibration::Type type)
+{
+ if(!calculationPossible(type)) {
+ return false;
+ }
+ if(minFreq < kit.minFreq() || maxFreq > kit.maxFreq()) {
+ // Calkit does not support complete calibration range
+ QMessageBox::critical(nullptr, "Unable to perform calibration", "The calibration kit does not support the complete span. Please choose a different calibration kit or a narrower span.");
+ return false;
+ }
+ switch(type) {
+ case Type::Port1SOL:
+ constructPort1SOL();
+ break;
+ case Type::Port2SOL:
+ constructPort2SOL();
+ break;
+ case Type::FullSOLT:
+ construct12TermPoints();
+ break;
+ case Type::None:
+ break;
+ }
+ this->type = type;
+ return true;
+}
+
+void Calibration::resetErrorTerms()
+{
+ type = Type::None;
+ points.clear();
+}
+
+void Calibration::construct12TermPoints()
+{
+ std::vector requiredMeasurements;
+ requiredMeasurements.push_back(Measurement::Port1Open);
+ requiredMeasurements.push_back(Measurement::Port1Short);
+ requiredMeasurements.push_back(Measurement::Port1Load);
+ requiredMeasurements.push_back(Measurement::Port2Open);
+ requiredMeasurements.push_back(Measurement::Port2Short);
+ requiredMeasurements.push_back(Measurement::Port2Load);
+ requiredMeasurements.push_back(Measurement::Through);
+ if(!SanityCheckSamples(requiredMeasurements)) {
+ throw runtime_error("Missing/wrong calibration measurement");
+ }
+ requiredMeasurements.push_back(Measurement::Isolation);
+ bool isolation_measured = SanityCheckSamples(requiredMeasurements);
+
+ // If we get here the calibration measurements are all okay
+ points.clear();
+ for(unsigned int i = 0;i(measurements[Measurement::Port1Open].datapoints[i].real_S11, measurements[Measurement::Port1Open].datapoints[i].imag_S11);
+ auto S11_short = complex(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11);
+ auto S11_load = complex(measurements[Measurement::Port1Load].datapoints[i].real_S11, measurements[Measurement::Port1Load].datapoints[i].imag_S11);
+ auto S22_open = complex(measurements[Measurement::Port2Open].datapoints[i].real_S22, measurements[Measurement::Port2Open].datapoints[i].imag_S22);
+ auto S22_short = complex(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22);
+ auto S22_load = complex(measurements[Measurement::Port2Load].datapoints[i].real_S22, measurements[Measurement::Port2Load].datapoints[i].imag_S22);
+ auto S21_isolation = complex(0,0);
+ auto S12_isolation = complex(0,0);
+ if(isolation_measured) {
+ S21_isolation = complex(measurements[Measurement::Isolation].datapoints[i].real_S21, measurements[Measurement::Isolation].datapoints[i].imag_S21);
+ S12_isolation = complex(measurements[Measurement::Isolation].datapoints[i].real_S12, measurements[Measurement::Isolation].datapoints[i].imag_S12);
+ }
+ auto S11_through = complex(measurements[Measurement::Through].datapoints[i].real_S11, measurements[Measurement::Through].datapoints[i].imag_S11);
+ auto S21_through = complex(measurements[Measurement::Through].datapoints[i].real_S21, measurements[Measurement::Through].datapoints[i].imag_S21);
+ auto S22_through = complex(measurements[Measurement::Through].datapoints[i].real_S22, measurements[Measurement::Through].datapoints[i].imag_S22);
+ auto S12_through = complex(measurements[Measurement::Through].datapoints[i].real_S12, measurements[Measurement::Through].datapoints[i].imag_S12);
+
+ auto actual = kit.toReflection(p.frequency);
+ // Forward calibration
+ computeSOL(S11_short, S11_open, S11_load, p.fe00, p.fe11, p.fe10e01, actual.Open, actual.Short, actual.Load);
+ p.fe30 = S21_isolation;
+ // See page 17 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
+ // Formulas for S11M and S21M solved for e22 and e10e32
+ auto deltaS = actual.ThroughS11*actual.ThroughS22 - actual.ThroughS21 * actual.ThroughS12;
+ p.fe22 = ((S11_through - p.fe00)*(1.0 - p.fe11 * actual.ThroughS11)-actual.ThroughS11*p.fe10e01)
+ / ((S11_through - p.fe00)*(actual.ThroughS22-p.fe11*deltaS)-deltaS*p.fe10e01);
+ p.fe10e32 = (S21_through - p.fe30)*(1.0 - p.fe11*actual.ThroughS11 - p.fe22*actual.ThroughS22 + p.fe11*p.fe22*deltaS) / actual.ThroughS21;
+ // Reverse calibration
+ computeSOL(S22_short, S22_open, S22_load, p.re33, p.re22, p.re23e32, actual.Open, actual.Short, actual.Load);
+ p.re03 = S12_isolation;
+ p.re11 = ((S22_through - p.re33)*(1.0 - p.re22 * actual.ThroughS22)-actual.ThroughS22*p.re23e32)
+ / ((S22_through - p.re33)*(actual.ThroughS11-p.re22*deltaS)-deltaS*p.re23e32);
+ p.re23e01 = (S12_through - p.re03)*(1.0 - p.re11*actual.ThroughS11 - p.re22*actual.ThroughS22 + p.re11*p.re22*deltaS) / actual.ThroughS12;
+ points.push_back(p);
+ }
+}
+
+void Calibration::constructPort1SOL()
+{
+ std::vector requiredMeasurements;
+ requiredMeasurements.push_back(Measurement::Port1Open);
+ requiredMeasurements.push_back(Measurement::Port1Short);
+ requiredMeasurements.push_back(Measurement::Port1Load);
+ if(!SanityCheckSamples(requiredMeasurements)) {
+ throw runtime_error("Missing/wrong calibration measurement");
+ }
+
+ // If we get here the calibration measurements are all okay
+ points.clear();
+ for(unsigned int i = 0;i(measurements[Measurement::Port1Open].datapoints[i].real_S11, measurements[Measurement::Port1Open].datapoints[i].imag_S11);
+ auto S11_short = complex(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11);
+ auto S11_load = complex(measurements[Measurement::Port1Load].datapoints[i].real_S11, measurements[Measurement::Port1Load].datapoints[i].imag_S11);
+ // OSL port1
+ auto actual = kit.toReflection(p.frequency);
+ // See page 19 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
+ computeSOL(S11_short, S11_open, S11_load, p.fe00, p.fe11, p.fe10e01, actual.Open, actual.Short, actual.Load);
+ // All other calibration coefficients to ideal values
+ p.fe30 = 0.0;
+ p.fe22 = 0.0;
+ p.fe10e32 = 1.0;
+ p.re33 = 0.0;
+ p.re22 = 0.0;
+ p.re23e32 = 1.0;
+ p.re03 = 0.0;
+ p.re11 = 0.0;
+ p.re23e01 = 1.0;
+ points.push_back(p);
+ }
+}
+
+void Calibration::constructPort2SOL()
+{
+ std::vector requiredMeasurements;
+ requiredMeasurements.push_back(Measurement::Port2Open);
+ requiredMeasurements.push_back(Measurement::Port2Short);
+ requiredMeasurements.push_back(Measurement::Port2Load);
+ if(!SanityCheckSamples(requiredMeasurements)) {
+ throw runtime_error("Missing/wrong calibration measurement");
+ }
+
+ // If we get here the calibration measurements are all okay
+ points.clear();
+ for(unsigned int i = 0;i(measurements[Measurement::Port2Open].datapoints[i].real_S22, measurements[Measurement::Port2Open].datapoints[i].imag_S22);
+ auto S22_short = complex(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22);
+ auto S22_load = complex(measurements[Measurement::Port2Load].datapoints[i].real_S22, measurements[Measurement::Port2Load].datapoints[i].imag_S22);
+ // OSL port2
+ auto actual = kit.toReflection(p.frequency);
+ // See page 19 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
+ computeSOL(S22_short, S22_open, S22_load, p.re33, p.re22, p.re23e32, actual.Open, actual.Short, actual.Load);
+ // All other calibration coefficients to ideal values
+ p.fe30 = 0.0;
+ p.fe22 = 0.0;
+ p.fe10e32 = 1.0;
+ p.fe00 = 0.0;
+ p.fe11 = 0.0;
+ p.fe10e01 = 1.0;
+ p.re03 = 0.0;
+ p.re11 = 0.0;
+ p.re23e01 = 1.0;
+ points.push_back(p);
+ }
+}
+
+void Calibration::correctMeasurement(Protocol::Datapoint &d)
+{
+ if(type == Type::None) {
+ // No calibration data, do nothing
+ return;
+ }
+ // Convert measurements to complex variables
+ auto S11m = complex(d.real_S11, d.imag_S11);
+ auto S21m = complex(d.real_S21, d.imag_S21);
+ auto S22m = complex(d.real_S22, d.imag_S22);
+ auto S12m = complex(d.real_S12, d.imag_S12);
+
+ // find correct entry
+ auto p = getCalibrationPoint(d);
+
+ // equations from page 20 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
+ auto denom = (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11) * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
+ - (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01 * p.fe22 * p.re11;
+ auto S11 = ((S11m - p.fe00) / p.fe10e01 * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
+ - p.fe22 * (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01) / denom;
+ auto S21 = ((S21m - p.fe30) / p.fe10e32 * (1.0 + (S22m - p.re33) / p.re23e32 * (p.re22 - p.fe22))) / denom;
+ auto S22 = ((S22m - p.re33) / p.re23e32 * (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11)
+ - p.re11 * (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01) / denom;
+ auto S12 = ((S12m - p.re03) / p.re23e01 * (1.0 + (S11m - p.fe00) / p.fe10e01 * (p.fe11 - p.re11))) / denom;
+
+ d.real_S11 = S11.real();
+ d.imag_S11 = S11.imag();
+ d.real_S12 = S12.real();
+ d.imag_S12 = S12.imag();
+ d.real_S21 = S21.real();
+ d.imag_S21 = S21.imag();
+ d.real_S22 = S22.real();
+ d.imag_S22 = S22.imag();
+}
+
+Calibration::InterpolationType Calibration::getInterpolation(Protocol::SweepSettings settings)
+{
+ if(!points.size()) {
+ return InterpolationType::NoCalibration;
+ }
+ if(settings.f_start < points.front().frequency || settings.f_stop > points.back().frequency) {
+ return InterpolationType::Extrapolate;
+ }
+ // Either exact or interpolation, check individual frequencies
+ uint32_t f_step = (settings.f_stop - settings.f_start) / (settings.points - 1);
+ for(uint64_t f = settings.f_start; f <= settings.f_stop; f += f_step) {
+ if(find_if(points.begin(), points.end(), [&f](const Point& p){
+ return abs(f - p.frequency) < 100;
+ }) == points.end()) {
+ return InterpolationType::Interpolate;
+ }
+ }
+ // if we get here all frequency points were matched
+ if(points.front().frequency == settings.f_start && points.back().frequency == settings.f_stop) {
+ return InterpolationType::Unchanged;
+ } else {
+ return InterpolationType::Exact;
+ }
+}
+
+QString Calibration::MeasurementToString(Calibration::Measurement m)
+{
+ switch(m) {
+ case Measurement::Port1Open:
+ return "Port 1 Open";
+ case Measurement::Port1Short:
+ return "Port 1 Short";
+ case Measurement::Port1Load:
+ return "Port 1 Load";
+ case Measurement::Port2Open:
+ return "Port 2 Open";
+ case Measurement::Port2Short:
+ return "Port 2 Short";
+ case Measurement::Port2Load:
+ return "Port 2 Load";
+ case Measurement::Through:
+ return "Through";
+ case Measurement::Isolation:
+ return "Isolation";
+ default:
+ return "Unknown";
+ }
+}
+
+QString Calibration::TypeToString(Calibration::Type t)
+{
+ switch(t) {
+ case Type::Port1SOL: return "Port 1"; break;
+ case Type::Port2SOL: return "Port 2"; break;
+ case Type::FullSOLT: return "SOLT"; break;
+ default: return "None"; break;
+ }
+}
+
+const std::vector Calibration::Types()
+{
+ const std::vector ret = {Type::Port1SOL, Type::Port2SOL, Type::FullSOLT};
+ return ret;
+}
+
+const std::vector Calibration::Measurements(Calibration::Type type)
+{
+ switch(type) {
+ case Type::FullSOLT:
+ case Type::None:
+ return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load, Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load, Measurement::Through, Measurement::Isolation};
+ break;
+ case Type::Port1SOL:
+ return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load};
+ break;
+ case Type::Port2SOL:
+ return {Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load};
+ break;
+ default:
+ return {};
+ break;
+ }
+}
+
+Calibration::MeasurementInfo Calibration::getMeasurementInfo(Calibration::Measurement m)
+{
+ MeasurementInfo info;
+ switch(m) {
+ case Measurement::Port1Short:
+ info.name = "Port 1 short";
+ info.prerequisites = "Short standard connected to port 1, port 2 open";
+ break;
+ case Measurement::Port1Open:
+ info.name = "Port 1 open";
+ info.prerequisites = "Open standard connected to port 1, port 2 open";
+ break;
+ case Measurement::Port1Load:
+ info.name = "Port 1 load";
+ info.prerequisites = "Load standard connected to port 1, port 2 open";
+ break;
+ case Measurement::Port2Short:
+ info.name = "Port 2 short";
+ info.prerequisites = "Port 1 open, short standard connected to port 2";
+ break;
+ case Measurement::Port2Open:
+ info.name = "Port 2 open";
+ info.prerequisites = "Port 1 open, open standard connected to port 2";
+ break;
+ case Measurement::Port2Load:
+ info.name = "Port 2 load";
+ info.prerequisites = "Port 1 open, load standard connected to port 2";
+ break;
+ case Measurement::Through:
+ info.name = "Through";
+ info.prerequisites = "Port 1 connected to port 2 via through standard";
+ break;
+ case Measurement::Isolation:
+ info.name = "Isolation";
+ info.prerequisites = "Both ports terminated into 50 ohm";
+ }
+ info.points = measurements[m].datapoints.size();
+ if(info.points > 0) {
+ info.fmin = measurements[m].datapoints.front().frequency;
+ info.fmax = measurements[m].datapoints.back().frequency;
+ info.points = measurements[m].datapoints.size();
+ }
+ info.timestamp = measurements[m].timestamp;
+ return info;
+}
+
+std::vector Calibration::getErrorTermTraces()
+{
+ std::vector traces;
+ const QString traceNames[12] = {"e00", "F_e11", "e10e01", "e10e32", "F_e22", "e30", "e33", "R_e11", "e23e32", "e23e01", "R_e22", "e03"};
+ constexpr bool reflection[12] = {true, true, false, false, true, false, true, true, false, false, true, false};
+ for(int i=0;i<12;i++) {
+ auto t = new Trace(traceNames[i], Qt::red);
+ t->setCalibration(true);
+ t->setReflection(reflection[i]);
+ traces.push_back(t);
+ }
+ for(auto p : points) {
+ Trace::Data d;
+ d.frequency = p.frequency;
+ for(int i=0;i<12;i++) {
+ switch(i) {
+ case 0: d.S = p.fe00; break;
+ case 1: d.S = p.fe11; break;
+ case 2: d.S = p.fe10e01; break;
+ case 3: d.S = p.fe10e32; break;
+ case 4: d.S = p.fe22; break;
+ case 5: d.S = p.fe30; break;
+ case 6: d.S = p.re33; break;
+ case 7: d.S = p.re11; break;
+ case 8: d.S = p.re23e32; break;
+ case 9: d.S = p.re23e01; break;
+ case 10: d.S = p.re22; break;
+ case 11: d.S = p.re03; break;
+ }
+ traces[i]->addData(d);
+ }
+ }
+ return traces;
+}
+
+bool Calibration::openFromFile(QString filename)
+{
+ if(filename.isEmpty()) {
+ filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", "", "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog);
+ if(filename.isEmpty()) {
+ // aborted selection
+ return false;
+ }
+ }
+
+ // attempt to load associated calibration kit first (needs to be available when performing calibration)
+ auto calkit_file = filename;
+ auto dotPos = calkit_file.lastIndexOf('.');
+ if(dotPos >= 0) {
+ calkit_file.truncate(dotPos);
+ }
+ calkit_file.append(".calkit");
+ try {
+ kit = Calkit::fromFile(calkit_file.toStdString());
+ } catch (runtime_error e) {
+ QMessageBox::warning(nullptr, "Missing calibration kit", "The calibration kit file associated with the selected calibration could not be parsed. The calibration might not be accurate. (" + QString(e.what()) + ")");
+ }
+
+ ifstream file;
+ file.open(filename.toStdString());
+ try {
+ file >> *this;
+ } catch(runtime_error e) {
+ QMessageBox::warning(nullptr, "File parsing error", e.what());
+ return false;
+ }
+
+ return true;
+}
+
+bool Calibration::saveToFile(QString filename)
+{
+ if(filename.isEmpty()) {
+ filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", "", "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog);
+ if(filename.isEmpty()) {
+ // aborted selection
+ return false;
+ }
+ }
+ // strip any potential file name extension and set default
+ auto dotPos = filename.lastIndexOf('.');
+ if(dotPos >= 0) {
+ filename.truncate(dotPos);
+ }
+ auto calibration_file = filename;
+ calibration_file.append(".cal");
+ ofstream file;
+ file.open(calibration_file.toStdString());
+ file << *this;
+
+ auto calkit_file = filename;
+ calkit_file.append(".calkit");
+ kit.toFile(calkit_file.toStdString());
+
+ return true;
+}
+
+ostream& operator<<(ostream &os, const Calibration &c)
+{
+ for(auto m : c.measurements) {
+ if(m.second.datapoints.size() > 0) {
+ os << c.MeasurementToString(m.first).toStdString() << endl;
+ os << m.second.timestamp.toSecsSinceEpoch() << endl;
+ os << m.second.datapoints.size() << endl;
+ for(auto p : m.second.datapoints) {
+ os << p.pointNum << " " << p.frequency << " ";
+ os << p.imag_S11 << " " << p.real_S11 << " " << p.imag_S21 << " " << p.real_S21 << " " << p.imag_S12 << " " << p.real_S12 << " " << p.imag_S22 << " " << p.real_S22;
+ os << endl;
+ }
+ }
+ }
+ os << Calibration::TypeToString(c.getType()).toStdString() << endl;
+ return os;
+}
+
+istream& operator >>(istream &in, Calibration &c)
+{
+ std::string line;
+ while(getline(in, line)) {
+ for(auto m : Calibration::Measurements()) {
+ if(Calibration::MeasurementToString(m) == QString::fromStdString(line)) {
+ // this is the correct measurement
+ c.clearMeasurement(m);
+ uint timestamp;
+ in >> timestamp;
+ c.measurements[m].timestamp = QDateTime::fromSecsSinceEpoch(timestamp);
+ unsigned int points;
+ in >> points;
+ for(unsigned int i=0;i> p.pointNum >> p.frequency;
+ in >> p.imag_S11 >> p.real_S11 >> p.imag_S21 >> p.real_S21 >> p.imag_S12 >> p.real_S12 >> p.imag_S22 >> p.real_S22;
+ c.measurements[m].datapoints.push_back(p);
+ if(in.eof() || in.bad() || in.fail()) {
+ c.clearMeasurement(m);
+ throw runtime_error("Failed to parse measurement \"" + line + "\", aborting calibration data import.");
+ }
+ }
+ break;
+ }
+ }
+ for(auto t : Calibration::Types()) {
+ if(Calibration::TypeToString(t) == QString::fromStdString(line)) {
+ // try to apply this calibration type
+ if(c.calculationPossible(t)) {
+ c.constructErrorTerms(t);
+ } else {
+ throw runtime_error("Incomplete calibration data, the requested \"" + line + "\"-Calibration could not be performed.");
+ }
+ }
+ break;
+ }
+ }
+ return in;
+}
+
+bool Calibration::SanityCheckSamples(std::vector &requiredMeasurements)
+{
+ // sanity check measurements, all need to be of the same size with the same frequencies (except for isolation which may be empty)
+ vector freqs;
+ for(auto type : requiredMeasurements) {
+ auto m = measurements[type];
+ if(m.datapoints.size() == 0) {
+ // empty required measurement
+ return false;
+ }
+ if(freqs.size() == 0) {
+ // this is the first measurement, create frequency vector
+ for(auto p : m.datapoints) {
+ freqs.push_back(p.frequency);
+ }
+ } else {
+ // compare with already assembled frequency vector
+ if(m.datapoints.size() != freqs.size()) {
+ return false;
+ }
+ for(unsigned int i=0;i= points.back().frequency) {
+ // use last point even for higher frequencies
+ return points.back();
+ }
+ auto p = lower_bound(points.begin(), points.end(), d.frequency, [](Point p, uint64_t freq) -> bool {
+ return p.frequency < freq;
+ });
+ if(p->frequency == d.frequency) {
+ // Exact match, return point
+ return *p;
+ }
+ // need to interpolate
+ auto high = p;
+ p--;
+ auto low = p;
+ double alpha = (d.frequency - low->frequency) / (high->frequency - low->frequency);
+ Point ret;
+ ret.frequency = d.frequency;
+ ret.fe00 = low->fe00 * (1 - alpha) + high->fe00 * alpha;
+ ret.fe11 = low->fe11 * (1 - alpha) + high->fe11 * alpha;
+ ret.fe22 = low->fe22 * (1 - alpha) + high->fe22 * alpha;
+ ret.fe30 = low->fe30 * (1 - alpha) + high->fe30 * alpha;
+ ret.re03 = low->re03 * (1 - alpha) + high->re03 * alpha;
+ ret.re11 = low->re11 * (1 - alpha) + high->re11 * alpha;
+ ret.re22 = low->re22 * (1 - alpha) + high->re22 * alpha;
+ ret.re33 = low->re33 * (1 - alpha) + high->re33 * alpha;
+ ret.fe10e01 = low->fe10e01 * (1 - alpha) + high->fe10e01 * alpha;
+ ret.fe10e32 = low->fe10e32 * (1 - alpha) + high->fe10e32 * alpha;
+ ret.re23e01 = low->re23e01 * (1 - alpha) + high->re23e01 * alpha;
+ ret.re23e32 = low->re23e32 * (1 - alpha) + high->re23e32 * alpha;
+ return ret;
+}
+
+void Calibration::computeSOL(std::complex s_m, std::complex o_m, std::complex l_m,
+ std::complex &directivity, std::complex &match, std::complex &tracking,
+ std::complex o_c, std::complex s_c, std::complex l_c)
+{
+ // equations from page 13 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
+ // solved while taking non ideal o/s/l standards into account
+ auto denom = l_c * o_c * (o_m - l_m) + l_c * s_c * (l_m - s_m) + o_c * s_c * (s_m - o_m);
+ directivity = (l_c * o_m * (s_m * (o_c - s_c) + l_m * s_c) - l_c * o_c * l_m * s_m + o_c * l_m * s_c * (s_m - o_m)) / denom;
+ match = (l_c * (o_m - s_m) + o_c * (s_m - l_m) + s_c * (l_m - o_m)) / denom;
+ auto delta = (l_c * l_m * (o_m - s_m) + o_c * o_m * (s_m - l_m) + s_c * s_m * (l_m - o_m)) / denom;
+ tracking = directivity * match - delta;
+}
+
+std::complex Calibration::correctSOL(std::complex measured, std::complex directivity, std::complex match, std::complex tracking)
+{
+ return (measured - directivity) / (measured * match - directivity * match + tracking);
+}
+
+Calkit &Calibration::getCalibrationKit()
+{
+ return kit;
+}
+
+void Calibration::setCalibrationKit(const Calkit &value)
+{
+ kit = value;
+}
+
+Calibration::Type Calibration::getType() const
+{
+ return type;
+}
+
+
diff --git a/Software/PC_Application/Calibration/calibration.h b/Software/PC_Application/Calibration/calibration.h
new file mode 100644
index 0000000..d329044
--- /dev/null
+++ b/Software/PC_Application/Calibration/calibration.h
@@ -0,0 +1,136 @@
+#ifndef CALIBRATION_H
+#define CALIBRATION_H
+
+#include "Device/device.h"
+#include
+#include
+#include