From 09d5c53bc74f91d161e1e52a9e0c2a3a1f6db289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Fri, 29 Jan 2021 21:44:44 +0100 Subject: [PATCH] Additional de-embedding options: 2xthru and matching network --- Software/PC_Application/Application.pro | 22 +- .../Calibration/calibration.cpp | 92 +-- Software/PC_Application/Tools/parameters.cpp | 43 ++ Software/PC_Application/Tools/parameters.h | 187 ++++++ .../VNA/Deembedding/deembedding.cpp | 93 +++ .../VNA/Deembedding/deembedding.h | 31 + .../VNA/Deembedding/deembeddingdialog.cpp | 107 ++++ .../VNA/Deembedding/deembeddingdialog.h | 49 ++ .../VNA/Deembedding/deembeddingdialog.ui | 154 +++++ .../VNA/Deembedding/deembeddingoption.cpp | 33 ++ .../VNA/Deembedding/deembeddingoption.h | 31 + .../PC_Application/VNA/Deembedding/form.ui | 41 ++ .../VNA/Deembedding/matchingnetwork.cpp | 549 ++++++++++++++++++ .../VNA/Deembedding/matchingnetwork.h | 90 +++ .../VNA/Deembedding/matchingnetworkdialog.ui | 547 +++++++++++++++++ .../VNA/{ => Deembedding}/portextension.cpp | 67 ++- .../VNA/{ => Deembedding}/portextension.h | 11 +- .../portextensioneditdialog.ui | 14 + .../VNA/Deembedding/twothru.cpp | 317 ++++++++++ .../PC_Application/VNA/Deembedding/twothru.h | 39 ++ .../VNA/Deembedding/twothrudialog.ui | 61 ++ Software/PC_Application/VNA/vna.cpp | 19 +- Software/PC_Application/VNA/vna.h | 4 +- Software/PC_Application/icons.qrc | 12 + Software/PC_Application/icons/DUT.png | Bin 0 -> 897 bytes Software/PC_Application/icons/DUT.svg | 37 ++ Software/PC_Application/icons/parallelC.svg | 18 + Software/PC_Application/icons/parallelL.svg | 18 + Software/PC_Application/icons/parallelR.svg | 18 + Software/PC_Application/icons/port1.png | Bin 0 -> 995 bytes Software/PC_Application/icons/port1.svg | 24 + Software/PC_Application/icons/port2.png | Bin 0 -> 1062 bytes Software/PC_Application/icons/port2.svg | 19 + Software/PC_Application/icons/seriesC.svg | 14 + Software/PC_Application/icons/seriesL.svg | 14 + Software/PC_Application/icons/seriesR.svg | 14 + Software/PC_Application/test.setup | 265 +++++++++ 37 files changed, 2954 insertions(+), 100 deletions(-) create mode 100644 Software/PC_Application/Tools/parameters.cpp create mode 100644 Software/PC_Application/Tools/parameters.h create mode 100644 Software/PC_Application/VNA/Deembedding/deembedding.cpp create mode 100644 Software/PC_Application/VNA/Deembedding/deembedding.h create mode 100644 Software/PC_Application/VNA/Deembedding/deembeddingdialog.cpp create mode 100644 Software/PC_Application/VNA/Deembedding/deembeddingdialog.h create mode 100644 Software/PC_Application/VNA/Deembedding/deembeddingdialog.ui create mode 100644 Software/PC_Application/VNA/Deembedding/deembeddingoption.cpp create mode 100644 Software/PC_Application/VNA/Deembedding/deembeddingoption.h create mode 100644 Software/PC_Application/VNA/Deembedding/form.ui create mode 100644 Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp create mode 100644 Software/PC_Application/VNA/Deembedding/matchingnetwork.h create mode 100644 Software/PC_Application/VNA/Deembedding/matchingnetworkdialog.ui rename Software/PC_Application/VNA/{ => Deembedding}/portextension.cpp (89%) rename Software/PC_Application/VNA/{ => Deembedding}/portextension.h (74%) rename Software/PC_Application/VNA/{ => Deembedding}/portextensioneditdialog.ui (96%) create mode 100644 Software/PC_Application/VNA/Deembedding/twothru.cpp create mode 100644 Software/PC_Application/VNA/Deembedding/twothru.h create mode 100644 Software/PC_Application/VNA/Deembedding/twothrudialog.ui create mode 100644 Software/PC_Application/icons/DUT.png create mode 100644 Software/PC_Application/icons/DUT.svg create mode 100644 Software/PC_Application/icons/parallelC.svg create mode 100644 Software/PC_Application/icons/parallelL.svg create mode 100644 Software/PC_Application/icons/parallelR.svg create mode 100644 Software/PC_Application/icons/port1.png create mode 100644 Software/PC_Application/icons/port1.svg create mode 100644 Software/PC_Application/icons/port2.png create mode 100644 Software/PC_Application/icons/port2.svg create mode 100644 Software/PC_Application/icons/seriesC.svg create mode 100644 Software/PC_Application/icons/seriesL.svg create mode 100644 Software/PC_Application/icons/seriesR.svg create mode 100644 Software/PC_Application/test.setup diff --git a/Software/PC_Application/Application.pro b/Software/PC_Application/Application.pro index f5c08e9..ebc52e2 100644 --- a/Software/PC_Application/Application.pro +++ b/Software/PC_Application/Application.pro @@ -24,6 +24,7 @@ HEADERS += \ SpectrumAnalyzer/tracewidgetsa.h \ Tools/eseries.h \ Tools/impedancematchdialog.h \ + Tools/parameters.h \ Traces/Math/dft.h \ Traces/Math/expression.h \ Traces/Math/medianfilter.h \ @@ -96,7 +97,12 @@ HEADERS += \ Traces/xyplotaxisdialog.h \ Util/qpointervariant.h \ Util/util.h \ - VNA/portextension.h \ + VNA/Deembedding/deembedding.h \ + VNA/Deembedding/deembeddingdialog.h \ + VNA/Deembedding/deembeddingoption.h \ + VNA/Deembedding/matchingnetwork.h \ + VNA/Deembedding/portextension.h \ + VNA/Deembedding/twothru.h \ VNA/tracewidgetvna.h \ VNA/vna.h \ appwindow.h \ @@ -135,6 +141,7 @@ SOURCES += \ SpectrumAnalyzer/tracewidgetsa.cpp \ Tools/eseries.cpp \ Tools/impedancematchdialog.cpp \ + Tools/parameters.cpp \ Traces/Math/dft.cpp \ Traces/Math/expression.cpp \ Traces/Math/medianfilter.cpp \ @@ -194,7 +201,12 @@ SOURCES += \ Traces/tracewidget.cpp \ Traces/tracexyplot.cpp \ Traces/xyplotaxisdialog.cpp \ - VNA/portextension.cpp \ + VNA/Deembedding/deembedding.cpp \ + VNA/Deembedding/deembeddingdialog.cpp \ + VNA/Deembedding/deembeddingoption.cpp \ + VNA/Deembedding/matchingnetwork.cpp \ + VNA/Deembedding/portextension.cpp \ + VNA/Deembedding/twothru.cpp \ VNA/tracewidgetvna.cpp \ VNA/vna.cpp \ appwindow.cpp \ @@ -243,7 +255,11 @@ FORMS += \ Traces/tracetouchstoneexport.ui \ Traces/tracewidget.ui \ Traces/xyplotaxisdialog.ui \ - VNA/portextensioneditdialog.ui \ + VNA/Deembedding/deembeddingdialog.ui \ + VNA/Deembedding/form.ui \ + VNA/Deembedding/matchingnetworkdialog.ui \ + VNA/Deembedding/portextensioneditdialog.ui \ + VNA/Deembedding/twothrudialog.ui \ main.ui \ preferencesdialog.ui diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp index 2a9def0..3bde37d 100644 --- a/Software/PC_Application/Calibration/calibration.cpp +++ b/Software/PC_Application/Calibration/calibration.cpp @@ -5,6 +5,7 @@ #include #include "unit.h" #include +#include "Tools/parameters.h" using namespace std; @@ -234,52 +235,6 @@ void Calibration::constructTransmissionNormalization() } } -template -class Tparam { -public: - Tparam(){}; - Tparam(T t11, T t12, T t21, T t22) - : t11(t11), t12(t12), t21(t21), t22(t22){}; - void fromSparam(T S11, T S21, T S12, T S22) { - t11 = -(S11*S22 - S12*S21) / S21; - t12 = S11 / S21; - t21 = -S22 / S21; - t22 = 1.0 / S21; - } - void toSparam(T &S11, T &S21, T &S12, T &S22) { - S11 = t12 / t22; - S21 = T(1) / t22; - S12 = (t11*t22 - t12*t21) / t22; - S22 = -t21 / t22; - } - Tparam inverse() { - Tparam i; - T det = t11*t22 - t12*t21; - i.t11 = t22 / det; - i.t12 = -t12 / det; - i.t21 = -t21 / det; - i.t22 = t11 / det; - return i; - } - Tparam operator*(const Tparam &r) { - Tparam p; - p.t11 = t11*r.t11 + t12*r.t21; - p.t12 = t11*r.t12 + t12*r.t22; - p.t21 = t21*r.t11 + t22*r.t21; - p.t22 = t21*r.t12 + t22*r.t22; - return p; - } - Tparam operator*(const T &r) { - Tparam p; - p.t11 = t11 * r; - p.t12 = t12 * r; - p.t21 = t21 * r; - p.t22 = t22 * r; - return p; - } - T t11, t12, t21, t22; -}; - template void solveQuadratic(T a, T b, T c, T &result1, T &result2) { T root = sqrt(b * b - T(4) * a * c); @@ -317,24 +272,24 @@ void Calibration::constructTRL() // calculate TRL calibration // variable names and formulas according to http://emlab.uiuc.edu/ece451/notes/new_TRL.pdf // page 19 - auto R_T = Tparam>(); - auto R_D = Tparam>(); - R_T.fromSparam(S11_through, S21_through, S12_through, S22_through); - R_D.fromSparam(S11_line, S21_line, S12_line, S22_line); + Sparam Sthrough(S11_through, S12_through, S21_through, S22_through); + Sparam Sline(S11_line, S12_line, S21_line, S22_line); + auto R_T = Tparam(Sthrough); + auto R_D = Tparam(Sline); auto T = R_D*R_T.inverse(); complex a_over_c, b; // page 21-22 - solveQuadratic(T.t21, T.t22 - T.t11, -T.t12, b, a_over_c); + solveQuadratic(T.m21, T.m22 - T.m11, -T.m12, b, a_over_c); // ensure correct root selection // page 23 if(abs(b) >= abs(a_over_c)) { swap(b, a_over_c); } // page 24 - auto g = R_T.t22; - auto d = R_T.t11 / g; - auto e = R_T.t12 / g; - auto f = R_T.t21 / g; + auto g = R_T.m22; + auto d = R_T.m11 / g; + auto e = R_T.m12 / g; + auto f = R_T.m21 / g; // page 25 auto r22_rho22 = g * (1.0 - e / a_over_c) / (1.0 - b / a_over_c); @@ -360,11 +315,15 @@ void Calibration::constructTRL() auto alpha = alpha_a / a; auto beta = beta_over_alpha * alpha; auto c = a / a_over_c; - auto Box_A = Tparam>(r22 * a, r22 * b, r22 * c, r22); - auto Box_B = Tparam>(rho22 * alpha, rho22 * beta, rho22 * gamma, rho22); - complex dummy1, dummy2; - Box_A.toSparam(p.fe00, dummy1, p.fe10e01, p.fe11); - Box_B.toSparam(p.fe22, p.fe10e32, dummy1, dummy2); + auto Box_A = Tparam(r22 * a, r22 * b, r22 * c, r22); + auto Box_B = Tparam(rho22 * alpha, rho22 * beta, rho22 * gamma, rho22); + auto S_A = Sparam(Box_A); + p.fe00 = S_A.m11; + p.fe10e01 = S_A.m12; + p.fe11 = S_A.m22; + auto S_B = Sparam(Box_B); + p.fe22 = S_B.m11; + p.fe10e32 = S_B.m21; // no isolation measurement available p.fe30 = 0.0; @@ -376,10 +335,15 @@ void Calibration::constructTRL() rho22 = 1.0/(alpha - beta * gamma); r22 = r22_rho22 / rho22; - Box_A = Tparam>(r22 * a, r22 * b, r22 * c, r22); - Box_B = Tparam>(rho22 * alpha, rho22 * beta, rho22 * gamma, rho22); - Box_A.toSparam(dummy1, dummy2, p.re23e01, p.re11); - Box_B.toSparam(p.re22, p.re23e32, dummy1, p.re33); + Box_A = Tparam(r22 * a, r22 * b, r22 * c, r22); + Box_B = Tparam(rho22 * alpha, rho22 * beta, rho22 * gamma, rho22); + S_A = Sparam(Box_A); + p.re23e01 = S_A.m12; + p.re11 = S_A.m22; + S_B = Sparam(Box_B); + p.re22 = S_B.m11; + p.re23e32 = S_B.m21; + p.re33 = S_B.m22; // no isolation measurement available p.re03 = 0.0; diff --git a/Software/PC_Application/Tools/parameters.cpp b/Software/PC_Application/Tools/parameters.cpp new file mode 100644 index 0000000..bd9e798 --- /dev/null +++ b/Software/PC_Application/Tools/parameters.cpp @@ -0,0 +1,43 @@ +#include "parameters.h" + +Sparam::Sparam(const Tparam &t) { + m11 = t.m12 / t.m22; + m21 = Type(1) / t.m22; + m12 = (t.m11*t.m22 - t.m12*t.m21) / t.m22; + m22 = -t.m21 / t.m22; +} + +Sparam::Sparam(const ABCDparam &a, Type Z01, Type Z02) { + auto denom = a.m11*Z02+a.m12+a.m21*Z01*Z02+a.m22*Z01; + m11 = (a.m11*Z02+a.m12-a.m21*conj(Z01)*Z02-a.m22*conj(Z01)) / denom; + m12 = (2.0*(a.m11*a.m22-a.m12*a.m21)*sqrt(real(Z01)*real(Z02))) / denom; + m21 = (2.0*sqrt(real(Z01)*real(Z02))) / denom; + m22 = (-a.m11*conj(Z02)+a.m12-a.m21*Z01*conj(Z02)+a.m22*Z01) / denom; +} + +Sparam::Sparam(const ABCDparam &a, Type Z0) + : Sparam(a, Z0, Z0) +{ +} + +ABCDparam::ABCDparam(const Sparam &s, Type Z01, Type Z02) +{ + auto denom = 2.0*s.m21*sqrt(real(Z01)*real(Z02)); + m11 = ((conj(Z01)+s.m11*Z01)*(1.0-s.m22)+s.m12*s.m21*Z01) / denom; + m12 = ((conj(Z01)+s.m11*Z01)*(conj(Z02)+s.m22*Z02)-s.m12*s.m21*Z01*Z02) / denom; + m21 = ((1.0-s.m11)*(1.0-s.m22)-s.m12*s.m21) / denom; + m22 = ((1.0-s.m11)*(conj(Z02)+s.m22*Z02)+s.m12*s.m21*Z02) / denom; +} + +Tparam::Tparam(const Sparam &s) +{ + m11 = -(s.m11*s.m22 - s.m12*s.m21) / s.m21; + m12 = s.m11 / s.m21; + m21 = -s.m22 / s.m21; + m22 = 1.0 / s.m21; +} + +ABCDparam::ABCDparam(const Sparam &s, Type Z0) + : ABCDparam(s, Z0, Z0) +{ +} diff --git a/Software/PC_Application/Tools/parameters.h b/Software/PC_Application/Tools/parameters.h new file mode 100644 index 0000000..4850eb4 --- /dev/null +++ b/Software/PC_Application/Tools/parameters.h @@ -0,0 +1,187 @@ +#ifndef TPARAM_H +#define TPARAM_H + +#include + +using Type = std::complex; + +class Parameters { +public: + Parameters(Type m11, Type m12, Type m21, Type m22) + : m11(m11), m12(m12), m21(m21), m22(m22){}; + Parameters(){}; + + Type m11, m12, m21, m22; +}; + +// forward declaration of parameter classes +class Sparam; +class Tparam; +class ABCDparam; + +class Sparam : public Parameters { +public: + using Parameters::Parameters; + Sparam(const Tparam &t); + Sparam(const ABCDparam &a, Type Z01, Type Z02); + Sparam(const ABCDparam &a, Type Z0); + Sparam operator+(const Sparam &r) { + Sparam p; + p.m11 = this->m11+r.m11; + p.m12 = this->m12+r.m12; + p.m21 = this->m21+r.m21; + p.m22 = this->m22+r.m22; + return p; + } + Sparam operator*(const Type &r) { + Sparam p(m11*r, m12*r, m21*r, m22*r); + return p; + } +}; + +class ABCDparam : public Parameters { +public: + using Parameters::Parameters; + ABCDparam(const Sparam &s, Type Z01, Type Z02); + ABCDparam(const Sparam &s, Type Z0); + ABCDparam operator*(const ABCDparam &r) { + ABCDparam p; + p.m11 = this->m11*r.m11 + this->m12*r.m21; + p.m12 = this->m11*r.m12 + this->m12*r.m22; + p.m21 = this->m21*r.m11 + this->m22*r.m21; + p.m22 = this->m21*r.m12 + this->m22*r.m22; + return p; + } + ABCDparam inverse() { + ABCDparam i; + Type det = m11*m22 - m12*m21; + i.m11 = m22 / det; + i.m12 = -m12 / det; + i.m21 = -m21 / det; + i.m22 = m11 / det; + return i; + } + ABCDparam operator*(const Type &r) { + ABCDparam p(m11*r, m12*r, m21*r, m22*r); + return p; + } + ABCDparam root() { + // calculate root of 2x2 matrix, according to https://en.wikipedia.org/wiki/Square_root_of_a_2_by_2_matrix (choose positive roots) + auto tau = m11 + m22; + auto sigma = m11*m22 - m12*m21; + auto s = sqrt(sigma); + auto t = sqrt(tau + 2.0*s); + ABCDparam r = *this; + r.m11 += s; + r.m22 += s; + r = r * (1.0/t); + return r; + } +}; + +class Tparam : public Parameters { +public: + using Parameters::Parameters; + Tparam(const Sparam &s); + Tparam operator*(const Tparam &r) { + Tparam p; + p.m11 = this->m11*r.m11 + this->m12*r.m21; + p.m12 = this->m11*r.m12 + this->m12*r.m22; + p.m21 = this->m21*r.m11 + this->m22*r.m21; + p.m22 = this->m21*r.m12 + this->m22*r.m22; + return p; + } + Tparam operator+(const Tparam &r) { + Tparam p; + p.m11 = this->m11+r.m11; + p.m12 = this->m12+r.m12; + p.m21 = this->m21+r.m21; + p.m22 = this->m22+r.m22; + return p; + } + Tparam inverse() { + Tparam i; + Type det = m11*m22 - m12*m21; + i.m11 = m22 / det; + i.m12 = -m12 / det; + i.m21 = -m21 / det; + i.m22 = m11 / det; + return i; + } + Tparam operator*(const Type &r) { + Tparam p(m11*r, m12*r, m21*r, m22*r); + return p; + } + Tparam root() { + // calculate root of 2x2 matrix, according to https://en.wikipedia.org/wiki/Square_root_of_a_2_by_2_matrix (choose positive roots) + auto tau = m11 + m22; + auto sigma = m11*m22 - m12*m21; + auto s = sqrt(sigma); + auto t = sqrt(tau + 2.0*s); + Tparam r = *this; + r.m11 += s; + r.m22 += s; + r = r * (1.0/t); + return r; + } +}; + +//template +//class Tparam { +//public: +// Tparam(){}; +// Tparam(T t11, T t12, T t21, T t22) +// : t11(t11), t12(t12), t21(t21), t22(t22){}; +// void fromSparam(T S11, T S21, T S12, T S22) { +// t11 = -(S11*S22 - S12*S21) / S21; +// t12 = S11 / S21; +// t21 = -S22 / S21; +// t22 = 1.0 / S21; +// } +// void toSparam(T &S11, T &S21, T &S12, T &S22) { +// S11 = t12 / t22; +// S21 = T(1) / t22; +// S12 = (t11*t22 - t12*t21) / t22; +// S22 = -t21 / t22; +// } +// Tparam inverse() { +// Tparam i; +// T det = t11*t22 - t12*t21; +// i.t11 = t22 / det; +// i.t12 = -t12 / det; +// i.t21 = -t21 / det; +// i.t22 = t11 / det; +// return i; +// } +// Tparam root() { +// // calculate root of 2x2 matrix, according to https://en.wikipedia.org/wiki/Square_root_of_a_2_by_2_matrix (choose positive roots) +// auto tau = t11 + t22; +// auto sigma = t11*t22 - t12*t21; +// auto s = sqrt(sigma); +// auto t = sqrt(tau + 2.0*s); +// Tparam r = *this; +// r.t11 += s; +// r.t22 += s; +// r = r * (1.0/t); +// return r; +// } +// Tparam operator*(const Tparam &r) { +// Tparam p; +// p.t11 = t11*r.t11 + t12*r.t21; +// p.t12 = t11*r.t12 + t12*r.t22; +// p.t21 = t21*r.t11 + t22*r.t21; +// p.t22 = t21*r.t12 + t22*r.t22; +// return p; +// } +// Tparam operator*(const T &r) { +// Tparam p; +// p.t11 = t11 * r; +// p.t12 = t12 * r; +// p.t21 = t21 * r; +// p.t22 = t22 * r; +// return p; +// } +// T t11, t12, t21, t22; +//}; + +#endif // TPARAM_H diff --git a/Software/PC_Application/VNA/Deembedding/deembedding.cpp b/Software/PC_Application/VNA/Deembedding/deembedding.cpp new file mode 100644 index 0000000..10593f3 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/deembedding.cpp @@ -0,0 +1,93 @@ +#include "deembedding.h" +#include "deembeddingdialog.h" +#include + +using namespace std; + +void Deembedding::configure() +{ + auto d = new DeembeddingDialog(this); + d->show(); +} + +void Deembedding::Deembed(Protocol::Datapoint &d) +{ + for(auto it = options.begin();it != options.end();it++) { + (*it)->transformDatapoint(d); + } +} + +void Deembedding::removeOption(unsigned int index) +{ + if(index < options.size()) { + delete options[index]; + options.erase(options.begin() + index); + } +} + +void Deembedding::addOption(DeembeddingOption *option) +{ + options.push_back(option); + connect(option, &DeembeddingOption::deleted, [=](DeembeddingOption *o){ + // find deleted option and remove from list + auto pos = find(options.begin(), options.end(), o); + if(pos != options.end()) { + options.erase(pos); + } + }); +} + +void Deembedding::swapOptions(unsigned int index) +{ + if(index + 1 >= options.size()) { + return; + } + std::swap(options[index], options[index+1]); +} + +nlohmann::json Deembedding::toJSON() +{ + nlohmann::json list; + for(auto m : options) { + nlohmann::json jm; + jm["operation"] = DeembeddingOption::getName(m->getType()).toStdString(); + jm["settings"] = m->toJSON(); + list.push_back(jm); + } + return list; +} + +void Deembedding::fromJSON(nlohmann::json j) +{ + // clear all options + while(options.size() > 0) { + removeOption(0); + } + for(auto jm : j) { + QString operation = QString::fromStdString(jm.value("operation", "")); + if(operation.isEmpty()) { + qWarning() << "Skipping empty de-embedding operation"; + continue; + } + // attempt to find the type of operation + DeembeddingOption::Type type = DeembeddingOption::Type::Last; + for(unsigned int i=0;i<(int) DeembeddingOption::Type::Last;i++) { + if(DeembeddingOption::getName((DeembeddingOption::Type) i) == operation) { + // found the correct operation + type = (DeembeddingOption::Type) i; + break; + } + } + if(type == DeembeddingOption::Type::Last) { + // unable to find this operation + qWarning() << "Unable to create de-embedding operation:" << operation; + continue; + } + qDebug() << "Creating math operation of type:" << operation; + auto op = DeembeddingOption::create(type); + if(jm.contains("settings")) { + op->fromJSON(jm["settings"]); + } + addOption(op); + } +} diff --git a/Software/PC_Application/VNA/Deembedding/deembedding.h b/Software/PC_Application/VNA/Deembedding/deembedding.h new file mode 100644 index 0000000..ab3c417 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/deembedding.h @@ -0,0 +1,31 @@ +#ifndef DEEMBEDDING_H +#define DEEMBEDDING_H + +#include "deembeddingoption.h" +#include +#include +#include "savable.h" + +class Deembedding : public QObject, public Savable +{ + Q_OBJECT +public: + Deembedding(){}; + ~Deembedding(){}; + + void Deembed(Protocol::Datapoint &d); + + void removeOption(unsigned int index); + void addOption(DeembeddingOption* option); + void swapOptions(unsigned int index); + std::vector& getOptions() {return options;}; + nlohmann::json toJSON() override; + void fromJSON(nlohmann::json j) override; +public slots: + void configure(); + +private: + std::vector options; +}; + +#endif // DEEMBEDDING_H diff --git a/Software/PC_Application/VNA/Deembedding/deembeddingdialog.cpp b/Software/PC_Application/VNA/Deembedding/deembeddingdialog.cpp new file mode 100644 index 0000000..5548f2d --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/deembeddingdialog.cpp @@ -0,0 +1,107 @@ +#include "deembeddingdialog.h" +#include "ui_deembeddingdialog.h" +#include "deembeddingoption.h" +#include + +DeembeddingDialog::DeembeddingDialog(Deembedding *d, QWidget *parent) : + QDialog(parent), + ui(new Ui::DeembeddingDialog), + model(d) +{ + ui->setupUi(this); + ui->view->setModel(&model); + auto addMenu = new QMenu(); + for(unsigned int i=0;i<(unsigned int)DeembeddingOption::Type::Last;i++) { + auto type = (DeembeddingOption::Type) i; + auto action = new QAction(DeembeddingOption::getName(type)); + connect(action, &QAction::triggered, [=](){ + auto option = DeembeddingOption::create(type); + model.addOption(option); + }); + addMenu->addAction(action); + } + ui->bAdd->setMenu(addMenu); + + connect(ui->view->selectionModel(), &QItemSelectionModel::currentRowChanged, [=](const QModelIndex ¤t, const QModelIndex &previous){ + Q_UNUSED(previous) + if(!current.isValid()) { + ui->bDelete->setEnabled(false); + ui->bMoveUp->setEnabled(false); + ui->bMoveDown->setEnabled(false); + ui->bEdit->setEnabled(false); + } else { + ui->bDelete->setEnabled(true); + ui->bMoveUp->setEnabled(current.row() > 0); + ui->bMoveDown->setEnabled(current.row() + 1 < model.rowCount()); + ui->bEdit->setEnabled(true); + } + }); + + connect(ui->bDelete, &QPushButton::clicked, [=](){ + model.deleteRow(ui->view->currentIndex().row()); + }); + connect(ui->bMoveUp, &QPushButton::clicked, [=](){ + auto index = ui->view->currentIndex(); + d->swapOptions(index.row() - 1); + ui->view->setCurrentIndex(index.sibling(index.row() - 1, 0)); + }); + connect(ui->bMoveDown, &QPushButton::clicked, [=](){ + auto index = ui->view->currentIndex(); + d->swapOptions(index.row()); + ui->view->setCurrentIndex(index.sibling(index.row() + 1, 0)); + }); + + connect(ui->view, &QListView::doubleClicked, [=](const QModelIndex &index) { + if(index.isValid()) { + d->getOptions()[index.row()]->edit(); + } + }); + connect(ui->bEdit, &QPushButton::clicked, [=](){ + auto index = ui->view->currentIndex(); + if(index.isValid()) { + d->getOptions()[index.row()]->edit(); + } + }); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); +} + +DeembeddingDialog::~DeembeddingDialog() +{ + delete ui; +} + +OptionModel::OptionModel(Deembedding *d, QObject *parent) + : QAbstractListModel(parent), + d(d){} + +int OptionModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return d->getOptions().size(); +} + +QVariant OptionModel::data(const QModelIndex &index, int role) const +{ + if(index.isValid() && role == Qt::DisplayRole) { + auto type = d->getOptions()[index.row()]->getType(); + return DeembeddingOption::getName(type); + } else { + return QVariant(); + } +} + +void OptionModel::addOption(DeembeddingOption *option) +{ + beginInsertRows(QModelIndex(), d->getOptions().size(), d->getOptions().size()); + d->addOption(option); + endInsertRows(); + // open the editor for the newly added operation + option->edit(); +} + +void OptionModel::deleteRow(unsigned int row) +{ + beginRemoveRows(QModelIndex(), row, row); + d->removeOption(row); + endRemoveRows(); +} diff --git a/Software/PC_Application/VNA/Deembedding/deembeddingdialog.h b/Software/PC_Application/VNA/Deembedding/deembeddingdialog.h new file mode 100644 index 0000000..881abe7 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/deembeddingdialog.h @@ -0,0 +1,49 @@ +#ifndef DEEMBEDDINGDIALOG_H +#define DEEMBEDDINGDIALOG_H + +#include +#include "deembeddingoption.h" +#include "deembedding.h" +#include + +namespace Ui { +class DeembeddingDialog; +} + +class OptionModel : public QAbstractListModel +{ + Q_OBJECT +public: + OptionModel(Deembedding *d, QObject *parent = 0); + + enum { + ColIndexStatus = 0, + ColIndexDescription = 1, + ColIndexDomain = 2, + ColIndexLast, + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + void addOption(DeembeddingOption *option); + void deleteRow(unsigned int row); + +private: + Deembedding *d; +}; + +class DeembeddingDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DeembeddingDialog(Deembedding* d, QWidget *parent = nullptr); + ~DeembeddingDialog(); + +private: + Ui::DeembeddingDialog *ui; + OptionModel model; +}; + +#endif // DEEMBEDDINGDIALOG_H diff --git a/Software/PC_Application/VNA/Deembedding/deembeddingdialog.ui b/Software/PC_Application/VNA/Deembedding/deembeddingdialog.ui new file mode 100644 index 0000000..1d6374e --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/deembeddingdialog.ui @@ -0,0 +1,154 @@ + + + DeembeddingDialog + + + + 0 + 0 + 386 + 324 + + + + De-embedding + + + + + + + + + + + + + + 0 + 0 + + + + Add + + + + + + + :/icons/add.png:/icons/add.png + + + + + + + false + + + + 0 + 0 + + + + Delete + + + + + + + :/icons/remove.png:/icons/remove.png + + + + + + + false + + + Move up + + + + + + + ../../Traces/Math../../Traces/Math + + + + + + + false + + + Move down + + + + + + + ../../Traces/Math../../Traces/Math + + + + + + + false + + + + 0 + 0 + + + + Edit + + + + + + + :/icons/edit.png:/icons/edit.png + + + + + + + Qt::Vertical + + + + 18 + 186 + + + + + + + + + + + + QDialogButtonBox::Ok + + + + + + + + + + diff --git a/Software/PC_Application/VNA/Deembedding/deembeddingoption.cpp b/Software/PC_Application/VNA/Deembedding/deembeddingoption.cpp new file mode 100644 index 0000000..1ec89c4 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/deembeddingoption.cpp @@ -0,0 +1,33 @@ +#include "deembeddingoption.h" + +#include "portextension.h" +#include "twothru.h" +#include "matchingnetwork.h" + +DeembeddingOption *DeembeddingOption::create(DeembeddingOption::Type type) +{ + switch(type) { + case Type::PortExtension: + return new PortExtension(); + case Type::TwoThru: + return new TwoThru(); + case Type::MatchingNetwork: + return new MatchingNetwork(); + default: + return nullptr; + } +} + +QString DeembeddingOption::getName(DeembeddingOption::Type type) +{ + switch(type) { + case Type::PortExtension: + return "Port Extension"; + case Type::TwoThru: + return "2xThru"; + case Type::MatchingNetwork: + return "Matching Network"; + default: + return ""; + } +} diff --git a/Software/PC_Application/VNA/Deembedding/deembeddingoption.h b/Software/PC_Application/VNA/Deembedding/deembeddingoption.h new file mode 100644 index 0000000..7016370 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/deembeddingoption.h @@ -0,0 +1,31 @@ +#ifndef DEEMBEDDINGOPTION_H +#define DEEMBEDDINGOPTION_H + +#include +#include "savable.h" +#include "Device/device.h" + +class DeembeddingOption : public QObject, public Savable +{ + Q_OBJECT +public: + enum class Type { + PortExtension, + TwoThru, + MatchingNetwork, + // Add new deembedding options here, do not explicitly assign values and keep the Last entry at the last position + Last, + }; + + static DeembeddingOption *create(Type type); + static QString getName(Type type); + + virtual void transformDatapoint(Protocol::Datapoint &p) = 0; + virtual void edit(){}; + virtual Type getType() = 0; +signals: + // Deembedding option may selfdestruct if not applicable with current settings. It should emit this signal before deleting itself + void deleted(DeembeddingOption *option); +}; + +#endif // DEEMBEDDING_H diff --git a/Software/PC_Application/VNA/Deembedding/form.ui b/Software/PC_Application/VNA/Deembedding/form.ui new file mode 100644 index 0000000..e116c9a --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/form.ui @@ -0,0 +1,41 @@ + + + Form + + + + 0 + 0 + 339 + 238 + + + + Form + + + QWidget#Form {image: url(:/icons/parallelC.svg);} +border: 1px solid red; + + + + + + + + + Qt::Vertical + + + + 20 + 186 + + + + + + + + + diff --git a/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp b/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp new file mode 100644 index 0000000..ef332c6 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp @@ -0,0 +1,549 @@ +#include "matchingnetwork.h" +#include "ui_matchingnetworkdialog.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +MatchingNetwork::MatchingNetwork() +{ + dropPending = false; + dragComponent = nullptr; + dropComponent = nullptr; + addNetwork = true; +} + +void MatchingNetwork::transformDatapoint(Protocol::Datapoint &p) +{ + auto S = Sparam(complex(p.real_S11, p.imag_S11), + complex(p.real_S12, p.imag_S12), + complex(p.real_S21, p.imag_S21), + complex(p.real_S22, p.imag_S22)); + auto measurement = ABCDparam(S, 50.0); + if(matching.count(p.frequency) == 0) { + // this point is not calculated yet + MatchingPoint m; + // start with identiy matrix + m.p1 = ABCDparam(1.0,0.0,0.0,1.0); + for(auto c : p1Network) { + m.p1 = m.p1 * c->parameters(p.frequency); + } + // same for network at port 2 + m.p2 = ABCDparam(1.0,0.0,0.0,1.0); + for(auto c : p2Network) { + m.p2 = m.p2 * c->parameters(p.frequency); + } + if(!addNetwork) { + // need to remove the effect of the networks, invert matrices + m.p1 = m.p1.inverse(); + m.p2 = m.p2.inverse(); + } + matching[p.frequency] = m; + } + // at this point the map contains the matching network effect + auto m = matching[p.frequency]; + auto corrected = m.p1 * measurement * m.p2; + S = Sparam(corrected, 50.0); + p.real_S11 = real(S.m11); + p.imag_S11 = imag(S.m11); + p.real_S12 = real(S.m12); + p.imag_S12 = imag(S.m12); + p.real_S21 = real(S.m21); + p.imag_S21 = imag(S.m21); + p.real_S22 = real(S.m22); + p.imag_S22 = imag(S.m22); +} + +void MatchingNetwork::edit() +{ + auto dialog = new QDialog(); + auto ui = new Ui::MatchingNetworkDialog(); + ui->setupUi(dialog); + + graph = new QWidget(); + ui->scrollArea->setWidget(graph); + auto layout = new QHBoxLayout(); + graph->setLayout(layout); + graph->setAcceptDrops(true); + graph->setObjectName("Graph"); + graph->installEventFilter(this); + ui->lSeriesC->installEventFilter(this); + ui->lSeriesL->installEventFilter(this); + ui->lSeriesR->installEventFilter(this); + ui->lParallelC->installEventFilter(this); + ui->lParallelL->installEventFilter(this); + ui->lParallelR->installEventFilter(this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(0); + layout->addStretch(1); + auto p1 = new QWidget(); + p1->setMinimumSize(portWidth, 150); + p1->setMaximumSize(portWidth, 150); + p1->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + p1->setStyleSheet("image: url(:/icons/port1.svg);"); + auto DUT = new QWidget(); + DUT->setMinimumSize(DUTWidth, 150); + DUT->setMaximumSize(DUTWidth, 150); + DUT->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + DUT->setStyleSheet("image: url(:/icons/DUT.svg);"); + auto p2 = new QWidget(); + p2->setMinimumSize(portWidth, 150); + p2->setMaximumSize(portWidth, 150); + p2->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + p2->setStyleSheet("image: url(:/icons/port2.svg);"); + layout->addWidget(p1); + for(auto w : p1Network) { + layout->addWidget(w); + connect(w, &MatchingComponent::MatchingComponent::valueChanged, [=](){ + matching.clear(); + }); + } + layout->addWidget(DUT); + for(auto w : p2Network) { + layout->addWidget(w); + connect(w, &MatchingComponent::MatchingComponent::valueChanged, [=](){ + matching.clear(); + }); + } + layout->addWidget(p2); + + layout->addStretch(1); + + dialog->show(); + if(addNetwork) { + ui->bAddNetwork->setChecked(true); + } else { + ui->bRemoveNetwork->setChecked(true); + } + connect(ui->bAddNetwork, &QRadioButton::toggled, [=](bool add) { + addNetwork = add; + // network changed, need to recalculate matching + matching.clear(); + }); + connect(ui->buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); +} + +nlohmann::json MatchingNetwork::toJSON() +{ + nlohmann::json j; + for(int i=0;i<2;i++) { + auto network = i==0 ? p1Network : p2Network; + nlohmann::json jn; + for(auto c : network) { + nlohmann::json jc; + jc["component"] = c->getName().toStdString(); + jc["params"] = c->toJSON(); + jn.push_back(jc); + } + j.push_back(jn); + } + return j; +} + +void MatchingNetwork::fromJSON(nlohmann::json j) +{ + for(int i=0;i<2;i++) { + auto jn = j[i]; + auto &network = i==0 ? p1Network : p2Network; + network.clear(); + for(auto jc : jn) { + if(!jc.contains("component")) { + continue; + } + auto c = MatchingComponent::createFromName(QString::fromStdString(jc["component"])); + if(!c) { + continue; + } + c->fromJSON(jc["params"]); + network.push_back(c); + } + } + matching.clear(); +} + +MatchingComponent *MatchingNetwork::componentAtPosition(int pos) +{ + pos -= graph->layout()->itemAt(0)->geometry().width(); + pos -= portWidth; + if(pos > 0 && pos <= (int) p1Network.size() * componentWidth) { + // position is in port 1 network + return p1Network[pos / componentWidth]; + } else if(pos > (int) p1Network.size() * componentWidth + DUTWidth) { + pos -= (int) p1Network.size() * componentWidth + DUTWidth; + if(pos <= (int) p2Network.size() * componentWidth) { + // position is in port 2 network + return p2Network[pos / componentWidth]; + } + } + return nullptr; +} + +unsigned int MatchingNetwork::findInsertPosition(int xcoord) +{ + xcoord -= graph->layout()->itemAt(0)->geometry().width(); + xcoord -= portWidth; + if(xcoord <= (int) p1Network.size() * componentWidth + DUTWidth/2) { + // added in port 1 network + int index = (xcoord + componentWidth / 2) / componentWidth; + if(index < 0) { + index = 0; + } else if(index > (int) p1Network.size()) { + index = p1Network.size(); + } + // add 2 (first two widgets are always the stretch and port 1 widget) + return index + 2; + } else { + // added in port 2 network + xcoord -= (int) p1Network.size() * componentWidth + DUTWidth; + int index = (xcoord + componentWidth / 2) / componentWidth; + if(index < 0) { + index = 0; + } else if(index > (int) p2Network.size()) { + index = p2Network.size(); + } + // add 3 (same two widgets as in port 1 + DUT) and the size of the port 1 network + return index + 3 + p1Network.size(); + } +} + +void MatchingNetwork::addComponentAtPosition(int pos, MatchingComponent *c) +{ + auto index = findInsertPosition(pos); + QHBoxLayout *l = static_cast(graph->layout()); + l->insertWidget(index, c); + c->show(); + + // add component to correct matching network + index -= 2; // first two widgets are fixed + if(index <= p1Network.size()) { + addComponent(true, index, c); + } else { + index -= 1 + p1Network.size(); + addComponent(false, index, c); + } + + // network changed, need to recalculate matching + matching.clear(); + connect(c, &MatchingComponent::valueChanged, [=](){ + matching.clear(); + }); +} + +void MatchingNetwork::addComponent(bool port1, int index, MatchingComponent *c) +{ + if(port1) { + p1Network.insert(p1Network.begin() + index, c); + // remove from list when the component deletes itself + connect(c, &MatchingComponent::deleted, [=](){ + p1Network.erase(remove(p1Network.begin(), p1Network.end(), c), p1Network.end()); + }); + } else { + // same procedure for port 2 network + p2Network.insert(p2Network.begin() + index, c); + // remove from list when the component deletes itself + connect(c, &MatchingComponent::deleted, [=](){ + p2Network.erase(remove(p2Network.begin(), p2Network.end(), c), p2Network.end()); + }); + } +} + +void MatchingNetwork::createDragComponent(MatchingComponent *c) +{ + QDrag *drag = new QDrag(this); + QMimeData *mimeData = new QMimeData; + + QByteArray encodedPointer; + QDataStream stream(&encodedPointer, QIODevice::WriteOnly); + stream << quintptr(c); + + mimeData->setData("matchingComponent/pointer", encodedPointer); + drag->setMimeData(mimeData); + + drag->exec(Qt::MoveAction); +} + +void MatchingNetwork::updateInsertIndicator(int xcoord) +{ + auto index = findInsertPosition(xcoord); + QHBoxLayout *l = static_cast(graph->layout()); + l->removeWidget(insertIndicator); + l->insertWidget(index, insertIndicator); +} + +bool MatchingNetwork::eventFilter(QObject *object, QEvent *event) +{ + if(object->objectName() == "Graph") { + if(event->type() == QEvent::MouseButtonPress) { + auto mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::LeftButton) { + dragComponent = componentAtPosition(mouseEvent->pos().x()); + if(dragComponent) { + dragStartPosition = mouseEvent->pos(); + return true; + } + } + return false; + } else if(event->type() == QEvent::MouseMove) { + auto mouseEvent = static_cast(event); + if (!(mouseEvent->buttons() & Qt::LeftButton)) { + return false; + } + if (!dragComponent) { + return false; + } + if ((mouseEvent->pos() - dragStartPosition).manhattanLength() + < QApplication::startDragDistance()) { + return false; + } + + // remove and hide component while it is being dragged + graph->layout()->removeWidget(dragComponent); + dragComponent->hide(); + p1Network.erase(remove(p1Network.begin(), p1Network.end(), dragComponent), p1Network.end()); + p2Network.erase(remove(p2Network.begin(), p2Network.end(), dragComponent), p2Network.end()); + graph->update(); + + // network changed, need to recalculate matching + matching.clear(); + + createDragComponent(dragComponent); + return true; + } else if(event->type() == QEvent::DragEnter) { + auto dragEvent = static_cast(event); + if(dragEvent->mimeData()->hasFormat("matchingComponent/pointer")) { + dropPending = true; + auto data = dragEvent->mimeData()->data("matchingComponent/pointer"); + QDataStream stream(&data, QIODevice::ReadOnly); + quintptr dropPtr; + stream >> dropPtr; + dropComponent = (MatchingComponent*) dropPtr; + dragEvent->acceptProposedAction(); + insertIndicator = new QWidget(); + insertIndicator->setMinimumSize(2, 150); + insertIndicator->setMaximumSize(2, 150); + insertIndicator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + insertIndicator->setStyleSheet("background-color:red;"); + updateInsertIndicator(dragEvent->pos().x()); + return true; + } + } else if(event->type() == QEvent::DragMove) { + auto dragEvent = static_cast(event); + updateInsertIndicator(dragEvent->pos().x()); + return true; + } else if(event->type() == QEvent::Drop) { + auto dragEvent = static_cast(event); + delete insertIndicator; + addComponentAtPosition(dragEvent->pos().x(), dropComponent); + return true; + } else if(event->type() == QEvent::DragLeave) { + dropPending = false; + dropComponent = nullptr; + delete insertIndicator; + } + } else { + // clicked/dragged one of the components outside of the graph + if(event->type() == QEvent::MouseButtonPress) { + auto mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::LeftButton) { + dragStartPosition = mouseEvent->pos(); + if(object->objectName() == "lSeriesC") { + dragComponent = new MatchingComponent(MatchingComponent::Type::SeriesC); + } else if(object->objectName() == "lSeriesL") { + dragComponent = new MatchingComponent(MatchingComponent::Type::SeriesL); + } else if(object->objectName() == "lSeriesR") { + dragComponent = new MatchingComponent(MatchingComponent::Type::SeriesR); + } else if(object->objectName() == "lParallelC") { + dragComponent = new MatchingComponent(MatchingComponent::Type::ParallelC); + } else if(object->objectName() == "lParallelL") { + dragComponent = new MatchingComponent(MatchingComponent::Type::ParallelL); + } else if(object->objectName() == "lParallelR") { + dragComponent = new MatchingComponent(MatchingComponent::Type::ParallelR); + } else { + dragComponent = nullptr; + } + return true; + } + return false; + } else if(event->type() == QEvent::MouseMove) { + auto mouseEvent = static_cast(event); + if (!(mouseEvent->buttons() & Qt::LeftButton)) { + return false; + } + if (!dragComponent) { + return false; + } + if ((mouseEvent->pos() - dragStartPosition).manhattanLength() + < QApplication::startDragDistance()) { + return false; + } + + createDragComponent(dragComponent); + return true; + } + } + return false; +} + +MatchingComponent::MatchingComponent(Type type) +{ + this->type = type; + setMinimumSize(150, 150); + setMaximumSize(150, 150); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + eValue = new SIUnitEdit(); + eValue->setPrecision(4); + eValue->setPrefixes("fpnum k"); + connect(eValue, &SIUnitEdit::valueChanged, this, &MatchingComponent::valueChanged); + setFocusPolicy(Qt::FocusPolicy::ClickFocus); + switch(type) { + case Type::SeriesR: + case Type::SeriesL: + case Type::SeriesC: { + auto layout = new QVBoxLayout(); + layout->addWidget(eValue); + setLayout(layout); + } + break; + case Type::ParallelR: + case Type::ParallelL: + case Type::ParallelC: { + auto layout = new QVBoxLayout(); + layout->addWidget(eValue); + layout->addStretch(1); + layout->setContentsMargins(9, 5, 9, 9); + setLayout(layout); + } + break; + default: + break; + } + switch(type) { + case Type::SeriesR: + eValue->setUnit("Ω"); + eValue->setValue(50); + setStyleSheet("image: url(:/icons/seriesR.svg);"); + break; + case Type::SeriesL: + eValue->setUnit("H"); + eValue->setValue(1e-9); + setStyleSheet("image: url(:/icons/seriesL.svg);"); + break; + case Type::SeriesC: + eValue->setUnit("F"); + eValue->setValue(1e-12); + setStyleSheet("image: url(:/icons/seriesC.svg);"); + break; + case Type::ParallelR: + eValue->setUnit("Ω"); + eValue->setValue(50); + setStyleSheet("image: url(:/icons/parallelR.svg);"); + break; + case Type::ParallelL: + eValue->setUnit("H"); + eValue->setValue(1e-9); + setStyleSheet("image: url(:/icons/parallelL.svg);"); + break; + case Type::ParallelC: + eValue->setUnit("F"); + eValue->setValue(1e-12); + setStyleSheet("image: url(:/icons/parallelC.svg);"); + break; + default: + break; + } +} + +ABCDparam MatchingComponent::parameters(double freq) +{ + switch(type) { + case Type::SeriesR: + return ABCDparam(1.0, eValue->value(), 0.0, 1.0); + case Type::SeriesL: + return ABCDparam(1.0, complex(0, freq * 2 * M_PI * eValue->value()), 0.0, 1.0); + case Type::SeriesC: + return ABCDparam(1.0, complex(0, -1.0 / (freq * 2 * M_PI * eValue->value())), 0.0, 1.0); + case Type::ParallelR: + return ABCDparam(1.0, 0.0, 1.0/eValue->value(), 1.0); + case Type::ParallelL: + return ABCDparam(1.0, 0.0, 1.0/complex(0, freq * 2 * M_PI * eValue->value()), 1.0); + case Type::ParallelC: + return ABCDparam(1.0, 0.0, 1.0/complex(0, -1.0 / (freq * 2 * M_PI * eValue->value())), 1.0); + default: + return ABCDparam(1.0, 0.0, 0.0, 1.0); + } +} + +void MatchingComponent::MatchingComponent::setValue(double v) +{ + eValue->setValue(v); +} + +MatchingComponent *MatchingComponent::createFromName(QString name) +{ + for(unsigned int i=0;i<(int) Type::Last;i++) { + if(name == typeToName((Type) i)) { + return new MatchingComponent((Type) i); + } + } + // invalid name + return nullptr; +} + +QString MatchingComponent::getName() +{ + return typeToName(type); +} + +nlohmann::json MatchingComponent::toJSON() +{ + nlohmann::json j; + j["value"] = eValue->value(); + return j; +} + +void MatchingComponent::fromJSON(nlohmann::json j) +{ + eValue->setValue(j.value("value", 1e-12)); +} + +QString MatchingComponent::typeToName(MatchingComponent::Type type) +{ + switch(type) { + case Type::SeriesR: return "SeriesR"; + case Type::SeriesL: return "SeriesL"; + case Type::SeriesC: return "SeriesC"; + case Type::ParallelR: return "ParallelR"; + case Type::ParallelL: return "ParallelL"; + case Type::ParallelC: return "ParallelC"; + default: return ""; + } +} + +void MatchingComponent::MatchingComponent::keyPressEvent(QKeyEvent *event) +{ + if(event->key() == Qt::Key_Delete) { + emit deleted(this); + delete this; + } +} + +void MatchingComponent::MatchingComponent::focusInEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + oldStylesheet = styleSheet(); + setStyleSheet(styleSheet().append("\nborder: 1px solid red;")); +} + +void MatchingComponent::MatchingComponent::focusOutEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + setStyleSheet(oldStylesheet); +} diff --git a/Software/PC_Application/VNA/Deembedding/matchingnetwork.h b/Software/PC_Application/VNA/Deembedding/matchingnetwork.h new file mode 100644 index 0000000..8f2bbb8 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/matchingnetwork.h @@ -0,0 +1,90 @@ +#ifndef MATCHINGNETWORK_H +#define MATCHINGNETWORK_H + +#include +#include +#include "deembeddingoption.h" +#include +#include "Tools/parameters.h" +#include "savable.h" + +class MatchingComponent : public QFrame, public Savable +{ + Q_OBJECT +public: + enum class Type { + SeriesR, + SeriesL, + SeriesC, + ParallelR, + ParallelL, + ParallelC, + // Add new matching components here, do not explicitly assign values and keep the Last entry at the last position + Last, + }; + + MatchingComponent(Type type); + ABCDparam parameters(double freq); + void setValue(double v); + + static MatchingComponent* createFromName(QString name); + QString getName(); + + nlohmann::json toJSON() override; + void fromJSON(nlohmann::json j) override; + +signals: + void valueChanged(); + void deleted(MatchingComponent* m); +protected: + SIUnitEdit *eValue; +private: + static QString typeToName(Type type); + Type type; + void keyPressEvent(QKeyEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + QString oldStylesheet; +}; + +class MatchingNetwork : public DeembeddingOption +{ +public: + MatchingNetwork(); + + // DeembeddingOption interface +public: + void transformDatapoint(Protocol::Datapoint &p) override; + void edit() override; + Type getType() override {return Type::MatchingNetwork;}; + nlohmann::json toJSON() override; + void fromJSON(nlohmann::json j) override; +private: + static constexpr int componentWidth = 150; + static constexpr int DUTWidth = 150; + static constexpr int portWidth = 75; + MatchingComponent *componentAtPosition(int pos); + unsigned int findInsertPosition(int xcoord); + void addComponentAtPosition(int pos, MatchingComponent *c); + void addComponent(bool port1, int index, MatchingComponent *c); + void createDragComponent(MatchingComponent *c); + void updateInsertIndicator(int xcoord); + bool eventFilter(QObject *object, QEvent *event) override; + std::vector p1Network, p2Network; + + QWidget *graph, *insertIndicator; + QPoint dragStartPosition; + MatchingComponent *dragComponent; + bool dropPending; + MatchingComponent *dropComponent; + + class MatchingPoint { + public: + ABCDparam p1, p2; + }; + std::map matching; + + bool addNetwork; +}; + +#endif // MATCHINGNETWORK_H diff --git a/Software/PC_Application/VNA/Deembedding/matchingnetworkdialog.ui b/Software/PC_Application/VNA/Deembedding/matchingnetworkdialog.ui new file mode 100644 index 0000000..2a52e6f --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/matchingnetworkdialog.ui @@ -0,0 +1,547 @@ + + + MatchingNetworkDialog + + + + 0 + 0 + 772 + 442 + + + + Matching Network + + + false + + + QWidget#Form {image: url(:/icons/tex/parallelL.svg);} + + + + + + + + + + 0 + 170 + + + + + 16777215 + 170 + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 750 + 168 + + + + + + + + + + + Operation + + + + + + Add effect of matching network + + + true + + + buttonGroup + + + + + + + + 8 + + + + Choose this option if you are directly connected to the DUT and would like to see the parameters with the specified matching network + + + true + + + + + + + Qt::Horizontal + + + + + + + Remove effect of matching network + + + buttonGroup + + + + + + + + 8 + + + + Choose this option if the matching network is physically present but you would like to see the parameters of just the DUT + + + true + + + + + + + + + + Matching Components + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 8 + + + + Drag/drop into signal path + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/seriesC.svg); + + + QFrame::NoFrame + + + QFrame::Plain + + + Series C + + + Qt::AlignCenter + + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/seriesL.svg); + + + QFrame::NoFrame + + + QFrame::Plain + + + Series L + + + Qt::AlignCenter + + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/seriesR.svg); + + + QFrame::NoFrame + + + QFrame::Plain + + + Series R + + + Qt::AlignCenter + + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/parallelC.svg); + + + QFrame::NoFrame + + + QFrame::Plain + + + Parallel C + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + border-image: url(:/icons/parallelL.svg); + + + QFrame::NoFrame + + + QFrame::Plain + + + Parallel L + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/parallelR.svg); + + + QFrame::NoFrame + + + QFrame::Plain + + + Parallel R + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + + + + + + + + + + QDialogButtonBox::Ok + + + + + + + + + + + + + diff --git a/Software/PC_Application/VNA/portextension.cpp b/Software/PC_Application/VNA/Deembedding/portextension.cpp similarity index 89% rename from Software/PC_Application/VNA/portextension.cpp rename to Software/PC_Application/VNA/Deembedding/portextension.cpp index d358c97..b4daf1b 100644 --- a/Software/PC_Application/VNA/portextension.cpp +++ b/Software/PC_Application/VNA/Deembedding/portextension.cpp @@ -7,7 +7,7 @@ using namespace std; PortExtension::PortExtension() - : QObject() + : DeembeddingOption() { port1.enabled = false; port1.frequency = 0; @@ -26,7 +26,7 @@ PortExtension::PortExtension() kit = nullptr; } -void PortExtension::applyToMeasurement(Protocol::Datapoint &d) +void PortExtension::transformDatapoint(Protocol::Datapoint &d) { if(measuring) { if(measurements.size() > 0) { @@ -180,6 +180,7 @@ void PortExtension::edit() ui->setupUi(dialog); // set initial values + ui->P1Enabled->setChecked(port1.enabled); ui->P1Time->setUnit("s"); ui->P1Time->setPrefixes("pnum "); ui->P1Distance->setUnit("m"); @@ -198,6 +199,7 @@ void PortExtension::edit() ui->P1calkit->setEnabled(false); } + ui->P2Enabled->setChecked(port2.enabled); ui->P2Time->setUnit("s"); ui->P2Time->setPrefixes("pnum "); ui->P2Distance->setUnit("m"); @@ -229,6 +231,9 @@ void PortExtension::edit() port2.frequency = ui->P2Frequency->value(); }; + connect(ui->P1Enabled, &QCheckBox::toggled, [=](bool enabled) { + port1.enabled = enabled; + }); // connections to link delay and distance connect(ui->P1Time, &SIUnitEdit::valueChanged, [=](double newval) { ui->P1Distance->setValueQuiet(newval * ui->P1Velocity->value() * c); @@ -258,6 +263,9 @@ void PortExtension::edit() startMeasurement(); }); + connect(ui->P2Enabled, &QCheckBox::toggled, [=](bool enabled) { + port2.enabled = enabled; + }); connect(ui->P2Time, &SIUnitEdit::valueChanged, [=](double newval) { ui->P2Distance->setValueQuiet(newval * ui->P2Velocity->value() * c); updateValuesFromUI(); @@ -303,26 +311,43 @@ void PortExtension::startMeasurement() measuring = true; } -QToolBar *PortExtension::createToolbar() -{ - auto tb = new QToolBar("Port Extension"); - auto editButton = new QPushButton("Port Extension"); - auto p1enable = new QCheckBox("Port 1"); - auto p2enable = new QCheckBox("Port 2"); - connect(p1enable, &QCheckBox::clicked, [=]() { - port1.enabled = p1enable->isChecked(); - }); - connect(p2enable, &QCheckBox::clicked, [=]() { - port2.enabled = p2enable->isChecked(); - }); - connect(editButton, &QPushButton::pressed, this, &PortExtension::edit); - tb->addWidget(editButton); - tb->addWidget(p1enable); - tb->addWidget(p2enable); - return tb; -} - void PortExtension::setCalkit(Calkit *kit) { this->kit = kit; } + +nlohmann::json PortExtension::toJSON() +{ + nlohmann::json j; + for(int i=0;i<2;i++) { + auto ext = i == 0 ? port1 : port2; + nlohmann::json je; + je["enabled"] = ext.enabled; + je["delay"] = ext.delay; + je["velocityFactor"] = ext.velocityFactor; + je["DCloss"] = ext.DCloss; + je["loss"] = ext.loss; + je["frequency"] = ext.frequency; + j.push_back(je); + } + return j; +} + +void PortExtension::fromJSON(nlohmann::json j) +{ + for(int i=0;i<2;i++) { + Extension ext; + nlohmann::json je = j[i]; + ext.enabled = je.value("enabled", false); + ext.delay = je.value("delay", 0.0); + ext.velocityFactor = je.value("velocityFactor", 0.66); + ext.DCloss = je.value("DCloss", 0.0); + ext.loss = je.value("loss", 0.0); + ext.frequency = je.value("frequency", 6000000000); + if(i==0) { + port1 = ext; + } else { + port2 = ext; + } + } +} diff --git a/Software/PC_Application/VNA/portextension.h b/Software/PC_Application/VNA/Deembedding/portextension.h similarity index 74% rename from Software/PC_Application/VNA/portextension.h rename to Software/PC_Application/VNA/Deembedding/portextension.h index 1045afd..a474064 100644 --- a/Software/PC_Application/VNA/portextension.h +++ b/Software/PC_Application/VNA/Deembedding/portextension.h @@ -6,21 +6,24 @@ #include #include "Calibration/calkit.h" #include +#include "deembeddingoption.h" namespace Ui { class PortExtensionEditDialog; } -class PortExtension : public QObject +class PortExtension : public DeembeddingOption { Q_OBJECT public: PortExtension(); - void applyToMeasurement(Protocol::Datapoint& d); - QToolBar *createToolbar(); + void transformDatapoint(Protocol::Datapoint& d) override; void setCalkit(Calkit *kit); + Type getType() override {return Type::PortExtension;} + nlohmann::json toJSON() override; + void fromJSON(nlohmann::json j) override; public slots: - void edit(); + void edit() override; private: void startMeasurement(); diff --git a/Software/PC_Application/VNA/portextensioneditdialog.ui b/Software/PC_Application/VNA/Deembedding/portextensioneditdialog.ui similarity index 96% rename from Software/PC_Application/VNA/portextensioneditdialog.ui rename to Software/PC_Application/VNA/Deembedding/portextensioneditdialog.ui index f63dba1..f35e06c 100644 --- a/Software/PC_Application/VNA/portextensioneditdialog.ui +++ b/Software/PC_Application/VNA/Deembedding/portextensioneditdialog.ui @@ -27,6 +27,13 @@ Port 1 + + + + Enabled + + + @@ -170,6 +177,13 @@ Port 2 + + + + Enabled + + + diff --git a/Software/PC_Application/VNA/Deembedding/twothru.cpp b/Software/PC_Application/VNA/Deembedding/twothru.cpp new file mode 100644 index 0000000..7bd510c --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/twothru.cpp @@ -0,0 +1,317 @@ +#include "twothru.h" +#include "CustomWidgets/informationbox.h" +#include "ui_twothrudialog.h" +#include "Traces/fftcomplex.h" + +using namespace std; + +TwoThru::TwoThru() +{ + measuring = false; +} + +void TwoThru::transformDatapoint(Protocol::Datapoint &p) +{ + auto S11 = complex(p.real_S11, p.imag_S11); + auto S12 = complex(p.real_S12, p.imag_S12); + auto S21 = complex(p.real_S21, p.imag_S21); + auto S22 = complex(p.real_S22, p.imag_S22); + Sparam S(S11, S12, S21, S22); + Tparam meas(S); + if(measuring) { + if(measurements.size() > 0 && p.pointNum == 0) { + // complete sweep measured, exit measurement mode + measuring = false; + // calculate error boxes, see https://www.freelists.org/post/si-list/IEEE-P370-Opensource-Deembedding-MATLAB-functions + // create vectors of S parameters + vector> S11, S12, S21, S22; + vector f; + for(auto m : measurements) { + if(m.frequency == 0) { + // ignore possible DC point + continue; + } + S11.push_back(complex(m.real_S11, m.imag_S11)); + S12.push_back(complex(m.real_S12, m.imag_S12)); + S21.push_back(complex(m.real_S21, m.imag_S21)); + S22.push_back(complex(m.real_S22, m.imag_S22)); + f.push_back(m.frequency); + } + auto n = f.size(); + + auto makeSymmetric = [](const vector> &in) -> vector> { + auto abs_DC = 2.0 * abs(in[0]) - abs(in[1]); + auto phase_DC = 2.0 * arg(in[0]) - arg(in[1]); + auto DC = polar(abs_DC, phase_DC); + vector> ret; + ret.push_back(DC); + // add non-symmetric part + ret.insert(ret.end(), in.begin(), in.end()); + // add flipped complex conjugate values + for(auto it = in.rbegin(); it != in.rend(); it++) { + ret.push_back(conj(*it)); + } + return ret; + }; + + auto makeRealAndScale = [](vector> &in) { + for(unsigned int i=0;i data_side1, data_side2; + + { + auto p112x = makeSymmetric(S11); + auto p212x = makeSymmetric(S21); + + // transform into time domain and calculate step responses + auto t112x = p112x; + Fft::transform(t112x, true); + makeRealAndScale(t112x); + Fft::shift(t112x, false); + partial_sum(t112x.begin(), t112x.end(), t112x.begin()); + auto t212x = p212x; + Fft::transform(t212x, true); + makeRealAndScale(t212x); + Fft::shift(t212x, false); + partial_sum(t212x.begin(), t212x.end(), t212x.begin()); + + // find the midpoint of the trace + auto mid = lower_bound(t212x.begin(), t212x.end(), 0.5, [](complex p, double c) -> bool { + return real(p) < c; + }) - t212x.begin(); + + // mask step response + vector> t111xStep(2*n + 1, 0.0); + copy(t112x.begin() + n, t112x.begin() + mid, t111xStep.begin() + n); + Fft::shift(t111xStep, true); + // create impulse response from masked step response + adjacent_difference(t111xStep.begin(), t111xStep.end(), t111xStep.begin()); + Fft::transform(t111xStep, false); + auto &p111x = t111xStep; + + // calculate p221x and p211x + vector> p221x; + vector> p211x; + double k = 1.0; + complex test, last_test; + for(unsigned int i=0;i 0) { + if(arg(test) - arg(last_test) > 0) { + k = -k; + } + } + last_test = test; + p211x.push_back(k*test); + } + + // create S parameter errorbox + for(unsigned int i=1;i<=n;i++) { + data_side1.push_back(Sparam(p111x[i], p211x[i], p211x[i], p221x[i])); + } + } + + // same thing for error box 2. Variable names get a bit confusing because they are viewed from port 2 (S22 is now called p112x, ...). + // All variable names follow https://gitlab.com/IEEE-SA/ElecChar/P370/-/blob/master/TG1/IEEEP3702xThru_Octave.m + { + auto p112x = makeSymmetric(S22); + auto p212x = makeSymmetric(S12); + + // transform into time domain and calculate step responses + auto t112x = p112x; + Fft::transform(t112x, true); + makeRealAndScale(t112x); + Fft::shift(t112x, false); + partial_sum(t112x.begin(), t112x.end(), t112x.begin()); + auto t212x = p212x; + Fft::transform(t212x, true); + makeRealAndScale(t212x); + Fft::shift(t212x, false); + partial_sum(t212x.begin(), t212x.end(), t212x.begin()); + + // find the midpoint of the trace + auto mid = lower_bound(t212x.begin(), t212x.end(), 0.5, [](complex p, double c) -> bool { + return real(p) < c; + }) - t212x.begin(); + + // mask step response + vector> t111xStep(2*n + 1, 0.0); + copy(t112x.begin() + n, t112x.begin() + mid, t111xStep.begin() + n); + Fft::shift(t111xStep, true); + // create impulse response from masked step response + adjacent_difference(t111xStep.begin(), t111xStep.end(), t111xStep.begin()); + Fft::transform(t111xStep, false); + auto &p111x = t111xStep; + + // calculate p221x and p211x + vector> p221x; + vector> p211x; + double k = 1.0; + complex test, last_test; + for(unsigned int i=0;i 0) { + if(arg(test) - arg(last_test) > 0) { + k = -k; + } + } + last_test = test; + p211x.push_back(k*test); + } + + // create S parameter errorbox + for(unsigned int i=1;i<=n;i++) { + data_side2.push_back(Sparam(p111x[i], p211x[i], p211x[i], data_side1[i-1].m22)); + data_side1[i-1].m22 = p221x[i]; + } + } + + // got the error boxes, convert to T parameters and invert + for(unsigned int i=0;iaccept(); + msgBox = nullptr; + } + updateLabel(); + } else if(measurements.size() > 0 || p.pointNum == 0) { + measurements.push_back(p); + } + } + + // correct measurement + if(points.size() > 0) { + if(p.frequency != 0 && (p.frequency < points.front().freq || p.frequency > points.back().freq)) { + // No exact match, measurement no longer valid + points.clear(); + InformationBox::ShowMessage("Warning", "2xThru measurement cleared because it no longer matches the selected span"); + return; + } + // find correct measurement point + auto point = lower_bound(points.begin(), points.end(), p.frequency, [](Point p, uint64_t freq) -> bool { + return p.freq < freq; + }); + Tparam inv1, inv2; + if(point->freq == p.frequency) { + inv1 = point->inverseP1; + inv2 = point->inverseP2; + } else { + // need to interpolate + auto high = point; + point--; + auto low = point; + double alpha = (p.frequency - low->freq) / (high->freq - low->freq); + inv1 = low->inverseP1 * (1 - alpha) + high->inverseP1 * alpha; + inv2 = low->inverseP2 * (1 - alpha) + high->inverseP2 * alpha; + } + // perform correction + Tparam corrected = inv1*meas*inv2; + // transform back into S parameters + Sparam S(corrected); + p.real_S11 = real(S.m11); + p.imag_S11 = imag(S.m11); + p.real_S12 = real(S.m12); + p.imag_S12 = imag(S.m12); + p.real_S21 = real(S.m21); + p.imag_S21 = imag(S.m21); + p.real_S22 = real(S.m22); + p.imag_S22 = imag(S.m22); + } +} + +void TwoThru::startMeasurement() +{ + points.clear(); + measurements.clear(); + updateLabel(); + msgBox = new QMessageBox(QMessageBox::Information, "2xThru", "Taking measurement...", QMessageBox::Cancel); + connect(msgBox, &QMessageBox::rejected, [=]() { + measuring = false; + points.clear(); + measurements.clear(); + updateLabel(); + }); + msgBox->show(); + measuring = true; +} + +void TwoThru::updateLabel() +{ + if(points.size() > 0) { + ui->lInfo->setText("Got "+QString::number(points.size())+" points"); + } else { + ui->lInfo->setText("No measurement, not deembedding"); + } +} + +void TwoThru::edit() +{ + auto dialog = new QDialog(); + ui = new Ui::TwoThruDialog(); + ui->setupUi(dialog); + + connect(ui->bMeasure, &QPushButton::clicked, this, &TwoThru::startMeasurement); + connect(ui->buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); + + updateLabel(); + + dialog->show(); +} + +nlohmann::json TwoThru::toJSON() +{ + nlohmann::json j; + for(auto p : points) { + nlohmann::json jp; + jp["frequency"] = p.freq; + jp["p1_11_r"] = p.inverseP1.m11.real(); + jp["p1_11_i"] = p.inverseP1.m11.imag(); + jp["p1_12_r"] = p.inverseP1.m12.real(); + jp["p1_12_i"] = p.inverseP1.m12.imag(); + jp["p1_21_r"] = p.inverseP1.m21.real(); + jp["p1_21_i"] = p.inverseP1.m21.imag(); + jp["p1_22_r"] = p.inverseP1.m22.real(); + jp["p1_22_i"] = p.inverseP1.m22.imag(); + jp["p2_11_r"] = p.inverseP2.m11.real(); + jp["p2_11_i"] = p.inverseP2.m11.imag(); + jp["p2_12_r"] = p.inverseP2.m12.real(); + jp["p2_12_i"] = p.inverseP2.m12.imag(); + jp["p2_21_r"] = p.inverseP2.m21.real(); + jp["p2_21_i"] = p.inverseP2.m21.imag(); + jp["p2_22_r"] = p.inverseP2.m22.real(); + jp["p2_22_i"] = p.inverseP2.m22.imag(); + j.push_back(jp); + } + return j; +} + +void TwoThru::fromJSON(nlohmann::json j) +{ + points.clear(); + for(auto jp : j) { + Point p; + p.freq = jp.value("frequency", 0.0); + p.inverseP1.m11 = complex(jp.value("p1_11_r", 0.0), jp.value("p1_11_i", 0.0)); + p.inverseP1.m12 = complex(jp.value("p1_12_r", 0.0), jp.value("p1_12_i", 0.0)); + p.inverseP1.m21 = complex(jp.value("p1_21_r", 0.0), jp.value("p1_21_i", 0.0)); + p.inverseP1.m22 = complex(jp.value("p1_22_r", 0.0), jp.value("p1_22_i", 0.0)); + p.inverseP2.m11 = complex(jp.value("p2_11_r", 0.0), jp.value("p2_11_i", 0.0)); + p.inverseP2.m12 = complex(jp.value("p2_12_r", 0.0), jp.value("p2_12_i", 0.0)); + p.inverseP2.m21 = complex(jp.value("p2_21_r", 0.0), jp.value("p2_21_i", 0.0)); + p.inverseP2.m22 = complex(jp.value("p2_22_r", 0.0), jp.value("p2_22_i", 0.0)); + points.push_back(p); + } +} diff --git a/Software/PC_Application/VNA/Deembedding/twothru.h b/Software/PC_Application/VNA/Deembedding/twothru.h new file mode 100644 index 0000000..b1f2c70 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/twothru.h @@ -0,0 +1,39 @@ +#ifndef TWOTHRU_H +#define TWOTHRU_H + +#include "deembeddingoption.h" +#include "Tools/parameters.h" +#include +#include + +namespace Ui { +class TwoThruDialog; +} + +class TwoThru : public DeembeddingOption +{ +public: + TwoThru(); + + virtual void transformDatapoint(Protocol::Datapoint &p) override; + virtual void edit() override; + virtual Type getType() override {return DeembeddingOption::Type::TwoThru;} + nlohmann::json toJSON() override; + void fromJSON(nlohmann::json j) override; + +private slots: + void startMeasurement(); + void updateLabel(); +private: + using Point = struct { + double freq; + Tparam inverseP1, inverseP2; + }; + std::vector measurements; + std::vector points; + bool measuring; + QMessageBox *msgBox; + Ui::TwoThruDialog *ui; +}; + +#endif // TWOTHRU_H diff --git a/Software/PC_Application/VNA/Deembedding/twothrudialog.ui b/Software/PC_Application/VNA/Deembedding/twothrudialog.ui new file mode 100644 index 0000000..b667119 --- /dev/null +++ b/Software/PC_Application/VNA/Deembedding/twothrudialog.ui @@ -0,0 +1,61 @@ + + + TwoThruDialog + + + Qt::NonModal + + + + 0 + 0 + 233 + 103 + + + + 2xThru + + + true + + + + + + + + + + + + + Measure + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QDialogButtonBox::Ok + + + + + + + + diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index 146d2d5..94d04da 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -147,7 +147,7 @@ VNA::VNA(AppWindow *window) import->show(); }); - portExtension.setCalkit(&cal.getCalibrationKit()); +// portExtension.setCalkit(&cal.getCalibrationKit()); // Tools menu auto toolsMenu = new QMenu("Tools", window); @@ -155,6 +155,8 @@ VNA::VNA(AppWindow *window) actions.insert(toolsMenu->menuAction()); auto impedanceMatching = toolsMenu->addAction("Impedance Matching"); connect(impedanceMatching, &QAction::triggered, this, &VNA::StartImpedanceMatching); + auto confDeembed = toolsMenu->addAction("De-embedding"); + connect(confDeembed, &QAction::triggered, &deembedding, &Deembedding::configure); defaultCalMenu = new QMenu("Default Calibration", window); assignDefaultCal = defaultCalMenu->addAction("Assign..."); @@ -367,9 +369,9 @@ VNA::VNA(AppWindow *window) window->addToolBar(tb_cal); toolbars.insert(tb_cal); - auto tb_portExtension = portExtension.createToolbar(); - window->addToolBar(tb_portExtension); - toolbars.insert(tb_portExtension); +// auto tb_portExtension = portExtension.createToolbar(); +// window->addToolBar(tb_portExtension); +// toolbars.insert(tb_portExtension); markerModel = new TraceMarkerModel(traceModel, this); @@ -490,7 +492,7 @@ void VNA::initializeDevice() if(QFile::exists(filename)) { if(cal.openFromFile(filename)) { ApplyCalibration(cal.getType()); - portExtension.setCalkit(&cal.getCalibrationKit()); +// portExtension.setCalkit(&cal.getCalibrationKit()); qDebug() << "Calibration successful from " << filename; } else { qDebug() << "Calibration not successfull from: " << filename; @@ -528,6 +530,7 @@ nlohmann::json VNA::toJSON() j["traces"] = traceModel.toJSON(); j["tiles"] = central->toJSON(); j["markers"] = markerModel->toJSON(); + j["de-embedding"] = deembedding.toJSON(); return j; } @@ -542,6 +545,9 @@ void VNA::fromJSON(nlohmann::json j) if(j.contains("markers")) { markerModel->fromJSON(j["markers"]); } + if(j.contains("de-embedding")) { + deembedding.fromJSON(j["de-embedding"]); + } } using namespace std; @@ -568,7 +574,8 @@ void VNA::NewDatapoint(Protocol::Datapoint d) if(calValid) { cal.correctMeasurement(d); } - portExtension.applyToMeasurement(d); + + deembedding.Deembed(d); traceModel.addVNAData(d, settings); emit dataChanged(); diff --git a/Software/PC_Application/VNA/vna.h b/Software/PC_Application/VNA/vna.h index e5d67ec..c3a9780 100644 --- a/Software/PC_Application/VNA/vna.h +++ b/Software/PC_Application/VNA/vna.h @@ -8,7 +8,7 @@ #include "CustomWidgets/tilewidget.h" #include "Device/device.h" #include -#include "portextension.h" +#include "Deembedding/deembedding.h" class VNA : public Mode { @@ -81,7 +81,7 @@ private: QAction *assignDefaultCal, *removeDefaultCal; QAction *saveCal; - PortExtension portExtension; + Deembedding deembedding; // Status Labels QLabel *lAverages; diff --git a/Software/PC_Application/icons.qrc b/Software/PC_Application/icons.qrc index e6064ca..2e6e1eb 100644 --- a/Software/PC_Application/icons.qrc +++ b/Software/PC_Application/icons.qrc @@ -35,5 +35,17 @@ icons/zoom-out.png icons/math_disabled.svg icons/math_enabled.svg + icons/parallelC.svg + icons/parallelL.svg + icons/parallelR.svg + icons/seriesC.svg + icons/seriesL.svg + icons/seriesR.svg + icons/DUT.png + icons/port1.png + icons/port2.png + icons/DUT.svg + icons/port1.svg + icons/port2.svg diff --git a/Software/PC_Application/icons/DUT.png b/Software/PC_Application/icons/DUT.png new file mode 100644 index 0000000000000000000000000000000000000000..359f9aebf1a96e0fadbfc4c6592bfd794f683032 GIT binary patch literal 897 zcmeAS@N?(olHy`uVBq!ia0vp^(?OVn2}J&l(*shR1s;*b3=Dj8K$wwzO7L9<24-VV z7srr_TW{}LduwMhG$h6*2kdExn0fBdDek7Ji^R4~d$iO;^G3vSVP`JI)>eCmZjU4P z!lWHtJQO21oE&=9l%%*f2A$Aka=c)B`rIz}$u{|>CsneZ6{hV=O!G)y8Ilz+_4{;J zhd>e6KoQNZMH*d7K!$7J5)BZ$G(_XZZ7FGK-QD{7A4qe*pS@i0A>#C3a z`}XYdSsL`Qq9!vlGa=!@&!0O3G+T=U=#fJ$?U= zzphFXJybUB-~a#jbX^^tnqM`Q**+hCzkc;<*Nz=O+8Z5Sd|v1f94vfP{^H@o&iVQ8 z@4x*gn;GJGH0k5JcX?S^t2Q;(*3_&yqbtHSckWz~`bRH*{XIN&>eL9Gut@Rrv@}0I zKdoYZe*WXfkK4=6w~qTWZ{7O!+qZ9@KX2Z)ZQG1y`fQ9i^ZfJT#fvXyZ22_n>eZ`H zpFS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/parallelC.svg b/Software/PC_Application/icons/parallelC.svg new file mode 100644 index 0000000..fe84c9e --- /dev/null +++ b/Software/PC_Application/icons/parallelC.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/parallelL.svg b/Software/PC_Application/icons/parallelL.svg new file mode 100644 index 0000000..115cfc2 --- /dev/null +++ b/Software/PC_Application/icons/parallelL.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/parallelR.svg b/Software/PC_Application/icons/parallelR.svg new file mode 100644 index 0000000..615639e --- /dev/null +++ b/Software/PC_Application/icons/parallelR.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/port1.png b/Software/PC_Application/icons/port1.png new file mode 100644 index 0000000000000000000000000000000000000000..37fc643c749c416aaebbe2e0da177ca61734a9eb GIT binary patch literal 995 zcmeAS@N?(olHy`uVBq!ia0vp^K0rL3g9%6)@Mui{Qk(@Ik;M!Qd~-mUk$p<=T?Pi` zsh%#5Ar-gY-reZ^+Chf>gG%pG5!W>#PHROMT4=Bw*~rIg&K~}DLFvRM=YNdikJWZe zalX=IwW7JBdoh=uAglG#CZ&!?4<-b>5zx$a`qkF_DPaD#J zE>>kj??%P)v_4H$fQ0I{c4;DDTJoW$l$?Mmz zD=RBMEZ7mDvn*(3fX0-wY3yuli!Q$S_|Z_&Wzv%NcJ{u%fWq*RXk_SoZ0E3#B=yalHvUG=9ZR~6%{l5mOp>~ zT+~(2)ljbg{@uH(LY=?(Z9r@{#TRDFV89uh= z%akVQK$(8Gx?gIyo`3G`>$`XF-lavWRtXCW z%lCh;+S}^Xm^h=v>hDpD_bivTZrKv@aN+dn(^rOQO+FbhO_Yai_wL=NLmxkW+#)dV z`T29_Ec&7&A}lN|Z{ECFSy{=+!c<#Z%X9e0-+yo4=B~f~`ulGnxF1`SoBQ^~ixgt!%=`ku`O|ml*m?hsWwFprrLeMp;rxh{{F4q)h=*J#lvgS+q7BB zmSvXME4m1Q!AX?RTv z$%?rmp?P|fMwdzE>BUla^B$b&n&vV;=jFw%s_!P-f9O1ZbLF&kuWfB@mt}skxqpE1 z+Q*+Nr_Jh4b*&UbO^9gek_y~-*Rx^gs=wE3-gmPNe{;C&+gYKl@y9>i-m|v;-4>T! uR-2Sorbg!G=GrghHT-~-F@ElT#DD#8_XpXI3ul12mci52&t;ucLK6U863$%! literal 0 HcmV?d00001 diff --git a/Software/PC_Application/icons/port1.svg b/Software/PC_Application/icons/port1.svg new file mode 100644 index 0000000..c93f08d --- /dev/null +++ b/Software/PC_Application/icons/port1.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/port2.png b/Software/PC_Application/icons/port2.png new file mode 100644 index 0000000000000000000000000000000000000000..8ee94a2c8d0a214cc86a8b665c05f3e4bc72269d GIT binary patch literal 1062 zcmeAS@N?(olHy`uVBq!ia0vp^K0rL3g9%6)@Mui{Qk(@Ik;M!Qd~-mUk$p<=T?Pi` z8=fwXAr-gY-gWfQik4viuyKyiLIp30H78teuo#{e+nOUSD0+0wtq||dTP}eLBDq_Z zM7rv#Xx?IT4G<9AnHlV}lS4?$senuJY0IsQ&tmLVL3dC5-cot}_y4ftAJ5Er|NLiK zU$eoilGt=F+fy_4D>SeOc_=g@5ueHr#jIe?ob~MUPZiH1tGRpk?md}eB*XXF#@^2E z-&y4)L7Lr1KfR3J7@;G;kuhu8s#i~*r1<*!)}4IG+WYTcUH@_C+w0e__gh|ETKaYN z?_V|hLbSfVj4m!NzL^tty>$Kc+c$5{%nxJL3!i*)%I#;B|9<@VG5hzs_wWC&az66h(l3?^ZUN&DSc)HcM;k$*DKfHs8H_x3{M!W0sk%?b%H^ z;o;ZMojdpEPmO|rg{5U=boA;~tEA-Q&e`&v-o(z%zIN?ep2IFm6T7;)UcJgvK3$rS z;2?7J?c29szkZ!LbLQT?wz1a_KP>R~=YLyJS!rouvEucwvuAy4YybZJyZ8S4ez(QD z)>+H+#ZP@5-P7BvtEcyDcId(7B5~Lp_8^TLQ7j) zT3Y)4+1uh9ckjM^;evsw>DT%gr;t!x?ce1KK7IOBVRO!)=WgEr8$VyZ>=gJ^`1|M2 zpN9`0e)A?rX<|iPotcp5>Bo1Ey?ghryPNw~pWEWCTetGHH$HiSU>HQWN5fKp;6%`p-xjo-(_S4FLx(}BvTUJwS{P)IrmYF_op;ve9 z+NJ+XMTm3t)hsjdA6|)v@)aGJexBWIBEe&4XD3#xa<@aF#beTuLYcFUB1bP@zWj(c zogrwFN?W2u-|ahhR9a4K(&%^hSx@1|V%yG|2F7aAjPS-u1I8vuGO%iZY4+s=- z6v)^awE6a1F_wicr>3@@ek#P`I4w15Z`Yy=>(?B$u0UGHe|P*QGhT{VlQU{$BX_5HrFVPv#5#XW*RrXJR+c SvFE^y&*16m=d#Wzp$Pz2knL3f literal 0 HcmV?d00001 diff --git a/Software/PC_Application/icons/port2.svg b/Software/PC_Application/icons/port2.svg new file mode 100644 index 0000000..f98b346 --- /dev/null +++ b/Software/PC_Application/icons/port2.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/seriesC.svg b/Software/PC_Application/icons/seriesC.svg new file mode 100644 index 0000000..f9aabbb --- /dev/null +++ b/Software/PC_Application/icons/seriesC.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/seriesL.svg b/Software/PC_Application/icons/seriesL.svg new file mode 100644 index 0000000..1e2b592 --- /dev/null +++ b/Software/PC_Application/icons/seriesL.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/seriesR.svg b/Software/PC_Application/icons/seriesR.svg new file mode 100644 index 0000000..1b9381e --- /dev/null +++ b/Software/PC_Application/icons/seriesR.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Software/PC_Application/test.setup b/Software/PC_Application/test.setup new file mode 100644 index 0000000..e22b8b7 --- /dev/null +++ b/Software/PC_Application/test.setup @@ -0,0 +1,265 @@ +{ + "Generator": null, + "SpectrumAnalyzer": { + "markers": null, + "tiles": { + "plot": "XY-plot", + "plotsettings": { + "XAxis": { + "div": 200000.0, + "log": false, + "max": 405226753.0, + "min": 402073243.0, + "mode": "Use Span", + "type": "Frequency" + }, + "YPrimary": { + "autorange": false, + "div": 10.0, + "log": false, + "max": 0.0, + "min": -120.0, + "traces": [ + 502445883, + 2060438610 + ], + "type": "Magnitude" + }, + "YSecondary": { + "autorange": true, + "div": 1.0, + "log": false, + "max": 0.0, + "min": 0.0, + "traces": null, + "type": "Unknown" + } + }, + "split": false + }, + "traces": [ + { + "color": "#ffff00", + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "Port1", + "parameter": 4, + "paused": false, + "reflection": false, + "type": "Live", + "visible": true + }, + { + "color": "#0000ff", + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "Port2", + "parameter": 5, + "paused": false, + "reflection": false, + "type": "Live", + "visible": true + } + ] + }, + "VNA": { + "de-embedding": [ + { + "operation": "Matching Network", + "settings": [ + [ + { + "component": "SeriesC", + "params": { + "value": 1.2e-11 + } + } + ], + [ + { + "component": "SeriesR", + "params": { + "value": 44.0 + } + }, + { + "component": "ParallelL", + "params": { + "value": 5.3000000000000005e-08 + } + } + ] + ] + } + ], + "markers": null, + "tiles": { + "orientation": "vertical", + "sizes": [ + 381, + 381 + ], + "split": true, + "tile1": { + "orientation": "horizontal", + "sizes": [ + 791, + 790 + ], + "split": true, + "tile1": { + "plot": "smithchart", + "plotsettings": { + "limit_to_span": true, + "traces": [ + 2459058830 + ] + }, + "split": false + }, + "tile2": { + "plot": "XY-plot", + "plotsettings": { + "XAxis": { + "div": 500000000.0, + "log": false, + "max": 6000000000.0, + "min": 0.0, + "mode": "Use Span", + "type": "Frequency" + }, + "YPrimary": { + "autorange": false, + "div": 10.0, + "log": false, + "max": 20.0, + "min": -120.0, + "traces": [ + 2100767570 + ], + "type": "Magnitude" + }, + "YSecondary": { + "autorange": false, + "div": 30.0, + "log": false, + "max": 180.0, + "min": -180.0, + "traces": [ + 2100767570 + ], + "type": "Phase" + } + }, + "split": false + } + }, + "tile2": { + "orientation": "horizontal", + "sizes": [ + 791, + 790 + ], + "split": true, + "tile1": { + "plot": "XY-plot", + "plotsettings": { + "XAxis": { + "div": 500000000.0, + "log": false, + "max": 6000000000.0, + "min": 0.0, + "mode": "Use Span", + "type": "Frequency" + }, + "YPrimary": { + "autorange": false, + "div": 10.0, + "log": false, + "max": 20.0, + "min": -120.0, + "traces": [ + 572044863 + ], + "type": "Magnitude" + }, + "YSecondary": { + "autorange": false, + "div": 30.0, + "log": false, + "max": 180.0, + "min": -180.0, + "traces": [ + 572044863 + ], + "type": "Phase" + } + }, + "split": false + }, + "tile2": { + "plot": "smithchart", + "plotsettings": { + "limit_to_span": true, + "traces": [ + 783313651 + ] + }, + "split": false + } + } + }, + "traces": [ + { + "color": "#ffff00", + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "S11", + "parameter": 0, + "paused": false, + "reflection": true, + "type": "Live", + "visible": true + }, + { + "color": "#0000ff", + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "S12", + "parameter": 1, + "paused": false, + "reflection": false, + "type": "Live", + "visible": true + }, + { + "color": "#00ff00", + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "S21", + "parameter": 2, + "paused": false, + "reflection": false, + "type": "Live", + "visible": true + }, + { + "color": "#ff0000", + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "S22", + "parameter": 3, + "paused": false, + "reflection": true, + "type": "Live", + "visible": true + } + ] + } +}