From 9cf76c968153f96ccf5a53dafd1499ec58f5cdb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Tue, 13 Sep 2022 21:42:47 +0200 Subject: [PATCH] WIP: make de-embedding and calibration work with arbitrary number of ports --- .../Calibration/calibration.cpp | 232 +++++++++++++++++- .../PC_Application/Calibration/calibration.h | 8 +- .../Calibration/calibrationmeasurement.cpp | 15 ++ .../Calibration/calibrationmeasurement.h | 39 +-- .../Calibration/manualcalibrationdialog.cpp | 5 +- .../Traces/sparamtraceselector.cpp | 81 +++--- .../Traces/sparamtraceselector.h | 12 +- Software/PC_Application/Traces/trace.cpp | 49 ++-- Software/PC_Application/Traces/trace.h | 4 +- .../PC_Application/Traces/tracepolarchart.cpp | 2 +- .../PC_Application/Traces/tracesmithchart.cpp | 2 +- .../VNA/Deembedding/deembedding.cpp | 66 ++--- .../VNA/Deembedding/deembedding.h | 9 +- .../VNA/Deembedding/deembeddingoption.h | 3 +- .../Deembedding/impedancerenormalization.cpp | 9 + .../Deembedding/impedancerenormalization.h | 1 + .../Deembedding/manualdeembeddingdialog.cpp | 5 +- .../VNA/Deembedding/matchingnetwork.cpp | 9 +- .../VNA/Deembedding/matchingnetwork.h | 1 + .../VNA/Deembedding/portextension.cpp | 10 + .../VNA/Deembedding/portextension.h | 1 + .../VNA/Deembedding/twothru.cpp | 5 + .../PC_Application/VNA/Deembedding/twothru.h | 1 + .../PC_Application/VNA/s2pImportOptions.ui | 2 +- .../PC_Application/VNA/tracewidgetvna.cpp | 79 +++--- Software/PC_Application/appwindow.cpp | 19 +- Software/PC_Application/icons.qrc | 2 + Software/PC_Application/icons/DUT2.png | Bin 275 -> 0 bytes Software/PC_Application/icons/DUT_onePort.png | Bin 0 -> 909 bytes Software/PC_Application/icons/port.png | Bin 0 -> 1052 bytes 30 files changed, 478 insertions(+), 193 deletions(-) delete mode 100644 Software/PC_Application/icons/DUT2.png create mode 100644 Software/PC_Application/icons/DUT_onePort.png create mode 100644 Software/PC_Application/icons/port.png diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp index 221fba9..b7bf04d 100644 --- a/Software/PC_Application/Calibration/calibration.cpp +++ b/Software/PC_Application/Calibration/calibration.cpp @@ -45,6 +45,7 @@ QString Calibration::TypeToString(Calibration::Type type) switch(type) { case Type::None: return "None"; case Type::SOLT: return "SOLT"; + case Type::ThroughNormalization: return "ThroughNormalization"; case Type::Last: return "Invalid"; } } @@ -134,6 +135,18 @@ void Calibration::correctMeasurement(VirtualDevice::VNAMeasurement &d) } } +void Calibration::correctTraces(std::map traceSet) +{ + auto points = Trace::assembleDatapoints(traceSet); + if(points.size()) { + // succeeded in assembling datapoints + for(auto &p : points) { + correctMeasurement(p); + } + Trace::fillFromDatapoints(traceSet, points); + } +} + void Calibration::edit() { auto d = new QDialog(); @@ -390,8 +403,7 @@ CalibrationMeasurement::Base *Calibration::newMeasurement(CalibrationMeasurement return m; } -Calibration::Point Calibration::computeSOLT(double f) -{ +Calibration::Point Calibration::createInitializedPoint(double f) { Point point; point.frequency = f; // resize vectors @@ -405,6 +417,12 @@ Calibration::Point Calibration::computeSOLT(double f) fill(point.L.begin(), point.L.end(), vector>(caltype.usedPorts.size())); fill(point.T.begin(), point.T.end(), vector>(caltype.usedPorts.size())); fill(point.I.begin(), point.I.end(), vector>(caltype.usedPorts.size())); + return point; +} + +Calibration::Point Calibration::computeSOLT(double f) +{ + Point point = createInitializedPoint(f); // Calculate SOL coefficients for(unsigned int i=0;i(findMeasurement(CalibrationMeasurement::Base::Type::Through, p1, p2)); + auto throughReverse = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Through, p2, p1)); + complex S11, S21; + Sparam Sideal; + if(throughForward) { + S21 = throughForward->getMeasured(f).m21; + Sideal = throughForward->getActual(f); + } else if(throughReverse) { + S21 = throughReverse->getMeasured(f).m12; + Sideal = throughReverse->getActual(f); + swap(Sideal.m12, Sideal.m21); + } + point.L[i][j] = 0.0; + point.T[i][j] = S21 / Sideal.m21; + point.I[i][j] = 0.0; + } + } + return point; +} + Calibration::CalType Calibration::getCaltype() const { return caltype; @@ -504,12 +563,151 @@ Calibration::InterpolationType Calibration::getInterpolation(double f_start, dou std::vector Calibration::getErrorTermTraces() { - return vector(); // TODO + vector ret; + if(points.size() == 0) { + return ret; + } + for(unsigned int i=0;isetReflection(true); + tDir->setCalibration(); + auto tSM = new Trace("SourceMatch_Port"+QString::number(p)); + tSM->setReflection(true); + tSM->setCalibration(); + auto tRT = new Trace("ReflectionTracking_Port"+QString::number(p)); + tRT->setReflection(false); + tRT->setCalibration(); + for(auto p : points) { + Trace::Data td; + td.x = p.frequency; + td.y = p.D[i]; + tDir->addData(td, Trace::DataType::Frequency); + td.y = p.S[i]; + tSM->addData(td, Trace::DataType::Frequency); + td.y = p.R[i]; + tRT->addData(td, Trace::DataType::Frequency); + } + ret.push_back(tDir); + ret.push_back(tSM); + ret.push_back(tRT); + for(unsigned int j=0;jsetReflection(true); + tRM->setCalibration(); + auto tTT = new Trace("TransmissionTracking_"+QString::number(p)+QString::number(p2)); + tTT->setReflection(false); + tTT->setCalibration(); + auto tTI = new Trace("TransmissionIsolation_"+QString::number(p)+QString::number(p2)); + tTI->setReflection(false); + tTI->setCalibration(); + for(auto p : points) { + Trace::Data td; + td.x = p.frequency; + td.y = p.L[i][j]; + tRM->addData(td, Trace::DataType::Frequency); + td.y = p.T[i][j]; + tTT->addData(td, Trace::DataType::Frequency); + td.y = p.I[i][j]; + tTI->addData(td, Trace::DataType::Frequency); + } + ret.push_back(tRM); + ret.push_back(tTT); + ret.push_back(tTI); + } + } + return ret; } std::vector Calibration::getMeasurementTraces() { - return vector(); // TODO + vector ret; + for(auto m : measurements) { + switch(m->getType()) { + case CalibrationMeasurement::Base::Type::Open: + case CalibrationMeasurement::Base::Type::Short: + case CalibrationMeasurement::Base::Type::Load: { + auto onePort = static_cast(m); + auto t = new Trace(CalibrationMeasurement::Base::TypeToString(onePort->getType())+"_Port"+QString::number(onePort->getPort())); + t->setCalibration(); + t->setReflection(true); + for(auto d : onePort->getPoints()) { + Trace::Data td; + td.x = d.frequency; + td.y = d.S; + t->addData(td, Trace::DataType::Frequency); + } + ret.push_back(t); + } + break; + case CalibrationMeasurement::Base::Type::Through: { + auto twoPort = static_cast(m); + auto ts11 = new Trace(CalibrationMeasurement::Base::TypeToString(twoPort->getType())+"_Port"+QString::number(twoPort->getPort1())+QString::number(twoPort->getPort2())+"_S11"); + auto ts12 = new Trace(CalibrationMeasurement::Base::TypeToString(twoPort->getType())+"_Port"+QString::number(twoPort->getPort1())+QString::number(twoPort->getPort2())+"_S12"); + auto ts21 = new Trace(CalibrationMeasurement::Base::TypeToString(twoPort->getType())+"_Port"+QString::number(twoPort->getPort1())+QString::number(twoPort->getPort2())+"_S21"); + auto ts22 = new Trace(CalibrationMeasurement::Base::TypeToString(twoPort->getType())+"_Port"+QString::number(twoPort->getPort1())+QString::number(twoPort->getPort2())+"_S22"); + ts11->setCalibration(); + ts11->setReflection(true); + ts12->setCalibration(); + ts12->setReflection(false); + ts21->setCalibration(); + ts21->setReflection(false); + ts22->setCalibration(); + ts22->setReflection(true); + for(auto d : twoPort->getPoints()) { + Trace::Data td; + td.x = d.frequency; + td.y = d.S.m11; + ts11->addData(td, Trace::DataType::Frequency); + td.y = d.S.m12; + ts12->addData(td, Trace::DataType::Frequency); + td.y = d.S.m21; + ts21->addData(td, Trace::DataType::Frequency); + td.y = d.S.m22; + ts22->addData(td, Trace::DataType::Frequency); + } + ret.push_back(ts11); + ret.push_back(ts12); + ret.push_back(ts21); + ret.push_back(ts22); + } + break; + case CalibrationMeasurement::Base::Type::Isolation: { + auto iso = static_cast(m); + int ports = iso->getPoints()[0].S.size(); + // Create the traces + vector> traces; + traces.resize(ports); + for(int i=0;igetType())+"_S"+QString::number(i+1)+QString::number(j+1)); + t->setCalibration(); + t->setReflection(i==j); + traces[i].push_back(t); + // also add to main return vector + ret.push_back(t); + } + } + // Fill the traces + for(auto p : iso->getPoints()) { + Trace::Data td; + td.x = p.frequency; + for(int i=0;iaddData(td, Trace::DataType::Frequency); + } + } + } + } + break; + } + } + return ret; } QString Calibration::getCurrentCalibrationFile() @@ -735,15 +933,15 @@ std::vector Calibration::getTypes() bool Calibration::canCompute(Calibration::CalType type, double *startFreq, double *stopFreq, int *points) { + using RequiredMeasurements = struct { + CalibrationMeasurement::Base::Type type; + int port1, port2; + }; + vector required; switch(type.type) { case Type::None: return true; // Always possible to reset the calibration - case Type::SOLT: { - using RequiredMeasurements = struct { - CalibrationMeasurement::Base::Type type; - int port1, port2; - }; - vector required; + case Type::SOLT: // SOL measurements for every port for(auto p : type.usedPorts) { required.push_back({.type = CalibrationMeasurement::Base::Type::Short, .port1 = p}); @@ -756,6 +954,17 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j}); } } + break; + case Type::ThroughNormalization: + // through 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}); + } + } + break; + } + if(required.size() > 0) { vector foundMeasurements; for(auto m : required) { auto meas = findMeasurement(m.type, m.port1, m.port2); @@ -767,8 +976,6 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl } } return hasFrequencyOverlap(foundMeasurements, startFreq, stopFreq, points); - } - break; } return false; } @@ -816,6 +1023,7 @@ int Calibration::minimumPorts(Calibration::Type type) { switch(type) { case Type::SOLT: return 1; + case Type::ThroughNormalization: return 2; } return -1; } diff --git a/Software/PC_Application/Calibration/calibration.h b/Software/PC_Application/Calibration/calibration.h index b7abd10..6bdb940 100644 --- a/Software/PC_Application/Calibration/calibration.h +++ b/Software/PC_Application/Calibration/calibration.h @@ -17,6 +17,7 @@ public: enum class Type { None, SOLT, + ThroughNormalization, Last, }; class CalType { @@ -36,6 +37,7 @@ public: // Applies calculated calibration coefficients to measurement data void correctMeasurement(VirtualDevice::VNAMeasurement &d); + void correctTraces(std::map traceSet); // Starts the calibration edit dialog, allowing the user to make/delete measurements void edit(); @@ -123,8 +125,8 @@ private: public: double frequency; std::vector> D; // Directivity - std::vector> R; // Source Match - std::vector> S; // Reflection tracking + std::vector> R; // Reflection tracking + std::vector> S; // Source Match std::vector>> L; // Receiver Match std::vector>> T; // Transmission tracking std::vector>> I; // Transmission isolation @@ -132,7 +134,9 @@ private: }; std::vector points; + Point createInitializedPoint(double f); Point computeSOLT(double f); + Point computeThroughNormalization(double f); std::vector measurements; diff --git a/Software/PC_Application/Calibration/calibrationmeasurement.cpp b/Software/PC_Application/Calibration/calibrationmeasurement.cpp index 65a1a23..2e10ce3 100644 --- a/Software/PC_Application/Calibration/calibrationmeasurement.cpp +++ b/Software/PC_Application/Calibration/calibrationmeasurement.cpp @@ -328,6 +328,11 @@ void CalibrationMeasurement::OnePort::setPort(int p) } } +std::vector CalibrationMeasurement::OnePort::getPoints() const +{ + return points; +} + double CalibrationMeasurement::TwoPort::minFreq() { if(points.size() > 0) { @@ -529,6 +534,11 @@ void CalibrationMeasurement::TwoPort::setReverseStandard(bool reverse) } } +std::vector CalibrationMeasurement::TwoPort::getPoints() const +{ + return points; +} + int CalibrationMeasurement::TwoPort::getPort1() const { return port1; @@ -658,3 +668,8 @@ std::complex CalibrationMeasurement::Isolation::getMeasured(double frequ return p.S[portRcv][portSrc]; } } + +std::vector CalibrationMeasurement::Isolation::getPoints() const +{ + return points; +} diff --git a/Software/PC_Application/Calibration/calibrationmeasurement.h b/Software/PC_Application/Calibration/calibrationmeasurement.h index 93055b0..d407cc8 100644 --- a/Software/PC_Application/Calibration/calibrationmeasurement.h +++ b/Software/PC_Application/Calibration/calibrationmeasurement.h @@ -1,4 +1,4 @@ -#ifndef CALIBRATIONMEASUREMENT_H +#ifndef CALIBRATIONMEASUREMENT_H #define CALIBRATIONMEASUREMENT_H #include "calstandard.h" @@ -83,6 +83,13 @@ public: virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; + class Point { + public: + double frequency; + std::complex S; + }; + std::vector getPoints() const; + std::complex getMeasured(double frequency); std::complex getActual(double frequency); @@ -95,11 +102,6 @@ signals: void portChanged(int p); protected: int port; - class Point { - public: - double frequency; - std::complex S; - }; std::vector points; }; @@ -156,12 +158,20 @@ public: virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; + class Point { + public: + double frequency; + Sparam S; + }; + std::vector getPoints() const; + Sparam getMeasured(double frequency); Sparam getActual(double frequency); int getPort1() const; int getPort2() const; + public slots: void setPort1(int p); void setPort2(int p); @@ -174,11 +184,6 @@ signals: protected: int port1, port2; bool reverseStandard; // Set to true if standard is defined with ports swapped - class Point { - public: - double frequency; - Sparam S; - }; std::vector points; }; @@ -211,17 +216,19 @@ public: virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; + class Point { + public: + double frequency; + std::vector>> S; + }; + std::vector getPoints() const; + std::complex getMeasured(double frequency, unsigned int portRcv, unsigned int portSrc); virtual std::set supportedStandardTypes() override {return {};} virtual Type getType() override {return Type::Isolation;} protected: - class Point { - public: - double frequency; - std::vector>> S; - }; std::vector points; }; diff --git a/Software/PC_Application/Calibration/manualcalibrationdialog.cpp b/Software/PC_Application/Calibration/manualcalibrationdialog.cpp index 1157318..d554f96 100644 --- a/Software/PC_Application/Calibration/manualcalibrationdialog.cpp +++ b/Software/PC_Application/Calibration/manualcalibrationdialog.cpp @@ -7,13 +7,12 @@ ManualCalibrationDialog::ManualCalibrationDialog(const TraceModel &model, Calibr ui(new Ui::ManualCalibrationDialog) { ui->setupUi(this); - auto traceSelector = new SparamTraceSelector(model, 2); + auto traceSelector = new SparamTraceSelector(model, cal->getCaltype().usedPorts); ui->verticalLayout->insertWidget(1, traceSelector, 1.0); ui->buttonBox->setEnabled(false); connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled); connect(ui->buttonBox, &QDialogButtonBox::accepted, [=]() { - auto t = traceSelector->getTraces(); -// cal->correctTraces(*t[0], *t[1], *t[2], *t[3]); // TODO + cal->correctTraces(traceSelector->getTraces()); accept(); }); } diff --git a/Software/PC_Application/Traces/sparamtraceselector.cpp b/Software/PC_Application/Traces/sparamtraceselector.cpp index 9567a74..8322c9a 100644 --- a/Software/PC_Application/Traces/sparamtraceselector.cpp +++ b/Software/PC_Application/Traces/sparamtraceselector.cpp @@ -5,42 +5,39 @@ using namespace std; -SparamTraceSelector::SparamTraceSelector(const TraceModel &model, unsigned int num_ports, bool empty_allowed, std::set skip) +SparamTraceSelector::SparamTraceSelector(const TraceModel &model, std::vector used_ports, bool empty_allowed) : model(model), - num_ports(num_ports), + used_ports(used_ports), empty_allowed(empty_allowed) { - // Create comboboxes - auto layout = new QFormLayout; - setLayout(layout); - for(unsigned int i=0;i(&QComboBox::currentIndexChanged), [=](int) { - traceSelectionChanged(box); - }); - boxes.push_back(box); - layout->addRow(label, box); - if(skip.count(i*num_ports + j)) { - label->setVisible(false); - box->setVisible(false); - } - } - } - + createGUI(); setInitialChoices(); } -std::vector SparamTraceSelector::getTraces() +SparamTraceSelector::SparamTraceSelector(const TraceModel &model, std::set used_ports, bool empty_allowed) + : model(model), + empty_allowed(empty_allowed) { - vector ret; - for(auto b : boxes) { - if(b->currentIndex() == 0) { - ret.push_back(nullptr); - } else { - auto trace = qvariant_cast(b->itemData(b->currentIndex())); - ret.push_back(trace); + // create vector from set + std::copy(used_ports.begin(), used_ports.end(), std::back_inserter(this->used_ports)); + createGUI(); + setInitialChoices(); +} + +std::map SparamTraceSelector::getTraces() +{ + std::map ret; + for(unsigned int i=0;icurrentIndex() == 0) { + t = nullptr; + } else { + t = qvariant_cast(b->itemData(b->currentIndex())); + } + QString name = "S"+QString::number(used_ports[i])+QString::number(used_ports[j]); + ret[name] = t; } } return ret; @@ -48,7 +45,7 @@ std::vector SparamTraceSelector::getTraces() void SparamTraceSelector::setInitialChoices() { - for(unsigned int i=0;iblockSignals(true); boxes[i]->clear(); boxes[i]->addItem("None"); @@ -61,7 +58,7 @@ void SparamTraceSelector::setInitialChoices() // can't select empty traces continue; } - bool reflectionRequired = i%(num_ports+1) == 0 ? true : false; + bool reflectionRequired = i%(used_ports.size()+1) == 0 ? true : false; if(reflectionRequired != t->isReflection()) { // invalid S parameter continue; @@ -106,7 +103,7 @@ void SparamTraceSelector::traceSelectionChanged(QComboBox *cb) text.chop(2); if(text.endsWith("S")) { // tracename ended in Sxx, probably other traces with matching prefix available - for(unsigned int i=0;icount();j++) { auto candidate = b->itemText(j); // check if correct parameter - QString expectedSparam = QString::number(i/num_ports+1)+QString::number(i%num_ports+1); + QString expectedSparam = QString::number(i/used_ports.size()+1)+QString::number(i%used_ports.size()+1); if(!candidate.endsWith(expectedSparam)) { // wrong S parameter, skip continue; @@ -170,3 +167,21 @@ void SparamTraceSelector::traceSelectionChanged(QComboBox *cb) emit selectionValid(valid); } } + +void SparamTraceSelector::createGUI() +{ + // Create comboboxes + auto layout = new QFormLayout; + setLayout(layout); + for(unsigned int i=0;i(&QComboBox::currentIndexChanged), [=](int) { + traceSelectionChanged(box); + }); + boxes.push_back(box); + layout->addRow(label, box); + } + } +} diff --git a/Software/PC_Application/Traces/sparamtraceselector.h b/Software/PC_Application/Traces/sparamtraceselector.h index c9da0db..67822e7 100644 --- a/Software/PC_Application/Traces/sparamtraceselector.h +++ b/Software/PC_Application/Traces/sparamtraceselector.h @@ -3,6 +3,8 @@ #include "tracemodel.h" +#include +#include #include #include @@ -11,12 +13,13 @@ class SparamTraceSelector : public QWidget Q_OBJECT public: - SparamTraceSelector(const TraceModel &model, unsigned int num_ports, bool empty_allowed = false, std::set skip = {}); + SparamTraceSelector(const TraceModel &model, std::vector used_ports, bool empty_allowed = false); + SparamTraceSelector(const TraceModel &model, std::set used_ports, bool empty_allowed = false); bool isValid(); - std::vector getTraces(); - unsigned int getPoints() { return points;}; + std::map getTraces(); + unsigned int getPoints() { return points;} signals: void selectionValid(bool valid); @@ -24,12 +27,13 @@ signals: private: void setInitialChoices(); void traceSelectionChanged(QComboBox *cb); + void createGUI(); const TraceModel &model; std::vector boxes; - unsigned int num_ports; bool empty_allowed; + std::vector used_ports; unsigned int points; double minFreq, maxFreq; bool valid; diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index f6e396b..c3dd8b7 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -260,24 +260,24 @@ QString Trace::fillFromCSV(CSV &csv, unsigned int parameter) return lastTraceName; } -void Trace::fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector &data) +void Trace::fillFromDatapoints(std::map traceSet, const std::vector &data) { - S11.clear(); - S12.clear(); - S21.clear(); - S22.clear(); + // remove all previous points + for(auto m : traceSet) { + m.second->clear(); + } + // add new points to traces for(auto d : data) { Trace::Data td; auto S = d.toSparam(1, 2); td.x = d.frequency; - td.y = S.m11; - S11.addData(td, DataType::Frequency); - td.y = S.m12; - S12.addData(td, DataType::Frequency); - td.y = S.m21; - S21.addData(td, DataType::Frequency); - td.y = S.m22; - S22.addData(td, DataType::Frequency); + for(auto m : d.measurements) { + td.y = m.second; + QString measurement = m.first; + if(traceSet.count(measurement)) { + traceSet[measurement]->addData(td, DataType::Frequency); + } + } } } @@ -894,20 +894,16 @@ std::vector Trace::createFromCSV(CSV &csv) return traces; } -std::vector Trace::assembleDatapoints(const Trace &S11, const Trace &S12, const Trace &S21, const Trace &S22) +std::vector Trace::assembleDatapoints(std::map traceSet) { vector ret; // Sanity check traces - unsigned int samples = S11.size(); - auto impedance = S11.getReferenceImpedance(); - vector traces; - traces.push_back(&S11); - traces.push_back(&S12); - traces.push_back(&S21); - traces.push_back(&S22); + unsigned int samples = traceSet.begin()->second->size(); + auto impedance = traceSet.begin()->second->getReferenceImpedance(); vector freqs; - for(const auto t : traces) { + for(auto m : traceSet) { + const Trace *t = m.second; if(t->size() != samples) { qWarning() << "Selected traces do not have the same size"; return ret; @@ -939,10 +935,11 @@ std::vector Trace::assembleDatapoints(const Trace // Checks passed, assemble datapoints for(unsigned int i=0;isample(i).y; + } d.pointNum = i; d.frequency = freqs[i]; ret.push_back(d); diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index 442eb84..f66283b 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -49,7 +49,7 @@ public: void setVelocityFactor(double v); void fillFromTouchstone(Touchstone &t, unsigned int parameter); QString fillFromCSV(CSV &csv, unsigned int parameter); // returns the suggested trace name (not yet set in member data) - static void fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector &data); + static void fillFromDatapoints(std::map traceSet, const std::vector &data); void fromLivedata(LivedataType type, QString param); void fromMath(); QString name() { return _name; } @@ -137,7 +137,7 @@ public: // Assembles datapoints as received from the VNA from four S parameter traces. Requires that all traces are in the frequency domain, // have the same number of samples and their samples must be at the same frequencies across all traces - static std::vector assembleDatapoints(const Trace &S11, const Trace &S12, const Trace &S21, const Trace &S22); + static std::vector assembleDatapoints(std::map traceSet); static LivedataType TypeFromString(QString s); static QString TypeToString(LivedataType t); diff --git a/Software/PC_Application/Traces/tracepolarchart.cpp b/Software/PC_Application/Traces/tracepolarchart.cpp index 7bc0f4a..df03e68 100644 --- a/Software/PC_Application/Traces/tracepolarchart.cpp +++ b/Software/PC_Application/Traces/tracepolarchart.cpp @@ -232,7 +232,7 @@ void TracePolarChart::draw(QPainter &p) { } if(dropPending) { - // TODO adjust coords due to shifted restore + // adjust coords due to shifted restore p.setOpacity(0.5); p.setBrush(Qt::white); p.setPen(Qt::white); diff --git a/Software/PC_Application/Traces/tracesmithchart.cpp b/Software/PC_Application/Traces/tracesmithchart.cpp index 9f5dd9f..0922f75 100644 --- a/Software/PC_Application/Traces/tracesmithchart.cpp +++ b/Software/PC_Application/Traces/tracesmithchart.cpp @@ -328,7 +328,7 @@ void TraceSmithChart::draw(QPainter &p) { } } if(dropPending) { - // TODO adjust coords due to shifted restore + // adjust coords due to shifted restore p.setOpacity(0.5); p.setBrush(Qt::white); p.setPen(Qt::white); diff --git a/Software/PC_Application/VNA/Deembedding/deembedding.cpp b/Software/PC_Application/VNA/Deembedding/deembedding.cpp index 1c8a8f9..7a20bf2 100644 --- a/Software/PC_Application/VNA/Deembedding/deembedding.cpp +++ b/Software/PC_Application/VNA/Deembedding/deembedding.cpp @@ -30,7 +30,7 @@ void Deembedding::measurementCompleted() measurementUI = nullptr; } -void Deembedding::startMeasurementDialog(bool S11, bool S12, bool S21, bool S22) +void Deembedding::startMeasurementDialog(DeembeddingOption *option) { measurements.clear(); measurementDialog = new QDialog; @@ -42,20 +42,7 @@ void Deembedding::startMeasurementDialog(bool S11, bool S12, bool S21, bool S22) }); // add the trace selector - set skip; - if(!S11) { - skip.insert(0); - } - if(!S12) { - skip.insert(1); - } - if(!S21) { - skip.insert(2); - } - if(!S22) { - skip.insert(3); - } - auto traceChooser = new SparamTraceSelector(tm, 2, false, skip); + auto traceChooser = new SparamTraceSelector(tm, option->getAffectedPorts()); ui->horizontalLayout_2->insertWidget(0, traceChooser, 1); connect(traceChooser, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled); @@ -70,33 +57,8 @@ void Deembedding::startMeasurementDialog(bool S11, bool S12, bool S21, bool S22) connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){ // create datapoints from individual traces measurements.clear(); - auto t = traceChooser->getTraces(); - auto S11 = t[0]; - auto S12 = t[1]; - auto S21 = t[2]; - auto S22 = t[3]; - for(unsigned int i=0;igetPoints();i++) { - VirtualDevice::VNAMeasurement p; - p.pointNum = i; - p.Z0 = 0; - p.dBm = 0; - Sparam S; - if(S11) { - S.m11 = S11->sample(i).y; - p.frequency = S11->sample(i).x; - } - if(S12) { - S.m12 = S12->sample(i).y; - p.frequency = S11->sample(i).x; - } - if(S21) { - S.m21 = S21->sample(i).y; - p.frequency = S11->sample(i).x; - } - if(S22) { - S.m22 = S22->sample(i).y; - p.frequency = S11->sample(i).x; - } + auto points = Trace::assembleDatapoints(traceChooser->getTraces()); + for(auto p : points) { measurements.push_back(p); } measurementCompleted(); @@ -151,15 +113,15 @@ void Deembedding::Deembed(VirtualDevice::VNAMeasurement &d) } } -void Deembedding::Deembed(Trace &S11, Trace &S12, Trace &S21, Trace &S22) +void Deembedding::Deembed(std::map traceSet) { - auto points = Trace::assembleDatapoints(S11, S12, S21, S22); + auto points = Trace::assembleDatapoints(traceSet); if(points.size()) { // succeeded in assembling datapoints for(auto &p : points) { Deembed(p); } - Trace::fillFromDatapoints(S11, S12, S21, S22, points); + Trace::fillFromDatapoints(traceSet, points); } } @@ -184,9 +146,9 @@ void Deembedding::addOption(DeembeddingOption *option) options.erase(pos); } }); - connect(option, &DeembeddingOption::triggerMeasurement, [=](bool S11, bool S12, bool S21, bool S22) { + connect(option, &DeembeddingOption::triggerMeasurement, [=]() { measuringOption = option; - startMeasurementDialog(S11, S12, S21, S22); + startMeasurementDialog(option); }); emit optionAdded(); } @@ -199,6 +161,16 @@ void Deembedding::swapOptions(unsigned int index) std::swap(options[index], options[index+1]); } +std::set Deembedding::getAffectedPorts() +{ + set ret; + for(auto o : options) { + auto affected = o->getAffectedPorts(); + ret.insert(affected.begin(), affected.end()); + } + return ret; +} + nlohmann::json Deembedding::toJSON() { nlohmann::json list; diff --git a/Software/PC_Application/VNA/Deembedding/deembedding.h b/Software/PC_Application/VNA/Deembedding/deembedding.h index f7163ac..601dbef 100644 --- a/Software/PC_Application/VNA/Deembedding/deembedding.h +++ b/Software/PC_Application/VNA/Deembedding/deembedding.h @@ -6,6 +6,8 @@ #include "Traces/tracemodel.h" #include +#include + #include #include #include @@ -20,11 +22,14 @@ public: ~Deembedding(){} void Deembed(VirtualDevice::VNAMeasurement &d); - void Deembed(Trace &S11, Trace &S12, Trace &S21, Trace &S22); + void Deembed(std::map traceSet); void removeOption(unsigned int index); void addOption(DeembeddingOption* option); void swapOptions(unsigned int index); + + std::set getAffectedPorts(); + std::vector& getOptions() {return options;} nlohmann::json toJSON() override; void fromJSON(nlohmann::json j) override; @@ -36,7 +41,7 @@ signals: void allOptionsCleared(); private: void measurementCompleted(); - void startMeasurementDialog(bool S11, bool S12, bool S21, bool S22); + void startMeasurementDialog(DeembeddingOption *option); std::vector options; DeembeddingOption *measuringOption; TraceModel &tm; diff --git a/Software/PC_Application/VNA/Deembedding/deembeddingoption.h b/Software/PC_Application/VNA/Deembedding/deembeddingoption.h index 5d69846..5a2e4a0 100644 --- a/Software/PC_Application/VNA/Deembedding/deembeddingoption.h +++ b/Software/PC_Application/VNA/Deembedding/deembeddingoption.h @@ -23,6 +23,7 @@ public: static DeembeddingOption *create(Type type); static QString getName(Type type); + virtual std::set getAffectedPorts() = 0; virtual void transformDatapoint(VirtualDevice::VNAMeasurement &p) = 0; virtual void edit(){} virtual Type getType() = 0; @@ -33,7 +34,7 @@ signals: // Deembedding option may selfdestruct if not applicable with current settings. It should emit this signal before deleting itself void deleted(DeembeddingOption *option); - void triggerMeasurement(bool S11 = true, bool S12 = true, bool S21 = true, bool S22 = true); + void triggerMeasurement(); }; #endif // DEEMBEDDING_H diff --git a/Software/PC_Application/VNA/Deembedding/impedancerenormalization.cpp b/Software/PC_Application/VNA/Deembedding/impedancerenormalization.cpp index 201d40b..71d311b 100644 --- a/Software/PC_Application/VNA/Deembedding/impedancerenormalization.cpp +++ b/Software/PC_Application/VNA/Deembedding/impedancerenormalization.cpp @@ -15,6 +15,15 @@ ImpedanceRenormalization::ImpedanceRenormalization() } +std::set ImpedanceRenormalization::getAffectedPorts() +{ + set ret; + for(int i=1;i<=VirtualDevice::getInfo(VirtualDevice::getConnected()).ports;i++) { + ret.insert(i); + } + return ret; +} + void ImpedanceRenormalization::transformDatapoint(VirtualDevice::VNAMeasurement &p) { std::map> transformed; diff --git a/Software/PC_Application/VNA/Deembedding/impedancerenormalization.h b/Software/PC_Application/VNA/Deembedding/impedancerenormalization.h index e461c45..28eb9d6 100644 --- a/Software/PC_Application/VNA/Deembedding/impedancerenormalization.h +++ b/Software/PC_Application/VNA/Deembedding/impedancerenormalization.h @@ -13,6 +13,7 @@ class ImpedanceRenormalization : public DeembeddingOption public: ImpedanceRenormalization(); + std::set getAffectedPorts() override; void transformDatapoint(VirtualDevice::VNAMeasurement &p) override; Type getType() override { return Type::ImpedanceRenormalization;} nlohmann::json toJSON() override; diff --git a/Software/PC_Application/VNA/Deembedding/manualdeembeddingdialog.cpp b/Software/PC_Application/VNA/Deembedding/manualdeembeddingdialog.cpp index 5a9b897..b1cf82f 100644 --- a/Software/PC_Application/VNA/Deembedding/manualdeembeddingdialog.cpp +++ b/Software/PC_Application/VNA/Deembedding/manualdeembeddingdialog.cpp @@ -7,13 +7,12 @@ ManualDeembeddingDialog::ManualDeembeddingDialog(const TraceModel &model, Deembe ui(new Ui::ManualDeembeddingDialog) { ui->setupUi(this); - auto traceSelector = new SparamTraceSelector(model, 2); + auto traceSelector = new SparamTraceSelector(model, deemb->getAffectedPorts()); ui->verticalLayout->insertWidget(1, traceSelector, 1.0); ui->buttonBox->setEnabled(false); connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled); connect(ui->buttonBox, &QDialogButtonBox::accepted, [=]() { - auto t = traceSelector->getTraces(); - deemb->Deembed(*t[0], *t[1], *t[2], *t[3]); + deemb->Deembed(traceSelector->getTraces()); accept(); }); } diff --git a/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp b/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp index 5e425f2..35887aa 100644 --- a/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp +++ b/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp @@ -27,6 +27,11 @@ MatchingNetwork::MatchingNetwork() port = 1; } +std::set MatchingNetwork::getAffectedPorts() +{ + return {port}; +} + void MatchingNetwork::transformDatapoint(VirtualDevice::VNAMeasurement &p) { auto S = p.toSparam(1, 2); @@ -106,12 +111,12 @@ void MatchingNetwork::edit() p1->setMinimumSize(portWidth, 151); p1->setMaximumSize(portWidth, 151); p1->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - p1->setStyleSheet("image: url(:/icons/port1.png);"); + p1->setStyleSheet("image: url(:/icons/port.png);"); auto DUT = new QWidget(); DUT->setMinimumSize(DUTWidth, 151); DUT->setMaximumSize(DUTWidth, 151); DUT->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - DUT->setStyleSheet("image: url(:/icons/DUT.png);"); + DUT->setStyleSheet("image: url(:/icons/DUT_onePort.png);"); layout->addWidget(p1); for(auto w : network) { diff --git a/Software/PC_Application/VNA/Deembedding/matchingnetwork.h b/Software/PC_Application/VNA/Deembedding/matchingnetwork.h index 314a4f9..81eca99 100644 --- a/Software/PC_Application/VNA/Deembedding/matchingnetwork.h +++ b/Software/PC_Application/VNA/Deembedding/matchingnetwork.h @@ -64,6 +64,7 @@ public: // DeembeddingOption interface public: + std::set getAffectedPorts() override; void transformDatapoint(VirtualDevice::VNAMeasurement &p) override; void edit() override; Type getType() override {return Type::MatchingNetwork;} diff --git a/Software/PC_Application/VNA/Deembedding/portextension.cpp b/Software/PC_Application/VNA/Deembedding/portextension.cpp index 0b0e2a8..deb4d76 100644 --- a/Software/PC_Application/VNA/Deembedding/portextension.cpp +++ b/Software/PC_Application/VNA/Deembedding/portextension.cpp @@ -24,6 +24,11 @@ PortExtension::PortExtension() kit = nullptr; } +std::set PortExtension::getAffectedPorts() +{ + return {port}; +} + void PortExtension::transformDatapoint(VirtualDevice::VNAMeasurement &d) { auto phase = -2 * M_PI * ext.delay * d.frequency; @@ -72,6 +77,8 @@ void PortExtension::edit() ui->DCloss->setValue(ext.DCloss); ui->Loss->setValue(ext.loss); ui->Frequency->setValue(ext.frequency); + ui->port->setValue(port); + ui->port->setMaximum(VirtualDevice::getInfo(VirtualDevice::getConnected()).ports); if(!kit) { ui->calkit->setEnabled(false); } @@ -97,6 +104,9 @@ void PortExtension::edit() ui->Time->setValueQuiet(ui->Distance->value() / (newval * c)); updateValuesFromUI(); }); + connect(ui->port, qOverload(&QSpinBox::valueChanged), [=](){ + port = ui->port->value(); + }); connect(ui->DCloss, &SIUnitEdit::valueChanged, updateValuesFromUI); connect(ui->Loss, &SIUnitEdit::valueChanged, updateValuesFromUI); connect(ui->Frequency, &SIUnitEdit::valueChanged, updateValuesFromUI); diff --git a/Software/PC_Application/VNA/Deembedding/portextension.h b/Software/PC_Application/VNA/Deembedding/portextension.h index 5013e0b..c6958bd 100644 --- a/Software/PC_Application/VNA/Deembedding/portextension.h +++ b/Software/PC_Application/VNA/Deembedding/portextension.h @@ -18,6 +18,7 @@ class PortExtension : public DeembeddingOption Q_OBJECT public: PortExtension(); + std::set getAffectedPorts() override; void transformDatapoint(VirtualDevice::VNAMeasurement& d) override; void setCalkit(Calkit *kit); Type getType() override {return Type::PortExtension;} diff --git a/Software/PC_Application/VNA/Deembedding/twothru.cpp b/Software/PC_Application/VNA/Deembedding/twothru.cpp index 062298c..f112b37 100644 --- a/Software/PC_Application/VNA/Deembedding/twothru.cpp +++ b/Software/PC_Application/VNA/Deembedding/twothru.cpp @@ -17,6 +17,11 @@ TwoThru::TwoThru() port2 = 2; } +std::set TwoThru::getAffectedPorts() +{ + return {port1, port2}; +} + void TwoThru::transformDatapoint(VirtualDevice::VNAMeasurement &p) { // correct measurement diff --git a/Software/PC_Application/VNA/Deembedding/twothru.h b/Software/PC_Application/VNA/Deembedding/twothru.h index 54b664a..840ae82 100644 --- a/Software/PC_Application/VNA/Deembedding/twothru.h +++ b/Software/PC_Application/VNA/Deembedding/twothru.h @@ -16,6 +16,7 @@ class TwoThru : public DeembeddingOption public: TwoThru(); + std::set getAffectedPorts() override; virtual void transformDatapoint(VirtualDevice::VNAMeasurement& p) override; virtual void edit() override; virtual Type getType() override {return DeembeddingOption::Type::TwoThru;} diff --git a/Software/PC_Application/VNA/s2pImportOptions.ui b/Software/PC_Application/VNA/s2pImportOptions.ui index 2c0b3d2..e4fb3c4 100644 --- a/Software/PC_Application/VNA/s2pImportOptions.ui +++ b/Software/PC_Application/VNA/s2pImportOptions.ui @@ -20,7 +20,7 @@ - You imported a two-port touchstone file, do you want to apply the currently active calibration or de-embed the data? + You imported a touchstone file, do you want to apply the currently active calibration or de-embed the data? true diff --git a/Software/PC_Application/VNA/tracewidgetvna.cpp b/Software/PC_Application/VNA/tracewidgetvna.cpp index 34b08fc..6f403e2 100644 --- a/Software/PC_Application/VNA/tracewidgetvna.cpp +++ b/Software/PC_Application/VNA/tracewidgetvna.cpp @@ -55,6 +55,7 @@ void TraceWidgetVNA::importDialog() if (!filename.isEmpty()) { try { std::vector traces; + int touchstonePorts = 0; QString prefix = QString(); if(filename.endsWith(".csv")) { auto csv = CSV::fromFile(filename); @@ -63,6 +64,7 @@ void TraceWidgetVNA::importDialog() // must be a touchstone file auto t = Touchstone::fromFile(filename.toStdString()); traces = Trace::createFromTouchstone(t); + touchstonePorts = t.ports(); } // contruct prefix from filename prefix = filename; @@ -78,44 +80,51 @@ void TraceWidgetVNA::importDialog() if(AppWindow::showGUI()) { i->show(); } - if(filename.endsWith(".s2p")) { - // potential candidate to process via calibration/de-embedding - connect(i, &TraceImportDialog::importFinsished, [=](const std::vector &traces) { - if(traces.size() == 4) { - // all traces imported, can calculate calibration/de-embedding - bool calAvailable = cal.getNumPoints() > 0; - bool deembedAvailable = deembed.getOptions().size() > 0; - if(calAvailable || deembedAvailable) { - // check if user wants to apply either one to the imported traces - auto dialog = new QDialog(); - auto ui = new Ui::s2pImportOptions; - ui->setupUi(dialog); - connect(dialog, &QDialog::finished, [=](){ - delete ui; - }); - ui->applyCal->setEnabled(calAvailable); - ui->deembed->setEnabled(deembedAvailable); - bool applyCal = false; - bool applyDeembed = false; - connect(ui->applyCal, &QCheckBox::toggled, [&](bool checked) { - applyCal = checked; - }); - connect(ui->deembed, &QCheckBox::toggled, [&](bool checked) { - applyDeembed = checked; - }); - if(AppWindow::showGUI()) { - dialog->exec(); - } - if(applyCal) { -// cal.correctTraces(*traces[0], *traces[1], *traces[2], *traces[3]); // TODO - } - if(applyDeembed) { - deembed.Deembed(*traces[0], *traces[1], *traces[2], *traces[3]); + // potential candidate to process via calibration/de-embedding + connect(i, &TraceImportDialog::importFinsished, [=](const std::vector &traces) { + if(traces.size() == touchstonePorts*touchstonePorts) { + // all traces imported, can calculate calibration/de-embedding + bool calAvailable = cal.getNumPoints() > 0; + bool deembedAvailable = deembed.getOptions().size() > 0; + if(calAvailable || deembedAvailable) { + // check if user wants to apply either one to the imported traces + auto dialog = new QDialog(); + auto ui = new Ui::s2pImportOptions; + ui->setupUi(dialog); + connect(dialog, &QDialog::finished, [=](){ + delete ui; + }); + ui->applyCal->setEnabled(calAvailable); + ui->deembed->setEnabled(deembedAvailable); + bool applyCal = false; + bool applyDeembed = false; + connect(ui->applyCal, &QCheckBox::toggled, [&](bool checked) { + applyCal = checked; + }); + connect(ui->deembed, &QCheckBox::toggled, [&](bool checked) { + applyDeembed = checked; + }); + if(AppWindow::showGUI()) { + dialog->exec(); + } + // assemble trace set + std::map set; + for(int i=1;i<=touchstonePorts;i++) { + for(int j=1;j<=touchstonePorts;j++) { + QString name = "S"+QString::number(i)+QString::number(j); + int index = (i-1)*touchstonePorts+(j-1); + set[name] = traces[index]; } } + if(applyCal) { + cal.correctTraces(set); + } + if(applyDeembed) { + deembed.Deembed(set); + } } - }); - } + } + }); } catch(const std::exception& e) { InformationBox::ShowError("Failed to import file", QString("Attempt to import file ended with error: \"") + e.what()+"\""); } diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index 1c62f1d..5d8f351 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -573,8 +573,23 @@ void AppWindow::SetupSCPI() if(!vdevice) { return QString("0/0/0"); } else if(vdevice->isCompoundDevice()) { - // TODO - return QString(); + // show highest temperature of all devices + int maxTempSource = 0; + int maxTempLO = 0; + int maxTempMCU = 0; + for(auto dev : vdevice->getDevices()) { + auto status = dev->StatusV1(); + if(status.temp_source > maxTempSource) { + maxTempSource = status.temp_source; + } + if(status.temp_LO1 > maxTempLO) { + maxTempLO = status.temp_LO1; + } + if(status.temp_MCU > maxTempMCU) { + maxTempMCU = status.temp_MCU; + } + } + return QString::number(maxTempSource)+"/"+QString::number(maxTempLO)+"/"+QString::number(maxTempMCU); } else { auto dev = vdevice->getDevice(); return QString::number(dev->StatusV1().temp_source)+"/"+QString::number(dev->StatusV1().temp_LO1)+"/"+QString::number(dev->StatusV1().temp_MCU); diff --git a/Software/PC_Application/icons.qrc b/Software/PC_Application/icons.qrc index eb88c3a..58f230d 100644 --- a/Software/PC_Application/icons.qrc +++ b/Software/PC_Application/icons.qrc @@ -67,5 +67,7 @@ icons/compound_V1_Ref_Middle.png icons/compound_V1_Ref_Right.png icons/compound_V1_USB.png + icons/DUT_onePort.png + icons/port.png diff --git a/Software/PC_Application/icons/DUT2.png b/Software/PC_Application/icons/DUT2.png deleted file mode 100644 index 2050daa9143e26b85b7effb40799c5461c6af659..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^g&@obBp6Py7S;kOmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5yxS$b1ju7A z@$_|Nf5yQoD5spTG!JM1gS4lMV~EA+UO_QmvAUQh^kMk%5tsu7QQFfoX`Lsg<#Tm9eR|fsvJgft|;0FBA>A b`6-!cmAEzdi!y%$YGCkm^>bP0l+XkK!lOnl diff --git a/Software/PC_Application/icons/DUT_onePort.png b/Software/PC_Application/icons/DUT_onePort.png new file mode 100644 index 0000000000000000000000000000000000000000..b34e0a47b44f85dbb63c3822578bef182b119e26 GIT binary patch literal 909 zcmeAS@N?(olHy`uVBq!ia0vp^(?OVn2}J&l(*shR1s;*b3=Dj8K$wwzO7L9<24;It z7srr_TW{~~^)@PGIPlT>{0i&Vm{~l`?F;@f$7b&i?GU&dQ?%h<^MU3(1EqqD4JSi6 z7H!MO=9+uNQ#FI5%2D}#li&P@a&mmHDyw#=?Ed_F?w_kGE9Kummn_%nb5m}Vnb#iL zptR6KL#PXgT9g)cAag8SD|6$6MO?$~-`HIKqV@al_mht8OEg&gX%|RCixNnKhsGjg zj$d#b-=2czMQ<1DJxF&`72$LRBBzM~E)xS(L^zMWJn{O%{@*`;-n5!CfBts9>b>)h zKNfWT`1Y-Dph%#I_1r!c&%+N3^78y9sfeiGFR-w&v^<%%`Sj`2A1i7MB<|e1x9xUr z?Df=0F_wp)K7IP|VMEyJ%vswuZd|D0wKC+^-MhYOlcS@fcgC!{nBk%{v2OqR%qXk5 zaWOGE(@)#YUmu`hF{vEv0dN2YL(ZdlA4-7+gZyGEA!K>i)eg(4YzI?edQDSAtDVcR^*WSHWt@%!)Z-@SWs>d!}y4n6+)cTY@2goaqRwYBw3pJlh-#>K|w z=I2M}KY8(D#m$^^=gz%*|9<_NHA{k4&YUUPuGJN9$@cEKeRq6({OYT#bWX2cy_)a9 zhTUGV>t1Z|J@roO>Hddr?h0U}29%_M!U3m&OFMpFl6+XR^cerizt5Td4_lr;vjR(+ zLeYrAfu^J{cHifu-+o(mzkd2}`=ZO7$7-CnNPpV(Ua|JqOu={6k2dLUDtA7!;=cX* yU8Ro_-BQ7c%t;WG$W%lEfr-o&!ddeFt++)(v-hb^&40lB#o+1c=d#Wzp$PzgEvRk) literal 0 HcmV?d00001 diff --git a/Software/PC_Application/icons/port.png b/Software/PC_Application/icons/port.png new file mode 100644 index 0000000000000000000000000000000000000000..ca722cbdc02671e43acb3ebdc5e7472bb66ca338 GIT binary patch literal 1052 zcmeAS@N?(olHy`uVBq!ia0vp^K0rL3g9%6)@Mui{Qk(@Ik;M!Qe20N}4&yJMm;V_U zn9q5-IEGZ*dV6QT_va`H_7BT@BeJG6-3aP#7AO&1JeiMADeS2IfuzC$5s_V1w=7;P zh!(x#xV)oF*7!%Vz#4^r5lo8|q5`L_ZvrE-cn(HVzIC|9-N*qN?gsh0QeWo9~YvI^<*^VKRI3=FPdex%1~lYlx_+ ztIzxz_~exk%fl~UzPx?=*0`CUpMUesH{tr%(~4H`i>nxUS3}A8Ym+tXJ=_CDJl7r zZRVE>8)s){`RZ@qzFG7=uAJ8~$1goN`0}%7&tAM(ar$XiPR^Q@DSffl_ix%K)h(AmNh&p(fjjC@#m@9{^DrUcnbi7&I8e*dmsfBpCS+Y55cqGMxo zGu31(#Flz4;~T`tkQ)7m-ev zkkHI59L- zGI?W!j#&3iM$N88DxN(XJyafj|D77i*M697_Ee3o9v`*I$I?PCn{2k|W!iTB>ebg@ zg)Y8*t9$27}Y;IG#FrB(}ed|-gP=9bbwraN5v(uRg^k-+TE;OXk;vd$@? F2>?o=@e}|6 literal 0 HcmV?d00001