optional 2xthru impedance correction
This commit is contained in:
parent
0f7c397a8a
commit
7352ad07b5
@ -258,6 +258,7 @@ FORMS += \
|
|||||||
VNA/Deembedding/deembeddingdialog.ui \
|
VNA/Deembedding/deembeddingdialog.ui \
|
||||||
VNA/Deembedding/form.ui \
|
VNA/Deembedding/form.ui \
|
||||||
VNA/Deembedding/matchingnetworkdialog.ui \
|
VNA/Deembedding/matchingnetworkdialog.ui \
|
||||||
|
VNA/Deembedding/measurementdialog.ui \
|
||||||
VNA/Deembedding/portextensioneditdialog.ui \
|
VNA/Deembedding/portextensioneditdialog.ui \
|
||||||
VNA/Deembedding/twothrudialog.ui \
|
VNA/Deembedding/twothrudialog.ui \
|
||||||
main.ui \
|
main.ui \
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "deembedding.h"
|
#include "deembedding.h"
|
||||||
#include "deembeddingdialog.h"
|
#include "deembeddingdialog.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include "ui_measurementdialog.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@ -10,9 +11,259 @@ void Deembedding::configure()
|
|||||||
d->show();
|
d->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Deembedding::measurementCompleted()
|
||||||
|
{
|
||||||
|
// pass on the measurement result to the option that triggered the measurement
|
||||||
|
if (measuringOption) {
|
||||||
|
measuringOption->measurementCompleted(measurements);
|
||||||
|
measuringOption = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete measurementDialog;
|
||||||
|
measurementDialog = nullptr;
|
||||||
|
measurementUI = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Deembedding::setInitialTraceSelections()
|
||||||
|
{
|
||||||
|
// all checkboxes initially set to none
|
||||||
|
measurementUI->cS11->blockSignals(true);
|
||||||
|
measurementUI->cS12->blockSignals(true);
|
||||||
|
measurementUI->cS21->blockSignals(true);
|
||||||
|
measurementUI->cS22->blockSignals(true);
|
||||||
|
measurementUI->cS11->clear();
|
||||||
|
measurementUI->cS12->clear();
|
||||||
|
measurementUI->cS21->clear();
|
||||||
|
measurementUI->cS22->clear();
|
||||||
|
measurementUI->cS11->addItem("None");
|
||||||
|
measurementUI->cS12->addItem("None");
|
||||||
|
measurementUI->cS21->addItem("None");
|
||||||
|
measurementUI->cS22->addItem("None");
|
||||||
|
// add applicable traces
|
||||||
|
for(auto t : tm.getTraces()) {
|
||||||
|
if(t->isReflection()) {
|
||||||
|
measurementUI->cS11->addItem(t->name(), QVariant::fromValue<Trace*>(t));
|
||||||
|
measurementUI->cS22->addItem(t->name(), QVariant::fromValue<Trace*>(t));
|
||||||
|
} else {
|
||||||
|
measurementUI->cS12->addItem(t->name(), QVariant::fromValue<Trace*>(t));
|
||||||
|
measurementUI->cS21->addItem(t->name(), QVariant::fromValue<Trace*>(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
measurementUI->cS11->blockSignals(false);
|
||||||
|
measurementUI->cS12->blockSignals(false);
|
||||||
|
measurementUI->cS21->blockSignals(false);
|
||||||
|
measurementUI->cS22->blockSignals(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Deembedding::traceSelectionChanged(QComboBox *w)
|
||||||
|
{
|
||||||
|
vector<QComboBox*> cbs;
|
||||||
|
if (measurementUI->cS11->isVisible()) {
|
||||||
|
cbs.push_back(measurementUI->cS11);
|
||||||
|
}
|
||||||
|
if (measurementUI->cS12->isVisible()) {
|
||||||
|
cbs.push_back(measurementUI->cS12);
|
||||||
|
}
|
||||||
|
if (measurementUI->cS21->isVisible()) {
|
||||||
|
cbs.push_back(measurementUI->cS21);
|
||||||
|
}
|
||||||
|
if (measurementUI->cS22->isVisible()) {
|
||||||
|
cbs.push_back(measurementUI->cS22);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update available traces in combo boxes
|
||||||
|
if(w->currentIndex() != 0 && points == 0) {
|
||||||
|
// the first trace has been selected, extract frequency info
|
||||||
|
Trace *t = qvariant_cast<Trace*>(w->itemData(w->currentIndex()));
|
||||||
|
points = t->size();
|
||||||
|
if(points > 0) {
|
||||||
|
minFreq = t->minX();
|
||||||
|
maxFreq = t->maxX();
|
||||||
|
}
|
||||||
|
// remove all trace options with incompatible frequencies
|
||||||
|
for(auto c : cbs) {
|
||||||
|
for(int i=1;i<c->count();i++) {
|
||||||
|
Trace *t = qvariant_cast<Trace*>(c->itemData(i));
|
||||||
|
if(t->size() != points || (points > 0 && (t->minX() != minFreq || t->maxX() != maxFreq))) {
|
||||||
|
// this trace is not available anymore
|
||||||
|
c->removeItem(i);
|
||||||
|
// decrement to check the next index in the next loop iteration
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(w->currentIndex() == 0 && points > 0) {
|
||||||
|
measurementUI->buttonBox->setEnabled(false);
|
||||||
|
// Check if all trace selections are set for none
|
||||||
|
for(auto c : cbs) {
|
||||||
|
if(c->currentIndex() != 0) {
|
||||||
|
// some trace is still selected, abort
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// all traces set for none
|
||||||
|
points = 0;
|
||||||
|
minFreq = 0;
|
||||||
|
maxFreq = 0;
|
||||||
|
setInitialTraceSelections();
|
||||||
|
}
|
||||||
|
bool allSelectionsValid = true;
|
||||||
|
for(auto c : cbs) {
|
||||||
|
if (c->currentIndex() == 0) {
|
||||||
|
allSelectionsValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(allSelectionsValid) {
|
||||||
|
measurementUI->buttonBox->setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Deembedding::startMeasurementDialog(bool S11, bool S12, bool S21, bool S22)
|
||||||
|
{
|
||||||
|
measurements.clear();
|
||||||
|
|
||||||
|
points = 0;
|
||||||
|
minFreq = 0.0;
|
||||||
|
maxFreq = 0.0;
|
||||||
|
|
||||||
|
measurementDialog = new QDialog;
|
||||||
|
auto ui = new Ui_DeembeddingMeasurementDialog;
|
||||||
|
measurementUI = ui;
|
||||||
|
ui->setupUi(measurementDialog);
|
||||||
|
// disable not needed GUI elements
|
||||||
|
if(!S11) {
|
||||||
|
ui->lS11->setVisible(false);
|
||||||
|
ui->cS11->setVisible(false);
|
||||||
|
}
|
||||||
|
if(!S12) {
|
||||||
|
ui->lS12->setVisible(false);
|
||||||
|
ui->cS12->setVisible(false);
|
||||||
|
}
|
||||||
|
if(!S21) {
|
||||||
|
ui->lS21->setVisible(false);
|
||||||
|
ui->cS21->setVisible(false);
|
||||||
|
}
|
||||||
|
if(!S22) {
|
||||||
|
ui->lS22->setVisible(false);
|
||||||
|
ui->cS22->setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(ui->bMeasure, &QPushButton::clicked, [=](){
|
||||||
|
ui->bMeasure->setEnabled(false);
|
||||||
|
ui->cS11->setEnabled(false);
|
||||||
|
ui->cS12->setEnabled(false);
|
||||||
|
ui->cS21->setEnabled(false);
|
||||||
|
ui->cS22->setEnabled(false);
|
||||||
|
ui->buttonBox->setEnabled(false);
|
||||||
|
measuring = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->cS11, qOverload<int>(&QComboBox::currentIndexChanged), [=](int){
|
||||||
|
traceSelectionChanged(ui->cS11);
|
||||||
|
});
|
||||||
|
connect(ui->cS12, qOverload<int>(&QComboBox::currentIndexChanged), [=](int){
|
||||||
|
traceSelectionChanged(ui->cS12);
|
||||||
|
});
|
||||||
|
connect(ui->cS21, qOverload<int>(&QComboBox::currentIndexChanged), [=](int){
|
||||||
|
traceSelectionChanged(ui->cS21);
|
||||||
|
});
|
||||||
|
connect(ui->cS22, qOverload<int>(&QComboBox::currentIndexChanged), [=](int){
|
||||||
|
traceSelectionChanged(ui->cS22);
|
||||||
|
});
|
||||||
|
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
|
||||||
|
// create datapoints from individual traces
|
||||||
|
measurements.clear();
|
||||||
|
Trace *S11 = nullptr, *S12 = nullptr, *S21 = nullptr, *S22 = nullptr;
|
||||||
|
if (ui->cS11->currentIndex() != 0) {
|
||||||
|
S11 = qvariant_cast<Trace*>(ui->cS11->itemData(ui->cS11->currentIndex()));
|
||||||
|
}
|
||||||
|
if (ui->cS12->currentIndex() != 0) {
|
||||||
|
S12 = qvariant_cast<Trace*>(ui->cS12->itemData(ui->cS12->currentIndex()));
|
||||||
|
}
|
||||||
|
if (ui->cS21->currentIndex() != 0) {
|
||||||
|
S21 = qvariant_cast<Trace*>(ui->cS21->itemData(ui->cS21->currentIndex()));
|
||||||
|
}
|
||||||
|
if (ui->cS22->currentIndex() != 0) {
|
||||||
|
S22 = qvariant_cast<Trace*>(ui->cS22->itemData(ui->cS22->currentIndex()));
|
||||||
|
}
|
||||||
|
for(unsigned int i=0;i<points;i++) {
|
||||||
|
Protocol::Datapoint p;
|
||||||
|
p.pointNum = i;
|
||||||
|
if(S11) {
|
||||||
|
auto sample = S11->sample(i);
|
||||||
|
p.imag_S11 = sample.y.imag();
|
||||||
|
p.real_S11 = sample.y.real();
|
||||||
|
p.frequency = sample.x;
|
||||||
|
}
|
||||||
|
if(S12) {
|
||||||
|
auto sample = S12->sample(i);
|
||||||
|
p.imag_S12 = sample.y.imag();
|
||||||
|
p.real_S12 = sample.y.real();
|
||||||
|
p.frequency = sample.x;
|
||||||
|
}
|
||||||
|
if(S21) {
|
||||||
|
auto sample = S21->sample(i);
|
||||||
|
p.imag_S21 = sample.y.imag();
|
||||||
|
p.real_S21 = sample.y.real();
|
||||||
|
p.frequency = sample.x;
|
||||||
|
}
|
||||||
|
if(S22) {
|
||||||
|
auto sample = S22->sample(i);
|
||||||
|
p.imag_S22 = sample.y.imag();
|
||||||
|
p.real_S22 = sample.y.real();
|
||||||
|
p.frequency = sample.x;
|
||||||
|
}
|
||||||
|
measurements.push_back(p);
|
||||||
|
}
|
||||||
|
measurementCompleted();
|
||||||
|
});
|
||||||
|
|
||||||
|
setInitialTraceSelections();
|
||||||
|
|
||||||
|
measurementDialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
Deembedding::Deembedding(TraceModel &tm)
|
||||||
|
: tm(tm),
|
||||||
|
measuring(false),
|
||||||
|
points(0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void Deembedding::Deembed(Protocol::Datapoint &d)
|
void Deembedding::Deembed(Protocol::Datapoint &d)
|
||||||
{
|
{
|
||||||
|
// figure out the point in one sweep based on the incomig pointNums
|
||||||
|
static unsigned lastPointNum;
|
||||||
|
if (d.pointNum == 0) {
|
||||||
|
sweepPoints = lastPointNum;
|
||||||
|
} else if(d.pointNum > sweepPoints) {
|
||||||
|
sweepPoints = d.pointNum;
|
||||||
|
}
|
||||||
|
lastPointNum = d.pointNum;
|
||||||
|
|
||||||
for(auto it = options.begin();it != options.end();it++) {
|
for(auto it = options.begin();it != options.end();it++) {
|
||||||
|
if (measuring && measuringOption == *it) {
|
||||||
|
// this option needs a measurement
|
||||||
|
if (d.pointNum == 0) {
|
||||||
|
if(measurements.size() == 0) {
|
||||||
|
// this is the first point of the measurement
|
||||||
|
measurements.push_back(d);
|
||||||
|
} else {
|
||||||
|
// this is the first point of the next sweep, measurement complete
|
||||||
|
measuring = false;
|
||||||
|
measurementCompleted();
|
||||||
|
}
|
||||||
|
} else if(measurements.size() > 0) {
|
||||||
|
// in the middle of the measurement, add point
|
||||||
|
measurements.push_back(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(measurementUI) {
|
||||||
|
measurementUI->progress->setValue(100 * measurements.size() / sweepPoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
(*it)->transformDatapoint(d);
|
(*it)->transformDatapoint(d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,6 +286,10 @@ void Deembedding::addOption(DeembeddingOption *option)
|
|||||||
options.erase(pos);
|
options.erase(pos);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
connect(option, &DeembeddingOption::triggerMeasurement, [=](bool S11, bool S12, bool S21, bool S22) {
|
||||||
|
measuringOption = option;
|
||||||
|
startMeasurementDialog(S11, S12, S21, S22);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Deembedding::swapOptions(unsigned int index)
|
void Deembedding::swapOptions(unsigned int index)
|
||||||
|
@ -5,12 +5,17 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "savable.h"
|
#include "savable.h"
|
||||||
|
#include "Traces/tracemodel.h"
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QComboBox>
|
||||||
|
|
||||||
|
class Ui_DeembeddingMeasurementDialog;
|
||||||
|
|
||||||
class Deembedding : public QObject, public Savable
|
class Deembedding : public QObject, public Savable
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Deembedding(){};
|
Deembedding(TraceModel &tm);
|
||||||
~Deembedding(){};
|
~Deembedding(){};
|
||||||
|
|
||||||
void Deembed(Protocol::Datapoint &d);
|
void Deembed(Protocol::Datapoint &d);
|
||||||
@ -23,9 +28,27 @@ public:
|
|||||||
void fromJSON(nlohmann::json j) override;
|
void fromJSON(nlohmann::json j) override;
|
||||||
public slots:
|
public slots:
|
||||||
void configure();
|
void configure();
|
||||||
|
signals:
|
||||||
|
void triggerMeasurement(bool S11 = true, bool S12 = true, bool S21 = true, bool S22 = true);
|
||||||
private:
|
private:
|
||||||
|
void measurementCompleted();
|
||||||
|
void setInitialTraceSelections();
|
||||||
|
void traceSelectionChanged(QComboBox *w);
|
||||||
|
void startMeasurementDialog(bool S11, bool S12, bool S21, bool S22);
|
||||||
std::vector<DeembeddingOption*> options;
|
std::vector<DeembeddingOption*> options;
|
||||||
|
DeembeddingOption *measuringOption;
|
||||||
|
TraceModel &tm;
|
||||||
|
|
||||||
|
bool measuring;
|
||||||
|
std::vector<Protocol::Datapoint> measurements;
|
||||||
|
QDialog *measurementDialog;
|
||||||
|
Ui_DeembeddingMeasurementDialog *measurementUI;
|
||||||
|
// parameters of the selected traces for the measurement
|
||||||
|
double minFreq, maxFreq;
|
||||||
|
unsigned long points;
|
||||||
|
|
||||||
|
unsigned long sweepPoints;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DEEMBEDDING_H
|
#endif // DEEMBEDDING_H
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>De-embedding</string>
|
<string>De-embedding</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
@ -23,9 +23,14 @@ public:
|
|||||||
virtual void transformDatapoint(Protocol::Datapoint &p) = 0;
|
virtual void transformDatapoint(Protocol::Datapoint &p) = 0;
|
||||||
virtual void edit(){};
|
virtual void edit(){};
|
||||||
virtual Type getType() = 0;
|
virtual Type getType() = 0;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
virtual void measurementCompleted(std::vector<Protocol::Datapoint> m){Q_UNUSED(m)};
|
||||||
signals:
|
signals:
|
||||||
// Deembedding option may selfdestruct if not applicable with current settings. It should emit this signal before deleting itself
|
// Deembedding option may selfdestruct if not applicable with current settings. It should emit this signal before deleting itself
|
||||||
void deleted(DeembeddingOption *option);
|
void deleted(DeembeddingOption *option);
|
||||||
|
|
||||||
|
void triggerMeasurement(bool S11 = true, bool S12 = true, bool S21 = true, bool S22 = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DEEMBEDDING_H
|
#endif // DEEMBEDDING_H
|
||||||
|
@ -67,6 +67,7 @@ void MatchingNetwork::edit()
|
|||||||
auto dialog = new QDialog();
|
auto dialog = new QDialog();
|
||||||
auto ui = new Ui::MatchingNetworkDialog();
|
auto ui = new Ui::MatchingNetworkDialog();
|
||||||
ui->setupUi(dialog);
|
ui->setupUi(dialog);
|
||||||
|
dialog->setModal(true);
|
||||||
|
|
||||||
graph = new QWidget();
|
graph = new QWidget();
|
||||||
ui->scrollArea->setWidget(graph);
|
ui->scrollArea->setWidget(graph);
|
||||||
|
144
Software/PC_Application/VNA/Deembedding/measurementdialog.ui
Normal file
144
Software/PC_Application/VNA/Deembedding/measurementdialog.ui
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>DeembeddingMeasurementDialog</class>
|
||||||
|
<widget class="QDialog" name="DeembeddingMeasurementDialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::ApplicationModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>582</width>
|
||||||
|
<height>228</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>De-embedding Measurement</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Either take a new measurement with the currently active frequency/span settings...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progress">
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="bMeasure">
|
||||||
|
<property name="text">
|
||||||
|
<string>Measure</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="line">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>... or select already existing traces for the measurement:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="lS11">
|
||||||
|
<property name="text">
|
||||||
|
<string>S11:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="cS11"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="lS12">
|
||||||
|
<property name="text">
|
||||||
|
<string>S12:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="cS12"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="lS21">
|
||||||
|
<property name="text">
|
||||||
|
<string>S21:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="cS21"/>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="lS22">
|
||||||
|
<property name="text">
|
||||||
|
<string>S22:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QComboBox" name="cS22"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -22,111 +22,11 @@ PortExtension::PortExtension()
|
|||||||
port2.delay = 0;
|
port2.delay = 0;
|
||||||
port2.velocityFactor = 0.66;
|
port2.velocityFactor = 0.66;
|
||||||
|
|
||||||
measuring = false;
|
|
||||||
kit = nullptr;
|
kit = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortExtension::transformDatapoint(Protocol::Datapoint &d)
|
void PortExtension::transformDatapoint(Protocol::Datapoint &d)
|
||||||
{
|
{
|
||||||
if(measuring) {
|
|
||||||
if(measurements.size() > 0) {
|
|
||||||
if(d.pointNum == 0) {
|
|
||||||
// sweep complete, evaluate measurement
|
|
||||||
|
|
||||||
double last_phase = 0.0;
|
|
||||||
double phasediff_sum = 0.0;
|
|
||||||
vector<double> att_x, att_y;
|
|
||||||
double avg_x = 0.0, avg_y = 0.0;
|
|
||||||
for(auto m : measurements) {
|
|
||||||
// grab correct measurement
|
|
||||||
complex<double> reflection;
|
|
||||||
if(isPort1) {
|
|
||||||
reflection = complex<double>(m.real_S11, m.imag_S11);
|
|
||||||
} else {
|
|
||||||
reflection = complex<double>(m.real_S22, m.imag_S22);
|
|
||||||
}
|
|
||||||
// remove calkit if specified
|
|
||||||
if(!isIdeal) {
|
|
||||||
complex<double> calStandard;
|
|
||||||
auto standards = kit->toSOLT(m.frequency);
|
|
||||||
if(isOpen) {
|
|
||||||
calStandard = standards.Open;
|
|
||||||
} else {
|
|
||||||
calStandard = standards.Short;
|
|
||||||
}
|
|
||||||
// remove effect of calibration standard
|
|
||||||
reflection /= calStandard;
|
|
||||||
}
|
|
||||||
// sum phase differences to previous point
|
|
||||||
auto phase = arg(reflection);
|
|
||||||
if(m.pointNum == 0) {
|
|
||||||
last_phase = phase;
|
|
||||||
} else {
|
|
||||||
auto phasediff = phase - last_phase;
|
|
||||||
last_phase = phase;
|
|
||||||
if(phasediff > M_PI) {
|
|
||||||
phasediff -= 2 * M_PI;
|
|
||||||
} else if(phasediff <= -M_PI) {
|
|
||||||
phasediff += 2 * M_PI;
|
|
||||||
}
|
|
||||||
phasediff_sum += phasediff;
|
|
||||||
qDebug() << phasediff;
|
|
||||||
}
|
|
||||||
|
|
||||||
double x = sqrt(m.frequency / measurements.back().frequency);
|
|
||||||
double y = 20*log10(abs(reflection));
|
|
||||||
att_x.push_back(x);
|
|
||||||
att_y.push_back(y);
|
|
||||||
avg_x += x;
|
|
||||||
avg_y += y;
|
|
||||||
}
|
|
||||||
auto phase = phasediff_sum / (measurements.size() - 1);
|
|
||||||
auto freq_diff = measurements[1].frequency - measurements[0].frequency;
|
|
||||||
auto delay = -phase / (2 * M_PI * freq_diff);
|
|
||||||
// measured delay is two-way but port extension expects one-way
|
|
||||||
delay /= 2;
|
|
||||||
|
|
||||||
// calculate linear regression with transformed square root model
|
|
||||||
avg_x /= measurements.size();
|
|
||||||
avg_y /= measurements.size();
|
|
||||||
double sum_top = 0.0;
|
|
||||||
double sum_bottom = 0.0;
|
|
||||||
for(unsigned int i=0;i<att_x.size();i++) {
|
|
||||||
sum_top += (att_x[i] - avg_x)*(att_y[i] - avg_y);
|
|
||||||
sum_bottom += (att_x[i] - avg_x)*(att_x[i] - avg_x);
|
|
||||||
}
|
|
||||||
double beta = sum_top / sum_bottom;
|
|
||||||
double alpha = avg_y - beta * avg_x;
|
|
||||||
|
|
||||||
double DCloss = -alpha / 2;
|
|
||||||
double loss = -beta / 2;
|
|
||||||
double freq = measurements.back().frequency;
|
|
||||||
if(isPort1) {
|
|
||||||
ui->P1Time->setValue(delay);
|
|
||||||
ui->P1DCloss->setValue(DCloss);
|
|
||||||
ui->P1Loss->setValue(loss);
|
|
||||||
ui->P1Frequency->setValue(freq);
|
|
||||||
} else {
|
|
||||||
ui->P2Time->setValue(delay);
|
|
||||||
ui->P2DCloss->setValue(DCloss);
|
|
||||||
ui->P2Loss->setValue(loss);
|
|
||||||
ui->P2Frequency->setValue(freq);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(msgBox) {
|
|
||||||
msgBox->close();
|
|
||||||
msgBox = nullptr;
|
|
||||||
}
|
|
||||||
measurements.clear();
|
|
||||||
} else {
|
|
||||||
measurements.push_back(d);
|
|
||||||
}
|
|
||||||
} else if(d.pointNum == 0) {
|
|
||||||
// first point of sweep, start measurement
|
|
||||||
measurements.push_back(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(port1.enabled || port2.enabled) {
|
if(port1.enabled || port2.enabled) {
|
||||||
// Convert measurements to complex variables
|
// Convert measurements to complex variables
|
||||||
auto S11 = complex<double>(d.real_S11, d.imag_S11);
|
auto S11 = complex<double>(d.real_S11, d.imag_S11);
|
||||||
@ -299,16 +199,94 @@ void PortExtension::edit()
|
|||||||
dialog->show();
|
dialog->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PortExtension::measurementCompleted(std::vector<Protocol::Datapoint> m)
|
||||||
|
{
|
||||||
|
if(m.size() > 0) {
|
||||||
|
double last_phase = 0.0;
|
||||||
|
double phasediff_sum = 0.0;
|
||||||
|
vector<double> att_x, att_y;
|
||||||
|
double avg_x = 0.0, avg_y = 0.0;
|
||||||
|
for(auto p : m) {
|
||||||
|
// grab correct measurement
|
||||||
|
complex<double> reflection;
|
||||||
|
if(isPort1) {
|
||||||
|
reflection = complex<double>(p.real_S11, p.imag_S11);
|
||||||
|
} else {
|
||||||
|
reflection = complex<double>(p.real_S22, p.imag_S22);
|
||||||
|
}
|
||||||
|
// remove calkit if specified
|
||||||
|
if(!isIdeal) {
|
||||||
|
complex<double> calStandard;
|
||||||
|
auto standards = kit->toSOLT(p.frequency);
|
||||||
|
if(isOpen) {
|
||||||
|
calStandard = standards.Open;
|
||||||
|
} else {
|
||||||
|
calStandard = standards.Short;
|
||||||
|
}
|
||||||
|
// remove effect of calibration standard
|
||||||
|
reflection /= calStandard;
|
||||||
|
}
|
||||||
|
// sum phase differences to previous point
|
||||||
|
auto phase = arg(reflection);
|
||||||
|
if(p.pointNum == 0) {
|
||||||
|
last_phase = phase;
|
||||||
|
} else {
|
||||||
|
auto phasediff = phase - last_phase;
|
||||||
|
last_phase = phase;
|
||||||
|
if(phasediff > M_PI) {
|
||||||
|
phasediff -= 2 * M_PI;
|
||||||
|
} else if(phasediff <= -M_PI) {
|
||||||
|
phasediff += 2 * M_PI;
|
||||||
|
}
|
||||||
|
phasediff_sum += phasediff;
|
||||||
|
qDebug() << phasediff;
|
||||||
|
}
|
||||||
|
|
||||||
|
double x = sqrt(p.frequency / m.back().frequency);
|
||||||
|
double y = 20*log10(abs(reflection));
|
||||||
|
att_x.push_back(x);
|
||||||
|
att_y.push_back(y);
|
||||||
|
avg_x += x;
|
||||||
|
avg_y += y;
|
||||||
|
}
|
||||||
|
auto phase = phasediff_sum / (m.size() - 1);
|
||||||
|
auto freq_diff = m[1].frequency - m[0].frequency;
|
||||||
|
auto delay = -phase / (2 * M_PI * freq_diff);
|
||||||
|
// measured delay is two-way but port extension expects one-way
|
||||||
|
delay /= 2;
|
||||||
|
|
||||||
|
// calculate linear regression with transformed square root model
|
||||||
|
avg_x /= m.size();
|
||||||
|
avg_y /= m.size();
|
||||||
|
double sum_top = 0.0;
|
||||||
|
double sum_bottom = 0.0;
|
||||||
|
for(unsigned int i=0;i<att_x.size();i++) {
|
||||||
|
sum_top += (att_x[i] - avg_x)*(att_y[i] - avg_y);
|
||||||
|
sum_bottom += (att_x[i] - avg_x)*(att_x[i] - avg_x);
|
||||||
|
}
|
||||||
|
double beta = sum_top / sum_bottom;
|
||||||
|
double alpha = avg_y - beta * avg_x;
|
||||||
|
|
||||||
|
double DCloss = -alpha / 2;
|
||||||
|
double loss = -beta / 2;
|
||||||
|
double freq = m.back().frequency;
|
||||||
|
if(isPort1) {
|
||||||
|
ui->P1Time->setValue(delay);
|
||||||
|
ui->P1DCloss->setValue(DCloss);
|
||||||
|
ui->P1Loss->setValue(loss);
|
||||||
|
ui->P1Frequency->setValue(freq);
|
||||||
|
} else {
|
||||||
|
ui->P2Time->setValue(delay);
|
||||||
|
ui->P2DCloss->setValue(DCloss);
|
||||||
|
ui->P2Loss->setValue(loss);
|
||||||
|
ui->P2Frequency->setValue(freq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PortExtension::startMeasurement()
|
void PortExtension::startMeasurement()
|
||||||
{
|
{
|
||||||
measurements.clear();
|
emit triggerMeasurement(isPort1, false, false, !isPort1);
|
||||||
msgBox = new QMessageBox(QMessageBox::Information, "Auto port extension", "Taking measurement...", QMessageBox::Cancel);
|
|
||||||
connect(msgBox, &QMessageBox::rejected, [=]() {
|
|
||||||
measuring = false;
|
|
||||||
measurements.clear();
|
|
||||||
});
|
|
||||||
msgBox->show();
|
|
||||||
measuring = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortExtension::setCalkit(Calkit *kit)
|
void PortExtension::setCalkit(Calkit *kit)
|
||||||
|
@ -24,6 +24,7 @@ public:
|
|||||||
void fromJSON(nlohmann::json j) override;
|
void fromJSON(nlohmann::json j) override;
|
||||||
public slots:
|
public slots:
|
||||||
void edit() override;
|
void edit() override;
|
||||||
|
void measurementCompleted(std::vector<Protocol::Datapoint> m) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startMeasurement();
|
void startMeasurement();
|
||||||
@ -40,11 +41,11 @@ private:
|
|||||||
|
|
||||||
// status variables for automatic measurements
|
// status variables for automatic measurements
|
||||||
Calkit *kit;
|
Calkit *kit;
|
||||||
bool measuring;
|
// bool measuring;
|
||||||
bool isPort1;
|
bool isPort1;
|
||||||
bool isOpen;
|
bool isOpen;
|
||||||
bool isIdeal;
|
bool isIdeal;
|
||||||
std::vector<Protocol::Datapoint> measurements;
|
// std::vector<Protocol::Datapoint> measurements;
|
||||||
QMessageBox *msgBox;
|
QMessageBox *msgBox;
|
||||||
Ui::PortExtensionEditDialog *ui;
|
Ui::PortExtensionEditDialog *ui;
|
||||||
};
|
};
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>PortExtensionEditDialog</class>
|
<class>PortExtensionEditDialog</class>
|
||||||
<widget class="QDialog" name="PortExtensionEditDialog">
|
<widget class="QDialog" name="PortExtensionEditDialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::ApplicationModal</enum>
|
||||||
|
</property>
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>318</width>
|
<width>318</width>
|
||||||
<height>476</height>
|
<height>505</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -2,227 +2,56 @@
|
|||||||
#include "CustomWidgets/informationbox.h"
|
#include "CustomWidgets/informationbox.h"
|
||||||
#include "ui_twothrudialog.h"
|
#include "ui_twothrudialog.h"
|
||||||
#include "Traces/fftcomplex.h"
|
#include "Traces/fftcomplex.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include "unit.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
TwoThru::TwoThru()
|
TwoThru::TwoThru()
|
||||||
{
|
{
|
||||||
measuring = false;
|
Z0 = 50.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwoThru::transformDatapoint(Protocol::Datapoint &p)
|
void TwoThru::transformDatapoint(Protocol::Datapoint &p)
|
||||||
{
|
{
|
||||||
auto S11 = complex<double>(p.real_S11, p.imag_S11);
|
|
||||||
auto S12 = complex<double>(p.real_S12, p.imag_S12);
|
|
||||||
auto S21 = complex<double>(p.real_S21, p.imag_S21);
|
|
||||||
auto S22 = complex<double>(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<complex<double>> S11, S12, S21, S22;
|
|
||||||
vector<double> f;
|
|
||||||
for(auto m : measurements) {
|
|
||||||
if(m.frequency == 0) {
|
|
||||||
// ignore possible DC point
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
S11.push_back(complex<double>(m.real_S11, m.imag_S11));
|
|
||||||
S12.push_back(complex<double>(m.real_S12, m.imag_S12));
|
|
||||||
S21.push_back(complex<double>(m.real_S21, m.imag_S21));
|
|
||||||
S22.push_back(complex<double>(m.real_S22, m.imag_S22));
|
|
||||||
f.push_back(m.frequency);
|
|
||||||
}
|
|
||||||
auto n = f.size();
|
|
||||||
|
|
||||||
auto makeSymmetric = [](const vector<complex<double>> &in) -> vector<complex<double>> {
|
|
||||||
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<complex<double>> 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<complex<double>> &in) {
|
|
||||||
for(unsigned int i=0;i<in.size();i++) {
|
|
||||||
in[i] = real(in[i]) / in.size();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// S parameter error boxes
|
|
||||||
vector<Sparam> 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
|
|
||||||
double threshold = 0.5*real(t212x.back());
|
|
||||||
auto mid = lower_bound(t212x.begin(), t212x.end(), threshold, [](complex<double> p, double c) -> bool {
|
|
||||||
return real(p) < c;
|
|
||||||
}) - t212x.begin();
|
|
||||||
|
|
||||||
// mask step response
|
|
||||||
vector<complex<double>> 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<complex<double>> p221x;
|
|
||||||
vector<complex<double>> p211x;
|
|
||||||
double k = 1.0;
|
|
||||||
complex<double> test, last_test;
|
|
||||||
for(unsigned int i=0;i<p112x.size();i++) {
|
|
||||||
p221x.push_back((p112x[i]-p111x[i])/p212x[i]);
|
|
||||||
test = sqrt(p212x[i]*(1.0-p221x[i]*p221x[i]));
|
|
||||||
if(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
|
|
||||||
double threshold = 0.5*real(t212x.back());
|
|
||||||
auto mid = lower_bound(t212x.begin(), t212x.end(), threshold, [](complex<double> p, double c) -> bool {
|
|
||||||
return real(p) < c;
|
|
||||||
}) - t212x.begin();
|
|
||||||
|
|
||||||
// mask step response
|
|
||||||
vector<complex<double>> 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<complex<double>> p221x;
|
|
||||||
vector<complex<double>> p211x;
|
|
||||||
double k = 1.0;
|
|
||||||
complex<double> test, last_test;
|
|
||||||
for(unsigned int i=0;i<p112x.size();i++) {
|
|
||||||
p221x.push_back((p112x[i]-p111x[i])/p212x[i]);
|
|
||||||
test = sqrt(p212x[i]*(1.0-p221x[i]*p221x[i]));
|
|
||||||
if(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(data_side1[i-1].m22, p211x[i], p211x[i], p111x[i]));
|
|
||||||
data_side1[i-1].m22 = p221x[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// got the error boxes, convert to T parameters and invert
|
|
||||||
for(unsigned int i=0;i<n;i++) {
|
|
||||||
Point p;
|
|
||||||
p.freq = f[i];
|
|
||||||
p.inverseP1 = Tparam(data_side1[i]).inverse();
|
|
||||||
p.inverseP2 = Tparam(data_side2[i]).inverse();
|
|
||||||
points.push_back(p);
|
|
||||||
}
|
|
||||||
measurements.clear();
|
|
||||||
|
|
||||||
if(msgBox) {
|
|
||||||
msgBox->accept();
|
|
||||||
msgBox = nullptr;
|
|
||||||
}
|
|
||||||
updateLabel();
|
|
||||||
} else if(measurements.size() > 0 || p.pointNum == 0) {
|
|
||||||
measurements.push_back(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// correct measurement
|
// correct measurement
|
||||||
if(points.size() > 0) {
|
if(points.size() > 0) {
|
||||||
if(p.frequency != 0 && (p.frequency < points.front().freq || p.frequency > points.back().freq)) {
|
auto S11 = complex<double>(p.real_S11, p.imag_S11);
|
||||||
// No exact match, measurement no longer valid
|
auto S12 = complex<double>(p.real_S12, p.imag_S12);
|
||||||
points.clear();
|
auto S21 = complex<double>(p.real_S21, p.imag_S21);
|
||||||
InformationBox::ShowMessage("Warning", "2xThru measurement cleared because it no longer matches the selected span");
|
auto S22 = complex<double>(p.real_S22, p.imag_S22);
|
||||||
return;
|
Sparam S(S11, S12, S21, S22);
|
||||||
}
|
Tparam meas(S);
|
||||||
// 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;
|
Tparam inv1, inv2;
|
||||||
if(point->freq == p.frequency) {
|
if(p.frequency < points.front().freq) {
|
||||||
inv1 = point->inverseP1;
|
inv1 = points.front().inverseP1;
|
||||||
inv2 = point->inverseP2;
|
inv2 = points.front().inverseP2;
|
||||||
|
} else if(p.frequency > points.back().freq) {
|
||||||
|
inv1 = points.back().inverseP1;
|
||||||
|
inv2 = points.back().inverseP2;
|
||||||
} else {
|
} else {
|
||||||
// need to interpolate
|
// find correct measurement point
|
||||||
auto high = point;
|
auto point = lower_bound(points.begin(), points.end(), p.frequency, [](Point p, uint64_t freq) -> bool {
|
||||||
point--;
|
return p.freq < freq;
|
||||||
auto low = point;
|
});
|
||||||
double alpha = (p.frequency - low->freq) / (high->freq - low->freq);
|
if(point->freq == p.frequency) {
|
||||||
inv1 = low->inverseP1 * (1 - alpha) + high->inverseP1 * alpha;
|
inv1 = point->inverseP1;
|
||||||
inv2 = low->inverseP2 * (1 - alpha) + high->inverseP2 * alpha;
|
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
|
// perform correction
|
||||||
Tparam corrected = inv1*meas*inv2;
|
Tparam corrected = inv1*meas*inv2;
|
||||||
// transform back into S parameters
|
// transform back into S parameters
|
||||||
Sparam S(corrected);
|
S = Sparam(corrected);
|
||||||
p.real_S11 = real(S.m11);
|
p.real_S11 = real(S.m11);
|
||||||
p.imag_S11 = imag(S.m11);
|
p.imag_S11 = imag(S.m11);
|
||||||
p.real_S12 = real(S.m12);
|
p.real_S12 = real(S.m12);
|
||||||
@ -236,27 +65,58 @@ void TwoThru::transformDatapoint(Protocol::Datapoint &p)
|
|||||||
|
|
||||||
void TwoThru::startMeasurement()
|
void TwoThru::startMeasurement()
|
||||||
{
|
{
|
||||||
points.clear();
|
emit triggerMeasurement();
|
||||||
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()
|
void TwoThru::updateGUI()
|
||||||
{
|
{
|
||||||
if(points.size() > 0) {
|
if(measurements2xthru.size() > 0) {
|
||||||
ui->lInfo->setText("Got "+QString::number(points.size())+" points");
|
ui->l2xthru->setText(QString::number(measurements2xthru.size())+" points from "
|
||||||
|
+Unit::ToString(measurements2xthru.front().frequency, "Hz", " kMG", 4)+" to "
|
||||||
|
+Unit::ToString(measurements2xthru.back().frequency, "Hz", " kMG", 4));
|
||||||
} else {
|
} else {
|
||||||
ui->lInfo->setText("No measurement, not deembedding");
|
ui->l2xthru->setText("Not available, not de-embedding");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(measurementsDUT.size() > 0) {
|
||||||
|
ui->lDUT->setText(QString::number(measurementsDUT.size())+" points from "
|
||||||
|
+Unit::ToString(measurementsDUT.front().frequency, "Hz", " kMG", 4)+" to "
|
||||||
|
+Unit::ToString(measurementsDUT.back().frequency, "Hz", " kMG", 4));
|
||||||
|
} else {
|
||||||
|
ui->lDUT->setText("Not available, not de-embedding");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(points.size() > 0) {
|
||||||
|
ui->lPoints->setText(QString::number(points.size())+" points from "
|
||||||
|
+Unit::ToString(points.front().freq, "Hz", " kMG", 4)+" to "
|
||||||
|
+Unit::ToString(points.back().freq, "Hz", " kMG", 4));
|
||||||
|
} else {
|
||||||
|
ui->lPoints->setText("Not available, not de-embedding");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (measurementsDUT.size() > 0 && measurements2xthru.size() > 0) {
|
||||||
|
// correction using both measurements is available
|
||||||
|
ui->Z0->setEnabled(true);
|
||||||
|
ui->bCalc->setEnabled(true);
|
||||||
|
} else if(measurements2xthru.size() > 0) {
|
||||||
|
// simpler correction using only 2xthru measurement available
|
||||||
|
ui->Z0->setEnabled(false);
|
||||||
|
ui->bCalc->setEnabled(true);
|
||||||
|
} else {
|
||||||
|
// no correction available
|
||||||
|
ui->Z0->setEnabled(false);
|
||||||
|
ui->bCalc->setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoThru::measurementCompleted(std::vector<Protocol::Datapoint> m)
|
||||||
|
{
|
||||||
|
if (measuring2xthru) {
|
||||||
|
measurements2xthru = m;
|
||||||
|
} else if(measuringDUT) {
|
||||||
|
measurementsDUT = m;
|
||||||
|
}
|
||||||
|
updateGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwoThru::edit()
|
void TwoThru::edit()
|
||||||
@ -264,11 +124,48 @@ void TwoThru::edit()
|
|||||||
auto dialog = new QDialog();
|
auto dialog = new QDialog();
|
||||||
ui = new Ui::TwoThruDialog();
|
ui = new Ui::TwoThruDialog();
|
||||||
ui->setupUi(dialog);
|
ui->setupUi(dialog);
|
||||||
|
ui->Z0->setUnit("Ω");
|
||||||
|
ui->Z0->setPrecision(4);
|
||||||
|
ui->Z0->setValue(Z0);
|
||||||
|
|
||||||
connect(ui->bMeasure, &QPushButton::clicked, this, &TwoThru::startMeasurement);
|
// choice of Z0 does not seem to make any difference, hide from user
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
|
ui->Z0->setVisible(false);
|
||||||
|
ui->lZ0->setVisible(false);
|
||||||
|
|
||||||
updateLabel();
|
connect(ui->bMeasure, &QPushButton::clicked, [=](){
|
||||||
|
measuringDUT = false;
|
||||||
|
measuring2xthru = true;
|
||||||
|
startMeasurement();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->bMeasureDUT, &QPushButton::clicked, [=](){
|
||||||
|
measuringDUT = true;
|
||||||
|
measuring2xthru = false;
|
||||||
|
startMeasurement();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->bClear, &QPushButton::clicked, [=](){
|
||||||
|
measurements2xthru.clear();
|
||||||
|
updateGUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->bClearDUT, &QPushButton::clicked, [=](){
|
||||||
|
measurementsDUT.clear();
|
||||||
|
updateGUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->bCalc, &QPushButton::clicked, [=](){
|
||||||
|
ui->lPoints->setText("Calculating...");
|
||||||
|
qApp->processEvents();
|
||||||
|
if(measurementsDUT.size() > 0) {
|
||||||
|
points = calculateErrorBoxes(measurements2xthru, measurementsDUT, ui->Z0->value());
|
||||||
|
} else {
|
||||||
|
points = calculateErrorBoxes(measurements2xthru);
|
||||||
|
}
|
||||||
|
updateGUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
updateGUI();
|
||||||
|
|
||||||
dialog->show();
|
dialog->show();
|
||||||
}
|
}
|
||||||
@ -317,3 +214,509 @@ void TwoThru::fromJSON(nlohmann::json j)
|
|||||||
points.push_back(p);
|
points.push_back(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<TwoThru::Point> TwoThru::calculateErrorBoxes(std::vector<Protocol::Datapoint> data_2xthru)
|
||||||
|
{
|
||||||
|
// calculate error boxes, see https://www.freelists.org/post/si-list/IEEE-P370-Opensource-Deembedding-MATLAB-functions
|
||||||
|
// create vectors of S parameters
|
||||||
|
vector<complex<double>> S11, S12, S21, S22;
|
||||||
|
vector<double> f;
|
||||||
|
|
||||||
|
// remove DC point if present
|
||||||
|
if(data_2xthru[0].frequency == 0) {
|
||||||
|
data_2xthru.erase(data_2xthru.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
data_2xthru = interpolateEvenFrequencySteps(data_2xthru);
|
||||||
|
|
||||||
|
for(auto m : data_2xthru) {
|
||||||
|
if(m.frequency == 0) {
|
||||||
|
// ignore possible DC point
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
S11.push_back(complex<double>(m.real_S11, m.imag_S11));
|
||||||
|
S12.push_back(complex<double>(m.real_S12, m.imag_S12));
|
||||||
|
S21.push_back(complex<double>(m.real_S21, m.imag_S21));
|
||||||
|
S22.push_back(complex<double>(m.real_S22, m.imag_S22));
|
||||||
|
f.push_back(m.frequency);
|
||||||
|
}
|
||||||
|
auto n = f.size();
|
||||||
|
|
||||||
|
auto makeSymmetric = [](const vector<complex<double>> &in) -> vector<complex<double>> {
|
||||||
|
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<complex<double>> 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<complex<double>> &in) {
|
||||||
|
for(unsigned int i=0;i<in.size();i++) {
|
||||||
|
in[i] = real(in[i]) / in.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// S parameter error boxes
|
||||||
|
vector<Sparam> 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
|
||||||
|
double threshold = 0.5*real(t212x.back());
|
||||||
|
auto mid = lower_bound(t212x.begin(), t212x.end(), threshold, [](complex<double> p, double c) -> bool {
|
||||||
|
return real(p) < c;
|
||||||
|
}) - t212x.begin();
|
||||||
|
|
||||||
|
// mask step response
|
||||||
|
vector<complex<double>> 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<complex<double>> p221x;
|
||||||
|
vector<complex<double>> p211x;
|
||||||
|
double k = 1.0;
|
||||||
|
complex<double> test, last_test;
|
||||||
|
for(unsigned int i=0;i<p112x.size();i++) {
|
||||||
|
p221x.push_back((p112x[i]-p111x[i])/p212x[i]);
|
||||||
|
test = sqrt(p212x[i]*(1.0-p221x[i]*p221x[i]));
|
||||||
|
if(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
|
||||||
|
double threshold = 0.5*real(t212x.back());
|
||||||
|
auto mid = lower_bound(t212x.begin(), t212x.end(), threshold, [](complex<double> p, double c) -> bool {
|
||||||
|
return real(p) < c;
|
||||||
|
}) - t212x.begin();
|
||||||
|
|
||||||
|
// mask step response
|
||||||
|
vector<complex<double>> 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<complex<double>> p221x;
|
||||||
|
vector<complex<double>> p211x;
|
||||||
|
double k = 1.0;
|
||||||
|
complex<double> test, last_test;
|
||||||
|
for(unsigned int i=0;i<p112x.size();i++) {
|
||||||
|
p221x.push_back((p112x[i]-p111x[i])/p212x[i]);
|
||||||
|
test = sqrt(p212x[i]*(1.0-p221x[i]*p221x[i]));
|
||||||
|
if(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(data_side1[i-1].m22, p211x[i], p211x[i], p111x[i]));
|
||||||
|
data_side1[i-1].m22 = p221x[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// got the error boxes, convert to T parameters and invert
|
||||||
|
vector<Point> ret;
|
||||||
|
for(unsigned int i=0;i<n;i++) {
|
||||||
|
Point p;
|
||||||
|
p.freq = f[i];
|
||||||
|
p.inverseP1 = Tparam(data_side1[i]).inverse();
|
||||||
|
p.inverseP2 = Tparam(data_side2[i]).inverse();
|
||||||
|
ret.push_back(p);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TwoThru::Point> TwoThru::calculateErrorBoxes(std::vector<Protocol::Datapoint> data_2xthru, std::vector<Protocol::Datapoint> data_fix_dut_fix, double z0)
|
||||||
|
{
|
||||||
|
vector<Point> ret;
|
||||||
|
|
||||||
|
if(data_2xthru.size() != data_fix_dut_fix.size()) {
|
||||||
|
InformationBox::ShowMessage("Unable to calculate", "The DUT and 2xthru measurements do not have the same amount of points, calculation not possible");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if frequencies are the same (measurements must be taken with identical span settings)
|
||||||
|
for(unsigned int i=0;i<data_2xthru.size();i++) {
|
||||||
|
if(abs((long int)data_2xthru[i].frequency - (long int)data_fix_dut_fix[i].frequency) > (double) data_2xthru[i].frequency / 1e9) {
|
||||||
|
InformationBox::ShowMessage("Unable to calculate", "The DUT and 2xthru measurements do not have identical frequencies for all points, calculation not possible");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data_2xthru = interpolateEvenFrequencySteps(data_2xthru);
|
||||||
|
data_fix_dut_fix = interpolateEvenFrequencySteps(data_fix_dut_fix);
|
||||||
|
|
||||||
|
// Variable names and order of calulation follows https://gitlab.com/IEEE-SA/ElecChar/P370/-/blob/master/TG1/IEEEP370Zc2xThru_Octave.m as close as possible
|
||||||
|
vector<Sparam> p;
|
||||||
|
vector<double> f;
|
||||||
|
for(auto d : data_2xthru) {
|
||||||
|
p.push_back(Sparam(complex<double>(d.real_S11, d.imag_S11),
|
||||||
|
complex<double>(d.real_S12, d.imag_S12),
|
||||||
|
complex<double>(d.real_S21, d.imag_S21),
|
||||||
|
complex<double>(d.real_S22, d.imag_S22)));
|
||||||
|
f.push_back(d.frequency);
|
||||||
|
}
|
||||||
|
auto data_2xthru_Sparam = p;
|
||||||
|
vector<Sparam> data_fix_dut_fix_Sparam;
|
||||||
|
for(auto d : data_fix_dut_fix) {
|
||||||
|
data_fix_dut_fix_Sparam.push_back(Sparam(complex<double>(d.real_S11, d.imag_S11),
|
||||||
|
complex<double>(d.real_S12, d.imag_S12),
|
||||||
|
complex<double>(d.real_S21, d.imag_S21),
|
||||||
|
complex<double>(d.real_S22, d.imag_S22)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// grabbing S21
|
||||||
|
vector<complex<double>> s212x;
|
||||||
|
for(auto s : p) {
|
||||||
|
s212x.push_back(s.m21);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the attenuation and phase constant per length
|
||||||
|
vector<complex<double>> gamma;
|
||||||
|
double last_angle = 0.0;
|
||||||
|
for(auto s : s212x) {
|
||||||
|
// unwrap phase
|
||||||
|
double angle = arg(s);
|
||||||
|
while(angle - last_angle > M_PI) {
|
||||||
|
angle -= 2 * M_PI;
|
||||||
|
}
|
||||||
|
while(angle - last_angle < -M_PI) {
|
||||||
|
angle += 2 * M_PI;
|
||||||
|
}
|
||||||
|
last_angle = angle;
|
||||||
|
double beta_per_length = -angle;
|
||||||
|
double alpha_per_length = 20 * log10(abs(s))/-8.686;
|
||||||
|
|
||||||
|
// assume no bandwidth limit (==0)
|
||||||
|
gamma.push_back(complex<double>(alpha_per_length, beta_per_length));
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function lambdas
|
||||||
|
auto makeSymmetric = [](const vector<complex<double>> &in) -> vector<complex<double>> {
|
||||||
|
auto ret = in;
|
||||||
|
for(auto it = in.rbegin();it != in.rend();it++) {
|
||||||
|
ret.push_back(conj(*it));
|
||||||
|
}
|
||||||
|
// went one step too far, remove the DC point from the symmetric data
|
||||||
|
ret.pop_back();
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto makeRealAndScale = [](vector<complex<double>> &in) {
|
||||||
|
for(unsigned int i=0;i<in.size();i++) {
|
||||||
|
in[i] = real(in[i]) / in.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto DC2 = [=](const vector<complex<double>> &s, const vector<double> &f) -> complex<double> {
|
||||||
|
auto simple_filter = [](const vector<double> &f, double f0) -> vector<complex<double>> {
|
||||||
|
vector<complex<double>> ret;
|
||||||
|
for(auto v : f) {
|
||||||
|
ret.push_back(1.0/complex<double>(1.0, pow(v/f0, 4)));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
complex<double> DCpoint = 0.002; // seed for the algorithm
|
||||||
|
double err = 1; // error seed
|
||||||
|
double allowedError = 1e-10; // allowable error
|
||||||
|
long cnt = 0;
|
||||||
|
auto df = f[1] - f[0];
|
||||||
|
auto n = f.size();
|
||||||
|
unsigned int ts = round((-3e-9) / ((2.0/df)/(n*2+1)) + (n*2+1)/2);
|
||||||
|
auto Hr = simple_filter(f, f.back()/2);
|
||||||
|
while (err > allowedError) {
|
||||||
|
vector<complex<double>> f1;
|
||||||
|
f1.push_back(DCpoint);
|
||||||
|
for(unsigned int i=0;i<n;i++) {
|
||||||
|
f1.push_back(s[i] * Hr[i]);
|
||||||
|
}
|
||||||
|
auto h1 = makeSymmetric(f1);
|
||||||
|
Fft::transform(h1, true);
|
||||||
|
makeRealAndScale(h1);
|
||||||
|
Fft::shift(h1, false);
|
||||||
|
partial_sum(h1.begin(), h1.end(), h1.begin());
|
||||||
|
|
||||||
|
vector<complex<double>> f2;
|
||||||
|
f2.push_back(DCpoint+0.001);
|
||||||
|
for(unsigned int i=0;i<n;i++) {
|
||||||
|
f2.push_back(s[i] * Hr[i]);
|
||||||
|
}
|
||||||
|
auto h2 = makeSymmetric(f2);
|
||||||
|
Fft::transform(h2, true);
|
||||||
|
makeRealAndScale(h2);
|
||||||
|
Fft::shift(h2, false);
|
||||||
|
partial_sum(h2.begin(), h2.end(), h2.begin());
|
||||||
|
|
||||||
|
auto m = (h2[ts]-h1[ts])/0.001;
|
||||||
|
auto b = h1[ts] - m*DCpoint;
|
||||||
|
DCpoint = (0.0 - b) / m;
|
||||||
|
err = abs(h1[ts] - 0.0);
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DCpoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto makeTL = [](const vector<complex<double>> &gamma, double l, complex<double> zLine, complex<double> z0) -> vector<Sparam> {
|
||||||
|
vector<Sparam> ret;
|
||||||
|
for(auto g : gamma) {
|
||||||
|
auto s11 = ((zLine*zLine-z0*z0)*sinh(g*l))/((zLine*zLine+z0*z0)*sinh(g*l)+2.0*z0*zLine*cosh(g*l));
|
||||||
|
auto s21 = (2.0*z0*zLine)/((zLine*zLine + z0*z0)*sinh(g*l)+2.0*z0*zLine*cosh(g*l));
|
||||||
|
ret.push_back(Sparam(s11, s21, s21, s11));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto hybrid = [](const vector<Sparam> &errorbox, const vector<Sparam> &data_2xthru, const vector<double> &freq_2xthru) -> vector<Sparam> {
|
||||||
|
// taking the errorbox created by peeling and using it only for e00 and e11
|
||||||
|
|
||||||
|
// grab s11 and s22 of errorbox model
|
||||||
|
vector<complex<double>> s111x, s221x;
|
||||||
|
for(auto s : errorbox) {
|
||||||
|
s111x.push_back(s.m11);
|
||||||
|
s221x.push_back(s.m22);
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab s21 of the 2x thru measurement
|
||||||
|
vector<complex<double>> s212x;
|
||||||
|
for(auto s : data_2xthru) {
|
||||||
|
s212x.push_back(s.m21);
|
||||||
|
}
|
||||||
|
auto f = freq_2xthru;
|
||||||
|
|
||||||
|
double k = 1.0;
|
||||||
|
complex<double> test, last_test;
|
||||||
|
vector<complex<double>> s211x;
|
||||||
|
vector<Sparam> ret;
|
||||||
|
for(unsigned int i=0;i<f.size();i++) {
|
||||||
|
test = sqrt(s212x[i]*(1.0-s221x[i]*s221x[i]));
|
||||||
|
if(i > 0) {
|
||||||
|
if(arg(test) - arg(last_test) > 0) {
|
||||||
|
k = -k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_test = test;
|
||||||
|
s211x.push_back(k*test);
|
||||||
|
|
||||||
|
// create the error box and make the s-parameter block
|
||||||
|
ret.push_back(Sparam(s111x[i], s211x[i], s211x[i], s221x[i]));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto makeErrorbox = [=](const vector<Sparam> &data_dut, const vector<Sparam> &data_2xthru, const vector<double> &freq_2xthru, const vector<complex<double>> &gamma, complex<double> z0) -> vector<Sparam> {
|
||||||
|
auto f = freq_2xthru;
|
||||||
|
auto n = f.size();
|
||||||
|
|
||||||
|
vector<complex<double>> s212x;
|
||||||
|
// add the DC point
|
||||||
|
s212x.push_back(1.0);
|
||||||
|
for(auto p : data_2xthru) {
|
||||||
|
s212x.push_back(p.m21);
|
||||||
|
}
|
||||||
|
// extract the mid point from the 2x thru
|
||||||
|
auto t212x = makeSymmetric(s212x);
|
||||||
|
Fft::transform(t212x, true);
|
||||||
|
makeRealAndScale(t212x);
|
||||||
|
auto x = max_element(t212x.begin(), t212x.end(), [](complex<double> a, complex<double> b) -> bool {
|
||||||
|
return abs(a) < abs(b);
|
||||||
|
}) - t212x.begin() + 1;
|
||||||
|
|
||||||
|
// define the relative length
|
||||||
|
double l = 1.0/(2*x);
|
||||||
|
|
||||||
|
// peel away the fixture and create the errorbox
|
||||||
|
|
||||||
|
// create the errorbox seed (a perfect transmission line with no delay)
|
||||||
|
vector<ABCDparam> abcd_errorbox(n, ABCDparam(Sparam(0.0, 1.0, 1.0, 0.0), z0));
|
||||||
|
|
||||||
|
|
||||||
|
for(unsigned int i=0;i<x;i++) {
|
||||||
|
// INPUTS: data_dut, f, abcd_errorbox, n
|
||||||
|
|
||||||
|
// define the fixture-dut-fixture S-parameters
|
||||||
|
vector<complex<double>> s_dut;
|
||||||
|
for(auto s : data_dut) {
|
||||||
|
s_dut.push_back(s.m11);
|
||||||
|
}
|
||||||
|
|
||||||
|
// define the point for extraction
|
||||||
|
s_dut.insert(s_dut.begin(), DC2(s_dut, f));
|
||||||
|
auto dc11 = makeSymmetric(s_dut);
|
||||||
|
Fft::transform(dc11, true);
|
||||||
|
makeRealAndScale(dc11);
|
||||||
|
Fft::shift(dc11, false);
|
||||||
|
partial_sum(dc11.begin(), dc11.end(), dc11.begin());
|
||||||
|
auto t11dutStep = dc11;
|
||||||
|
vector<complex<double>> z11dutStep;
|
||||||
|
for(auto s : t11dutStep) {
|
||||||
|
z11dutStep.push_back(-z0 * (s+1.0)/(s-1.0));
|
||||||
|
}
|
||||||
|
Fft::shift(z11dutStep, true);
|
||||||
|
auto zLine = z11dutStep;
|
||||||
|
|
||||||
|
// create the TL
|
||||||
|
auto TL = makeTL(gamma, l, zLine[0], z0);
|
||||||
|
|
||||||
|
for(unsigned int i=0;i<n;i++) {
|
||||||
|
// peel away the the TL
|
||||||
|
auto abcd_TL = ABCDparam(TL[i], z0);
|
||||||
|
auto abcd_dut = ABCDparam(data_dut[i], z0);
|
||||||
|
abcd_dut = abcd_TL.inverse() * abcd_dut;
|
||||||
|
// add to the errorbox
|
||||||
|
abcd_errorbox[i] = abcd_errorbox[i] * abcd_TL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vector<Sparam> errorbox;
|
||||||
|
for(auto abcd : abcd_errorbox) {
|
||||||
|
errorbox.push_back(Sparam(abcd, z0));
|
||||||
|
}
|
||||||
|
return hybrid(errorbox, data_2xthru, f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// make the first error box
|
||||||
|
auto data_side1 = makeErrorbox(data_fix_dut_fix_Sparam, data_2xthru_Sparam, f, gamma, z0);
|
||||||
|
|
||||||
|
// reverse the port order of fixture-dut-fixture and 2x thru
|
||||||
|
vector<Sparam> data_fix_dut_fix_reversed;
|
||||||
|
for(auto s : data_fix_dut_fix_Sparam) {
|
||||||
|
data_fix_dut_fix_reversed.push_back(Sparam(s.m22, s.m21, s.m12, s.m11));
|
||||||
|
}
|
||||||
|
vector<Sparam> data_2xthru_reversed;
|
||||||
|
for(auto s : data_2xthru_Sparam) {
|
||||||
|
data_2xthru_reversed.push_back(Sparam(s.m22, s.m21, s.m12, s.m11));
|
||||||
|
}
|
||||||
|
|
||||||
|
// make the second error box
|
||||||
|
auto data_side2 = makeErrorbox(data_fix_dut_fix_reversed, data_2xthru_reversed, f, gamma, z0);
|
||||||
|
|
||||||
|
// got the error boxes, convert to T parameters and invert
|
||||||
|
for(unsigned int i=0;i<f.size();i++) {
|
||||||
|
Point p;
|
||||||
|
p.freq = f[i];
|
||||||
|
p.inverseP1 = Tparam(data_side1[i]).inverse();
|
||||||
|
// correct port order of error box 2
|
||||||
|
auto side2 = Sparam(data_side2[i].m22, data_side2[i].m21, data_side2[i].m12, data_side2[i].m11);
|
||||||
|
p.inverseP2 = Tparam(side2).inverse();
|
||||||
|
ret.push_back(p);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Protocol::Datapoint> TwoThru::interpolateEvenFrequencySteps(std::vector<Protocol::Datapoint> input)
|
||||||
|
{
|
||||||
|
vector<Protocol::Datapoint> ret;
|
||||||
|
if(input.size() > 1) {
|
||||||
|
int size = input.size();
|
||||||
|
double freqStep = 0.0;
|
||||||
|
if(input.front().frequency == 0) {
|
||||||
|
freqStep = input[1].frequency;
|
||||||
|
size--;
|
||||||
|
} else {
|
||||||
|
freqStep = input[0].frequency;
|
||||||
|
}
|
||||||
|
if(freqStep * size == input.back().frequency) {
|
||||||
|
// already correct spacing, no interpolation necessary
|
||||||
|
for(auto d : input) {
|
||||||
|
if(d.frequency == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ret.push_back(d);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// needs to interpolate
|
||||||
|
double freq = freqStep;
|
||||||
|
while(freq <= input.back().frequency) {
|
||||||
|
Protocol::Datapoint interp;
|
||||||
|
auto it = lower_bound(input.begin(), input.end(), freq, [](const Protocol::Datapoint &lhs, const double f) -> bool {
|
||||||
|
return lhs.frequency < f;
|
||||||
|
});
|
||||||
|
if(it->frequency == freq) {
|
||||||
|
interp = *it;
|
||||||
|
} else {
|
||||||
|
// no exact match, needs to interpolate
|
||||||
|
auto high = *it;
|
||||||
|
it--;
|
||||||
|
auto low = *it;
|
||||||
|
double alpha = (freq - low.frequency) / (high.frequency - low.frequency);
|
||||||
|
interp.real_S11 = low.real_S11 * (1.0 - alpha) + high.real_S11 * alpha;
|
||||||
|
interp.imag_S11 = low.imag_S11 * (1.0 - alpha) + high.imag_S11 * alpha;
|
||||||
|
interp.real_S12 = low.real_S12 * (1.0 - alpha) + high.real_S12 * alpha;
|
||||||
|
interp.imag_S12 = low.imag_S12 * (1.0 - alpha) + high.imag_S12 * alpha;
|
||||||
|
interp.real_S21 = low.real_S21 * (1.0 - alpha) + high.real_S21 * alpha;
|
||||||
|
interp.imag_S21 = low.imag_S21 * (1.0 - alpha) + high.imag_S21 * alpha;
|
||||||
|
interp.real_S22 = low.real_S22 * (1.0 - alpha) + high.real_S22 * alpha;
|
||||||
|
interp.imag_S22 = low.imag_S22 * (1.0 - alpha) + high.imag_S22 * alpha;
|
||||||
|
}
|
||||||
|
interp.frequency = freq;
|
||||||
|
ret.push_back(interp);
|
||||||
|
freq += freqStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
@ -23,15 +23,24 @@ public:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void startMeasurement();
|
void startMeasurement();
|
||||||
void updateLabel();
|
void updateGUI();
|
||||||
|
void measurementCompleted(std::vector<Protocol::Datapoint> m) override;
|
||||||
private:
|
private:
|
||||||
using Point = struct {
|
using Point = struct {
|
||||||
double freq;
|
double freq;
|
||||||
Tparam inverseP1, inverseP2;
|
Tparam inverseP1, inverseP2;
|
||||||
};
|
};
|
||||||
std::vector<Protocol::Datapoint> measurements;
|
|
||||||
|
static std::vector<Protocol::Datapoint> interpolateEvenFrequencySteps(std::vector<Protocol::Datapoint> input);
|
||||||
|
static std::vector<Point> calculateErrorBoxes(std::vector<Protocol::Datapoint> data_2xthru);
|
||||||
|
static std::vector<Point> calculateErrorBoxes(std::vector<Protocol::Datapoint> data_2xthru, std::vector<Protocol::Datapoint> data_fix_dut_fix, double z0);
|
||||||
|
|
||||||
|
std::vector<Protocol::Datapoint> measurements2xthru;
|
||||||
|
std::vector<Protocol::Datapoint> measurementsDUT;
|
||||||
|
double Z0;
|
||||||
std::vector<Point> points;
|
std::vector<Point> points;
|
||||||
bool measuring;
|
bool measuring2xthru;
|
||||||
|
bool measuringDUT;
|
||||||
QMessageBox *msgBox;
|
QMessageBox *msgBox;
|
||||||
Ui::TwoThruDialog *ui;
|
Ui::TwoThruDialog *ui;
|
||||||
};
|
};
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>233</width>
|
<width>742</width>
|
||||||
<height>103</height>
|
<height>198</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -21,41 +21,112 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="lInfo">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="text">
|
<property name="title">
|
||||||
<string/>
|
<string>Measurements</string>
|
||||||
</property>
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0,0" columnminimumwidth="0,0,0,0">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>2xThru (mandatory):</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fixture-DUT-Fixture (optional):</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3">
|
||||||
|
<widget class="QPushButton" name="bMeasure">
|
||||||
|
<property name="text">
|
||||||
|
<string>Measure</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="l2xthru">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="lDUT">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="3">
|
||||||
|
<widget class="QPushButton" name="bMeasureDUT">
|
||||||
|
<property name="text">
|
||||||
|
<string>Measure</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QPushButton" name="bClear">
|
||||||
|
<property name="text">
|
||||||
|
<string>Clear</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QPushButton" name="bClearDUT">
|
||||||
|
<property name="text">
|
||||||
|
<string>Clear</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="bMeasure">
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
<property name="text">
|
<property name="title">
|
||||||
<string>Measure</string>
|
<string>Calculated de-embedding parameters</string>
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0,0">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lPoints">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lZ0">
|
||||||
|
<property name="text">
|
||||||
|
<string>Z0:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="SIUnitEdit" name="Z0"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="bCalc">
|
||||||
|
<property name="text">
|
||||||
|
<string>Calculate</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>SIUnitEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>CustomWidgets/siunitedit.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
|
|
||||||
VNA::VNA(AppWindow *window)
|
VNA::VNA(AppWindow *window)
|
||||||
: Mode(window, "Vector Network Analyzer"),
|
: Mode(window, "Vector Network Analyzer"),
|
||||||
|
deembedding(traceModel),
|
||||||
central(new TileWidget(traceModel))
|
central(new TileWidget(traceModel))
|
||||||
{
|
{
|
||||||
averages = 1;
|
averages = 1;
|
||||||
|
Loading…
Reference in New Issue
Block a user