From fc1d006edd1a1b0ee4566ec5e5dcd75081448e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Sat, 1 Oct 2022 16:12:33 +0200 Subject: [PATCH] Sliding load support + TRL standard and measurements --- .../Calibration/CalStandardLineEditDialog.ui | 147 ++++++++++++++++++ .../CalStandardReflectEditDialog.ui | 106 +++++++++++++ .../Calibration/calibration.cpp | 82 +++++++++- .../PC_Application/Calibration/calibration.h | 5 +- .../Calibration/calibrationmeasurement.cpp | 8 + .../Calibration/calibrationmeasurement.h | 38 +++++ .../Calibration/calstandard.cpp | 126 ++++++++++++++- .../PC_Application/Calibration/calstandard.h | 35 +++++ Software/PC_Application/LibreVNA-GUI.pro | 2 + Software/PC_Application/Util/util.cpp | 79 ++++++++++ Software/PC_Application/Util/util.h | 2 + 11 files changed, 621 insertions(+), 9 deletions(-) create mode 100644 Software/PC_Application/Calibration/CalStandardLineEditDialog.ui create mode 100644 Software/PC_Application/Calibration/CalStandardReflectEditDialog.ui diff --git a/Software/PC_Application/Calibration/CalStandardLineEditDialog.ui b/Software/PC_Application/Calibration/CalStandardLineEditDialog.ui new file mode 100644 index 0000000..92681e7 --- /dev/null +++ b/Software/PC_Application/Calibration/CalStandardLineEditDialog.ui @@ -0,0 +1,147 @@ + + + CalStandardLineEditDialog + + + + 0 + 0 + 383 + 202 + + + + Edit Line Standard + + + true + + + + + + + + Name: + + + + + + + + + + + + + + Z0: + + + + + + + false + + + + + + + Delay: + + + + + + + + + + Minimum usable frequency: + + + + + + + false + + + + + + + Maximum usable frequency: + + + + + + + false + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SIUnitEdit + QLineEdit +
CustomWidgets/siunitedit.h
+
+
+ + + + buttonBox + accepted() + CalStandardLineEditDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CalStandardLineEditDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + + + +
diff --git a/Software/PC_Application/Calibration/CalStandardReflectEditDialog.ui b/Software/PC_Application/Calibration/CalStandardReflectEditDialog.ui new file mode 100644 index 0000000..486ae8b --- /dev/null +++ b/Software/PC_Application/Calibration/CalStandardReflectEditDialog.ui @@ -0,0 +1,106 @@ + + + CalStandardReflectEditDialog + + + + 0 + 0 + 291 + 109 + + + + Edit Reflect Standard + + + true + + + + + + + + Name: + + + + + + + + + + + + + + Reflection type: + + + + + + + + Open + + + + + Short + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CalStandardReflectEditDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CalStandardReflectEditDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp index 00d23fe..b328a48 100644 --- a/Software/PC_Application/Calibration/calibration.cpp +++ b/Software/PC_Application/Calibration/calibration.cpp @@ -3,6 +3,7 @@ #include "CustomWidgets/informationbox.h" #include "Util/app_common.h" #include "unit.h" +#include "Util/util.h" #include "LibreCAL/librecaldialog.h" #include "Eigen/Dense" @@ -296,6 +297,7 @@ QString Calibration::TypeToString(Calibration::Type type) case Type::None: return "None"; case Type::SOLT: return "SOLT"; case Type::ThroughNormalization: return "ThroughNormalization"; + case Type::TRL: return "TRL"; case Type::Last: return "Invalid"; } } @@ -560,6 +562,12 @@ void Calibration::edit() } } } + // update calibration (may have changed due to deleted measurement) + if(canCompute(caltype)) { + compute(caltype); + } else { + deactivate(); + } updateMeasurementTable(); updateCalibrationList(); }); @@ -605,6 +613,12 @@ void Calibration::edit() for(auto s : selected) { measurements[s.row()]->clearPoints(); } + // update calibration (may have changed due to deleted measurement) + if(canCompute(caltype)) { + compute(caltype); + } else { + deactivate(); + } updateMeasurementTable(); updateCalibrationList(); }); @@ -647,8 +661,11 @@ CalibrationMeasurement::Base *Calibration::newMeasurement(CalibrationMeasurement case CalibrationMeasurement::Base::Type::Open: m = new CalibrationMeasurement::Open(this); break; case CalibrationMeasurement::Base::Type::Short: m = new CalibrationMeasurement::Short(this); break; case CalibrationMeasurement::Base::Type::Load: m = new CalibrationMeasurement::Load(this); break; + case CalibrationMeasurement::Base::Type::SlidingLoad: m = new CalibrationMeasurement::SlidingLoad(this); break; + case CalibrationMeasurement::Base::Type::Reflect: m = new CalibrationMeasurement::Reflect(this); break; case CalibrationMeasurement::Base::Type::Through: m = new CalibrationMeasurement::Through(this); break; case CalibrationMeasurement::Base::Type::Isolation: m = new CalibrationMeasurement::Isolation(this); break; + case CalibrationMeasurement::Base::Type::Line: m = new CalibrationMeasurement::Line(this); break; } return m; } @@ -679,13 +696,33 @@ Calibration::Point Calibration::computeSOLT(double f) auto p = caltype.usedPorts[i]; auto _short = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Short, p)); auto open = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Open, p)); - auto load = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Load, p)); auto s_m = _short->getMeasured(f); auto o_m = open->getMeasured(f); - auto l_m = load->getMeasured(f); auto s_c = _short->getActual(f); auto o_c = open->getActual(f); - auto l_c = load->getActual(f); + complex l_c, l_m; + auto slidingMeasurements = findMeasurements(CalibrationMeasurement::Base::Type::SlidingLoad, p); + if(slidingMeasurements.size() >= 3) { + // use sliding load + vector> slidingMeasured; + for(auto m : slidingMeasurements) { + auto slidingLoad = static_cast(m); + auto value = slidingLoad->getMeasured(f); + if(isnan(abs(value))) { + throw runtime_error("missing sliding load measurement"); + } + slidingMeasured.push_back(value); + } + // use center of measurement for ideal load measurement + l_m = Util::findCenterOfCircle(slidingMeasured); + // assume perfect sliding load + l_c = 0.0; + } else { + // use normal load standard + auto load = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Load, p)); + l_c = load->getActual(f); + l_m = load->getMeasured(f); + } 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); point.D[i] = (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; point.S[i] = (l_c * (o_m - s_m) + o_c * (s_m - l_m) + s_c * (l_m - o_m)) / denom; @@ -1203,7 +1240,13 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl for(auto p : type.usedPorts) { required.push_back({.type = CalibrationMeasurement::Base::Type::Short, .port1 = p}); required.push_back({.type = CalibrationMeasurement::Base::Type::Open, .port1 = p}); - required.push_back({.type = CalibrationMeasurement::Base::Type::Load, .port1 = p}); + if(findMeasurements(CalibrationMeasurement::Base::Type::SlidingLoad, p).size() >= 3) { + // got enough sliding load measurements, use these + required.push_back({.type = CalibrationMeasurement::Base::Type::SlidingLoad, .port1 = p}); + } else { + // not enough sliding load measurement, use normal load + required.push_back({.type = CalibrationMeasurement::Base::Type::Load, .port1 = p}); + } } // through measurements between all ports for(int i=1;i<=type.usedPorts.size();i++) { @@ -1220,6 +1263,19 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl } } break; + case Type::TRL: + // Reflect measurement for every port + for(auto p : type.usedPorts) { + required.push_back({.type = CalibrationMeasurement::Base::Type::Reflect, .port1 = p}); + } + // through and line measurements between all ports + for(int i=1;i<=type.usedPorts.size();i++) { + for(int j=i+1;j<=type.usedPorts.size();j++) { + required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j}); + required.push_back({.type = CalibrationMeasurement::Base::Type::Line, .port1 = i, .port2 = j}); + } + } + break; } if(required.size() > 0) { vector foundMeasurements; @@ -1278,6 +1334,7 @@ int Calibration::minimumPorts(Calibration::Type type) switch(type) { case Type::SOLT: return 1; case Type::ThroughNormalization: return 2; + case Type::TRL: return 2; } return -1; } @@ -1415,8 +1472,9 @@ bool Calibration::hasFrequencyOverlap(std::vector Calibration::findMeasurements(CalibrationMeasurement::Base::Type type, int port1, int port2) { + vector ret; for(auto m : measurements) { if(m->getType() != type) { continue; @@ -1434,9 +1492,19 @@ CalibrationMeasurement::Base *Calibration::findMeasurement(CalibrationMeasuremen } } // if we get here, we have a match - return m; + ret.push_back(m); + } + return ret; +} + +CalibrationMeasurement::Base *Calibration::findMeasurement(CalibrationMeasurement::Base::Type type, int port1, int port2) +{ + auto meas = findMeasurements(type, port1, port2); + if(meas.size() > 0) { + return meas[0]; + } else { + return nullptr; } - return nullptr; } QString Calibration::CalType::getReadableDescription() diff --git a/Software/PC_Application/Calibration/calibration.h b/Software/PC_Application/Calibration/calibration.h index 2d49efa..b082fec 100644 --- a/Software/PC_Application/Calibration/calibration.h +++ b/Software/PC_Application/Calibration/calibration.h @@ -19,6 +19,7 @@ public: None, SOLT, ThroughNormalization, + TRL, Last, }; class CalType { @@ -121,8 +122,10 @@ private: void deleteMeasurements(); bool hasFrequencyOverlap(std::vector m, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr); + // returns all measurements that match the paramaters + std::vector findMeasurements(CalibrationMeasurement::Base::Type type, int port1 = 0, int port2 = 0); + // returns the first measurement in the list that matches the parameters CalibrationMeasurement::Base* findMeasurement(CalibrationMeasurement::Base::Type type, int port1 = 0, int port2 = 0); - CalibrationMeasurement::Base *newMeasurement(CalibrationMeasurement::Base::Type type); class Point { diff --git a/Software/PC_Application/Calibration/calibrationmeasurement.cpp b/Software/PC_Application/Calibration/calibrationmeasurement.cpp index 35d616c..2d04fc6 100644 --- a/Software/PC_Application/Calibration/calibrationmeasurement.cpp +++ b/Software/PC_Application/Calibration/calibrationmeasurement.cpp @@ -86,8 +86,11 @@ QString CalibrationMeasurement::Base::TypeToString(CalibrationMeasurement::Base: case Type::Open: return "Open"; case Type::Short: return "Short"; case Type::Load: return "Load"; + case Type::SlidingLoad: return "SlidingLoad"; + case Type::Reflect: return "Reflect"; case Type::Through: return "Through"; case Type::Isolation: return "Isolation"; + case Type::Line: return "Line"; case Type::Last: return "Invalid"; } } @@ -678,3 +681,8 @@ std::vector CalibrationMeasurement::Is { return points; } + +QWidget *CalibrationMeasurement::SlidingLoad::createStandardWidget() +{ + return new QLabel("Connect sliding load"); +} diff --git a/Software/PC_Application/Calibration/calibrationmeasurement.h b/Software/PC_Application/Calibration/calibrationmeasurement.h index 985417c..c77fadc 100644 --- a/Software/PC_Application/Calibration/calibrationmeasurement.h +++ b/Software/PC_Application/Calibration/calibrationmeasurement.h @@ -21,8 +21,11 @@ public: Open, Short, Load, + SlidingLoad, + Reflect, Through, Isolation, + Line, Last, }; @@ -138,6 +141,31 @@ public: virtual Type getType() override {return Type::Load;} }; +class SlidingLoad : public OnePort +{ + Q_OBJECT +public: + SlidingLoad(Calibration *cal) : + OnePort(cal){setFirstSupportedStandard();} + + virtual QWidget* createStandardWidget() override; + + virtual std::set supportedStandardTypes() override {return {};} + virtual Type getType() override {return Type::SlidingLoad;} +}; + +class Reflect : public OnePort +{ + Q_OBJECT +public: + Reflect(Calibration *cal) : + OnePort(cal){setFirstSupportedStandard();} + + virtual std::set supportedStandardTypes() override {return {CalStandard::Virtual::Type::Open, + CalStandard::Virtual::Type::Short, CalStandard::Virtual::Type::Reflect};} + virtual Type getType() override {return Type::Reflect;} +}; + class TwoPort : public Base { Q_OBJECT @@ -234,5 +262,15 @@ protected: std::vector points; }; +class Line : public TwoPort +{ + Q_OBJECT +public: + Line(Calibration *cal) : + TwoPort(cal){setFirstSupportedStandard();} + virtual std::set supportedStandardTypes() override {return {CalStandard::Virtual::Type::Line};} + virtual Type getType() override {return Type::Line;} +}; + } #endif // CALIBRATIONMEASUREMENT_H diff --git a/Software/PC_Application/Calibration/calstandard.cpp b/Software/PC_Application/Calibration/calstandard.cpp index 7f731b7..c1fd8d4 100644 --- a/Software/PC_Application/Calibration/calstandard.cpp +++ b/Software/PC_Application/Calibration/calstandard.cpp @@ -2,7 +2,9 @@ #include "ui_CalStandardOpenEditDialog.h" #include "ui_CalStandardShortEditDialog.h" #include "ui_CalStandardLoadEditDialog.h" +#include "ui_CalStandardReflectEditDialog.h" #include "ui_CalStandardThroughEditDialog.h" +#include "ui_CalStandardLineEditDialog.h" #include "unit.h" #include "Util/util.h" @@ -23,8 +25,9 @@ Virtual *Virtual::create(Virtual::Type type) case Type::Open: return new Open; case Type::Short: return new Short; case Type::Load: return new Load; + case Type::Reflect: return new Reflect; case Type::Through: return new Through; - case Type::Line: // TODO + case Type::Line: return new Line; case Type::Last: break; } @@ -46,6 +49,7 @@ QString Virtual::TypeToString(Virtual::Type type) case Type::Open: return "Open"; case Type::Short: return "Short"; case Type::Load: return "Load"; + case Type::Reflect: return "Reflect"; case Type::Through: return "Through"; case Type::Line: return "Line"; case Type::Last: return "Invalid"; @@ -677,3 +681,123 @@ void Through::fromJSON(nlohmann::json j) delay = j.value("delay", 0.0); loss = j.value("loss", 0.0); } + +Reflect::Reflect() +{ + isShort = true; +} + +std::complex Reflect::toS11(double freq) +{ + Q_UNUSED(freq) + return std::numeric_limits>::quiet_NaN(); +} + +void Reflect::edit(std::function finishedCallback) +{ + auto d = new QDialog; + auto ui = new Ui::CalStandardReflectEditDialog; + ui->setupUi(d); + + ui->name->setText(name); + ui->type->setCurrentIndex(isShort ? 1 : 0); + + QObject::connect(d, &QDialog::accepted, [=](){ + name = ui->name->text(); + isShort = ui->type->currentIndex() == 1; + if(finishedCallback) { + finishedCallback(); + } + }); + + d->show(); +} + +nlohmann::json Reflect::toJSON() +{ + auto j = OnePort::toJSON(); + j["isShort"] = isShort; + return j; +} + +void Reflect::fromJSON(nlohmann::json j) +{ + OnePort::fromJSON(j); + isShort = j.value("isShort", true); +} + +Line::Line() +{ + Z0 = 50.0; + setDelay(0.0); +} + +Sparam Line::toSparam(double freq) +{ + Sparam ret; + ret.m11 = numeric_limits>::quiet_NaN(); + ret.m12 = numeric_limits>::quiet_NaN(); + ret.m21 = numeric_limits>::quiet_NaN(); + ret.m22 = numeric_limits>::quiet_NaN(); + return ret; +} + +void Line::edit(std::function finishedCallback) +{ + auto d = new QDialog; + auto ui = new Ui::CalStandardLineEditDialog; + ui->setupUi(d); + + ui->name->setText(name); + ui->Z0->setUnit("Ω"); + ui->Z0->setPrecision(2); + ui->Z0->setValue(Z0); + ui->delay->setValue(delay); + ui->delay->setUnit("s"); + ui->delay->setPrefixes("pnum "); + ui->delay->setPrecision(4); + ui->minFreq->setUnit("Hz"); + ui->minFreq->setPrecision(3); + ui->minFreq->setPrefixes(" kMG"); + ui->minFreq->setValue(minFreq); + ui->maxFreq->setUnit("Hz"); + ui->maxFreq->setPrecision(3); + ui->maxFreq->setPrefixes(" kMG"); + ui->maxFreq->setValue(maxFreq); + + QObject::connect(ui->delay, &SIUnitEdit::valueChanged, [=](double val){ + ui->minFreq->setValue(1.0 / val * 20 / 360); + ui->maxFreq->setValue(1.0 / val * 160 / 360); + }); + + QObject::connect(d, &QDialog::accepted, [=](){ + name = ui->name->text(); + Z0 = ui->Z0->value(); + setDelay(ui->delay->value()); + if(finishedCallback) { + finishedCallback(); + } + }); + + d->show(); +} + +nlohmann::json Line::toJSON() +{ + auto j = TwoPort::toJSON(); + j["delay"] = delay; + return j; +} + +void Line::fromJSON(nlohmann::json j) +{ + TwoPort::fromJSON(j); + setDelay(j.value("delay", 0.0)); +} + +void Line::setDelay(double delay) +{ + this->delay = delay; + minFreq = 1.0 / delay * 20 / 360; + maxFreq = 1.0 / delay * 160 / 360; +} diff --git a/Software/PC_Application/Calibration/calstandard.h b/Software/PC_Application/Calibration/calstandard.h index daeed38..029040c 100644 --- a/Software/PC_Application/Calibration/calstandard.h +++ b/Software/PC_Application/Calibration/calstandard.h @@ -20,6 +20,7 @@ public: Open, Short, Load, + Reflect, Through, Line, Last @@ -126,6 +127,23 @@ private: bool Cfirst; }; +class Reflect : public OnePort +{ +public: + Reflect(); + Reflect(QString name, bool isShort) + : OnePort(name), isShort(isShort){} + + virtual std::complex toS11(double freq) override; + virtual void edit(std::function finishedCallback = nullptr) override; + virtual Type getType() override {return Type::Reflect;} + virtual nlohmann::json toJSON() override; + virtual void fromJSON(nlohmann::json j) override; +private: + bool isShort; +}; + + class TwoPort : public Virtual { public: @@ -161,6 +179,23 @@ private: double Z0, delay, loss; }; +class Line : public TwoPort +{ +public: + Line(); + Line(QString name, double Z0, double delay) + : TwoPort(name), Z0(Z0), delay(delay){} + + virtual Sparam toSparam(double freq) override; + virtual void edit(std::function finishedCallback = nullptr) override; + virtual Type getType() override {return Type::Line;} + virtual nlohmann::json toJSON() override; + virtual void fromJSON(nlohmann::json j) override; +private: + void setDelay(double delay); + double Z0, delay; +}; + } #endif // CALSTANDARD_H diff --git a/Software/PC_Application/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI.pro index 4e3837e..075df2f 100644 --- a/Software/PC_Application/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI.pro @@ -278,8 +278,10 @@ osx:LIBS += $(shell pkg-config --libs libusb-1.0) QT += widgets network FORMS += \ + Calibration/CalStandardLineEditDialog.ui \ Calibration/CalStandardLoadEditDialog.ui \ Calibration/CalStandardOpenEditDialog.ui \ + Calibration/CalStandardReflectEditDialog.ui \ Calibration/CalStandardShortEditDialog.ui \ Calibration/CalStandardThroughEditDialog.ui \ Calibration/LibreCAL/librecaldialog.ui \ diff --git a/Software/PC_Application/Util/util.cpp b/Software/PC_Application/Util/util.cpp index a762c0a..610e254 100644 --- a/Software/PC_Application/Util/util.cpp +++ b/Software/PC_Application/Util/util.cpp @@ -90,3 +90,82 @@ unsigned long long Util::random(unsigned long long max) std::uniform_int_distribution distribute(0, max); return distribute(generator); } + +std::complex Util::findCenterOfCircle(const std::vector > &points) +{ + int i,iter,IterMAX=99; + + double Xi,Yi,Zi; + double Mz,Mxy,Mxx,Myy,Mxz,Myz,Mzz,Cov_xy,Var_z; + double A0,A1,A2,A22; + double Dy,xnew,x,ynew,y; + double DET,Xcenter,Ycenter; + + // find means + double meanX = 0.0, meanY = 0.0; + for(auto p : points) { + meanX += p.real(); + meanY += p.imag(); + } + meanX /= points.size(); + meanY /= points.size(); + + // computing moments + + Mxx=Myy=Mxy=Mxz=Myz=Mzz=0.; + + for (i=0; i<(int) points.size(); i++) + { + Xi = points[i].real() - meanX; // centered x-coordinates + Yi = points[i].imag() - meanY; // centered y-coordinates + Zi = Xi*Xi + Yi*Yi; + + Mxy += Xi*Yi; + Mxx += Xi*Xi; + Myy += Yi*Yi; + Mxz += Xi*Zi; + Myz += Yi*Zi; + Mzz += Zi*Zi; + } + Mxx /= points.size(); + Myy /= points.size(); + Mxy /= points.size(); + Mxz /= points.size(); + Myz /= points.size(); + Mzz /= points.size(); + + // computing the coefficients of the characteristic polynomial + + Mz = Mxx + Myy; + Cov_xy = Mxx*Myy - Mxy*Mxy; + Var_z = Mzz - Mz*Mz; + + A2 = 4.0*Cov_xy - 3.0*Mz*Mz - Mzz; + A1 = Var_z*Mz + 4.0*Cov_xy*Mz - Mxz*Mxz - Myz*Myz; + A0 = Mxz*(Mxz*Myy - Myz*Mxy) + Myz*(Myz*Mxx - Mxz*Mxy) - Var_z*Cov_xy; + A22 = A2 + A2; + + // finding the root of the characteristic polynomial + // using Newton's method starting at x=0 + // (it is guaranteed to converge to the right root) + + for (x=0.,y=A0,iter=0; iter=abs(y)) break; + x = xnew; y = ynew; + } + + // computing paramters of the fitting circle + + DET = x*x - x*Mz + Cov_xy; + Xcenter = (Mxz*(Myy - x) - Myz*Mxy)/DET/2.0; + Ycenter = (Myz*(Mxx - x) - Mxz*Mxy)/DET/2.0; + + // assembling the output + + return std::complex(Xcenter + meanX, Ycenter + meanY); +} diff --git a/Software/PC_Application/Util/util.h b/Software/PC_Application/Util/util.h index 244e0f1..485ddac 100644 --- a/Software/PC_Application/Util/util.h +++ b/Software/PC_Application/Util/util.h @@ -77,6 +77,8 @@ namespace Util { double distanceToLine(QPointF point, QPointF l1, QPointF l2, QPointF *closestLinePoint = nullptr, double *pointRatio = nullptr); unsigned long long random(unsigned long long max); + + std::complex findCenterOfCircle(const std::vector> &points); } #endif // UTILH_H