TRL calibration routine

This commit is contained in:
Jan Käberich 2022-10-01 23:11:13 +02:00
parent 2ef913f073
commit d43a1e6e38
16 changed files with 36638 additions and 3 deletions

View File

@ -74,6 +74,7 @@ Thumbs.db
Application Application
/build-* /build-*
LibreVNA-GUI/LibreVNA-GUI LibreVNA-GUI/LibreVNA-GUI
LibreVNA-Test/LibreVNA-Test

View File

@ -629,7 +629,15 @@ void Calibration::edit()
}); });
connect(ui->editCalkit, &QPushButton::clicked, [=](){ connect(ui->editCalkit, &QPushButton::clicked, [=](){
kit.edit(); kit.edit([=](){
updateMeasurementTable();
// update calibration (may have changed due to edited calibration standard)
if(canCompute(caltype)) {
compute(caltype);
} else {
deactivate();
}
});
}); });
QObject::connect(ui->table, &QTableWidget::currentCellChanged, updateTableEditButtons); QObject::connect(ui->table, &QTableWidget::currentCellChanged, updateTableEditButtons);
@ -810,6 +818,172 @@ Calibration::Point Calibration::computeThroughNormalization(double f)
return point; return point;
} }
Calibration::Point Calibration::computeTRL(double freq)
{
Point point = createInitializedPoint(freq);
// calculate forward match and transmission
for(unsigned int i=0;i<caltype.usedPorts.size();i++) {
for(unsigned int j=0;j<caltype.usedPorts.size();j++) {
if(i == j) {
// calculation only possible with through measurements
continue;
}
auto p1 = caltype.usedPorts[i];
auto p2 = caltype.usedPorts[j];
// grab reflection measurements
auto S11_reflection = static_cast<CalibrationMeasurement::Reflect*>(findMeasurement(CalibrationMeasurement::Base::Type::Reflect, p1));
auto S22_reflection = static_cast<CalibrationMeasurement::Reflect*>(findMeasurement(CalibrationMeasurement::Base::Type::Reflect, p2));
bool S11_short, S22_short;
{
auto S11_short_standard = dynamic_cast<CalStandard::Short*>(S11_reflection->getStandard());
auto S11_open_standard = dynamic_cast<CalStandard::Open*>(S11_reflection->getStandard());
auto S11_reflect_standard = dynamic_cast<CalStandard::Reflect*>(S11_reflection->getStandard());
if(S11_short_standard) {
S11_short = true;
} else if(S11_open_standard) {
S11_short = false;
} else if(S11_reflect_standard) {
S11_short = S11_reflect_standard->getIsShort();
} else {
// invalid standard
throw runtime_error("Invalid standard defined for reflection measurement");
}
auto S22_short_standard = dynamic_cast<CalStandard::Short*>(S22_reflection->getStandard());
auto S22_open_standard = dynamic_cast<CalStandard::Open*>(S22_reflection->getStandard());
auto S22_reflect_standard = dynamic_cast<CalStandard::Reflect*>(S22_reflection->getStandard());
if(S22_short_standard) {
S22_short = true;
} else if(S22_open_standard) {
S22_short = false;
} else if(S22_reflect_standard) {
S22_short = S22_reflect_standard->getIsShort();
} else {
// invalid standard
throw runtime_error("Invalid standard defined for reflection measurement");
}
}
bool reflectionIsNegative;
if(S11_short && S22_short) {
reflectionIsNegative = true;
} else if(!S11_short && !S22_short) {
reflectionIsNegative = false;
} else {
throw runtime_error("Reflection measurements must all use the same standard (either open or short)");
}
// grab through measurement
auto throughForward = static_cast<CalibrationMeasurement::Through*>(findMeasurement(CalibrationMeasurement::Base::Type::Through, p1, p2));
auto throughReverse = static_cast<CalibrationMeasurement::Through*>(findMeasurement(CalibrationMeasurement::Base::Type::Through, p2, p1));
Sparam Sthrough;
if(throughForward) {
Sthrough = throughForward->getMeasured(freq);
} else if(throughReverse) {
Sthrough = throughReverse->getMeasured(freq);
swap(Sthrough.m11, Sthrough.m22);
swap(Sthrough.m12, Sthrough.m21);
}
// grab line measurement
auto forwardLines = findMeasurements(CalibrationMeasurement::Base::Type::Line, p1, p2);
auto reverseLines = findMeasurements(CalibrationMeasurement::Base::Type::Line, p2, p1);
// find the closest (geometric) match for the current frequency
double closestIdealFreqRatio = numeric_limits<double>::max();
bool closestLineIsReversed = false;
CalibrationMeasurement::Line* closestLine = nullptr;
CalStandard::Line* closestStandard = nullptr;
for(int i=0;i<2;i++) {
auto list = i ? reverseLines : forwardLines;
for(auto l : list) {
auto line = static_cast<CalibrationMeasurement::Line*>(l);
auto standard = static_cast<CalStandard::Line*>(line->getStandard());
double idealFreq = (standard->minFrequency() + standard->maxFrequency()) / 2;
double mismatch = idealFreq / freq;
if(mismatch < 0) {
mismatch = 1.0 / mismatch;
}
if(mismatch < closestIdealFreqRatio) {
closestIdealFreqRatio = mismatch;
closestLineIsReversed = i > 0;
closestLine = line;
closestStandard = standard;
}
}
}
if(!closestLine) {
throw runtime_error("Unable to find required line measurement");
}
if(freq < closestStandard->minFrequency() || freq > closestStandard->maxFrequency()) {
throw runtime_error("No line standard supports the required frequency ("+QString::number(freq).toStdString()+")");
}
Sparam Sline = closestLine->getMeasured(freq);
if(closestLineIsReversed) {
swap(Sline.m11, Sline.m22);
swap(Sline.m12, Sline.m21);
}
// got all required measurements
// calculate TRL calibration
// variable names and formulas according to http://emlab.uiuc.edu/ece451/notes/new_TRL.pdf
// page 19
auto R_T = Tparam(Sthrough);
auto R_D = Tparam(Sline);
auto T = R_D*R_T.inverse();
complex<double> a_over_c, b;
// page 21-22
Util::solveQuadratic(T.m21, T.m22 - T.m11, -T.m12, b, a_over_c);
// ensure correct root selection
// page 23
if(abs(b) >= abs(a_over_c)) {
swap(b, a_over_c);
}
// page 24
auto g = R_T.m22;
auto d = R_T.m11 / g;
auto e = R_T.m12 / g;
auto f = R_T.m21 / g;
// page 25
auto r22_rho22 = g * (1.0 - e / a_over_c) / (1.0 - b / a_over_c);
auto gamma = (f - d / a_over_c) / (1.0 - e / a_over_c);
auto beta_over_alpha = (e - b) / (d - b * f);
// page 26
auto alpha_a = (d - b * f) / (1.0 - e / a_over_c);
auto w1 = S11_reflection->getMeasured(freq);
auto w2 = S22_reflection->getMeasured(freq);
// page 28
auto a = sqrt((w1 - b) / (w2 + gamma) * (1.0 + w2 * beta_over_alpha) / (1.0 - w1 / a_over_c) * alpha_a);
// page 29, check sign of a
auto reflection = (w1 - b) / (a * (1.0 - w1 / a_over_c));
if((reflection.real() > 0 && reflectionIsNegative) || (reflection.real() < 0 && !reflectionIsNegative)) {
// wrong sign for a
a = -a;
}
// Revert back from error boxes with T parameters to S paramaters,
// page 17 + formulas for calculating S parameters from T parameters.
// Forward coefficients, normalize for S21 = 1.0 -> r22 = 1.0
auto r22 = complex<double>(1.0);
auto rho22 = r22_rho22 / r22;
auto alpha = alpha_a / a;
auto beta = beta_over_alpha * alpha;
auto c = a / a_over_c;
auto Box_A = Tparam(r22 * a, r22 * b, r22 * c, r22);
auto Box_B = Tparam(rho22 * alpha, rho22 * beta, rho22 * gamma, rho22);
auto S_A = Sparam(Box_A);
point.D[i] = S_A.m11;
point.R[i] = S_A.m12;
point.S[i] = S_A.m22;
auto S_B = Sparam(Box_B);
point.L[i][j] = S_B.m11;
point.T[i][j] = S_B.m21;
// no isolation measurement available
point.I[i][j] = 0.0;
// Reverse coefficients, will be handled in loop iteration where i=j and j=i
}
}
return point;
}
Calibration::CalType Calibration::getCaltype() const Calibration::CalType Calibration::getCaltype() const
{ {
return caltype; return caltype;
@ -1312,6 +1486,8 @@ bool Calibration::compute(Calibration::CalType type)
Point p; Point p;
switch(type.type) { switch(type.type) {
case Type::SOLT: p = computeSOLT(f); break; case Type::SOLT: p = computeSOLT(f); break;
case Type::ThroughNormalization: p = computeThroughNormalization(f); break;
case Type::TRL: p = computeTRL(f); break;
} }
points.push_back(p); points.push_back(p);
} }
@ -1320,6 +1496,7 @@ bool Calibration::compute(Calibration::CalType type)
caltype.usedPorts.clear(); caltype.usedPorts.clear();
} }
emit activated(caltype); emit activated(caltype);
unsavedChanges = true;
return true; return true;
} }
@ -1352,6 +1529,7 @@ void Calibration::clearMeasurements(std::set<CalibrationMeasurement::Base *> m)
for(auto meas : m) { for(auto meas : m) {
meas->clearPoints(); meas->clearPoints();
} }
unsavedChanges = true;
} }
void Calibration::measurementsComplete() void Calibration::measurementsComplete()
@ -1364,6 +1542,7 @@ void Calibration::deactivate()
points.clear(); points.clear();
caltype.type = Type::None; caltype.type = Type::None;
caltype.usedPorts.clear(); caltype.usedPorts.clear();
unsavedChanges = true;
emit deactivated(); emit deactivated();
} }

View File

@ -144,6 +144,7 @@ private:
Point createInitializedPoint(double f); Point createInitializedPoint(double f);
Point computeSOLT(double f); Point computeSOLT(double f);
Point computeThroughNormalization(double f); Point computeThroughNormalization(double f);
Point computeTRL(double f);
std::vector<CalibrationMeasurement::Base*> measurements; std::vector<CalibrationMeasurement::Base*> measurements;

View File

@ -238,6 +238,9 @@ QWidget *CalibrationMeasurement::OnePort::createSettingsWidget()
auto cbPort = new QComboBox(); auto cbPort = new QComboBox();
auto dev = VirtualDevice::getConnected(); auto dev = VirtualDevice::getConnected();
if(dev) { if(dev) {
if(port == 0) {
setPort(1);
}
for(int i=1;i<=dev->getInfo().ports;i++) { for(int i=1;i<=dev->getInfo().ports;i++) {
cbPort->addItem(QString::number(i)); cbPort->addItem(QString::number(i));
if(port == i) { if(port == i) {
@ -384,6 +387,12 @@ QWidget *CalibrationMeasurement::TwoPort::createSettingsWidget()
cbReverse->setToolTip("Enable this option if the calibration standard is defined with the port order swapped"); cbReverse->setToolTip("Enable this option if the calibration standard is defined with the port order swapped");
auto dev = VirtualDevice::getConnected(); auto dev = VirtualDevice::getConnected();
if(dev) { if(dev) {
if(port1 == 0) {
setPort1(1);
}
if(port2 == 0) {
setPort2(2);
}
for(int i=1;i<=dev->getInfo().ports;i++) { for(int i=1;i<=dev->getInfo().ports;i++) {
cbPort1->addItem(QString::number(i)); cbPort1->addItem(QString::number(i));
cbPort2->addItem(QString::number(i)); cbPort2->addItem(QString::number(i));

View File

@ -705,6 +705,11 @@ void Reflect::fromJSON(nlohmann::json j)
isShort = j.value("isShort", true); isShort = j.value("isShort", true);
} }
bool Reflect::getIsShort() const
{
return isShort;
}
Line::Line() Line::Line()
{ {
Z0 = 50.0; Z0 = 50.0;

View File

@ -136,6 +136,8 @@ public:
virtual Type getType() override {return Type::Reflect;} virtual Type getType() override {return Type::Reflect;}
virtual nlohmann::json toJSON() override; virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override; virtual void fromJSON(nlohmann::json j) override;
bool getIsShort() const;
private: private:
bool isShort; bool isShort;
}; };

View File

@ -81,6 +81,13 @@ namespace Util {
std::complex<double> findCenterOfCircle(const std::vector<std::complex<double>> &points); std::complex<double> findCenterOfCircle(const std::vector<std::complex<double>> &points);
std::complex<double> addTransmissionLine(std::complex<double> termination_reflection, double offset_impedance, double offset_delay, double offset_loss, double frequency); std::complex<double> addTransmissionLine(std::complex<double> termination_reflection, double offset_impedance, double offset_delay, double offset_loss, double frequency);
template<typename T> void solveQuadratic(T a, T b, T c, T &result1, T &result2)
{
T root = sqrt(b * b - T(4) * a * c);
result1 = (-b + root) / (T(2) * a);
result2 = (-b - root) / (T(2) * a);
}
} }
#endif // UTILH_H #endif // UTILH_H

View File

@ -80,7 +80,7 @@ VNA::VNA(AppWindow *window, QString name)
auto calLoad = calMenu->addAction("Load"); auto calLoad = calMenu->addAction("Load");
saveCal = calMenu->addAction("Save"); saveCal = calMenu->addAction("Save");
calMenu->addSeparator(); calMenu->addSeparator();
saveCal->setEnabled(false); // saveCal->setEnabled(false);
connect(calLoad, &QAction::triggered, [=](){ connect(calLoad, &QAction::triggered, [=](){
LoadCalibration(""); LoadCalibration("");
@ -438,7 +438,7 @@ VNA::VNA(AppWindow *window, QString name)
calImportTerms->setEnabled(false); calImportTerms->setEnabled(false);
calImportMeas->setEnabled(false); calImportMeas->setEnabled(false);
calApplyToTraces->setEnabled(false); calApplyToTraces->setEnabled(false);
saveCal->setEnabled(false); // saveCal->setEnabled(false);
}); });
connect(&cal, &Calibration::activated, [=](Calibration::CalType applied){ connect(&cal, &Calibration::activated, [=](Calibration::CalType applied){
cbType->blockSignals(true); cbType->blockSignals(true);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
{
"Description": "TRL test calibration kit",
"Manufacturer": "Hugen",
"Serialnumber": "-",
"standards": [
{
"params": {
"id": 5166523964218309461,
"isShort": true,
"name": "TRL"
},
"type": "Reflect"
},
{
"params": {
"Z0": 50.0,
"delay": 0.0,
"id": 4725361202400330854,
"loss": 0.0,
"name": "TRL"
},
"type": "Through"
},
{
"params": {
"delay": 1e-09,
"id": 10060287735088848795,
"name": "TRL"
},
"type": "Line"
}
],
"version": "1.4.0-b20e5598b"
}

View File

@ -0,0 +1,82 @@
{
"calkit": {
"Description": "TRL test calibration kit",
"Manufacturer": "Hugen",
"Serialnumber": "-",
"standards": [
{
"params": {
"id": 5166523964218309461,
"isShort": true,
"name": "TRL"
},
"type": "Reflect"
},
{
"params": {
"Z0": 50.0,
"delay": 0.0,
"id": 4725361202400330854,
"loss": 0.0,
"name": "TRL"
},
"type": "Through"
},
{
"params": {
"delay": 1e-09,
"id": 10060287735088848795,
"name": "TRL"
},
"type": "Line"
}
],
"version": "1.4.0-b20e5598b"
},
"device": "205835943750",
"measurements": [
{
"data": {
"points": null,
"port": 1,
"standard": 5166523964218309461,
"timestamp": -3600
},
"type": "Reflect"
},
{
"data": {
"points": null,
"port": 1,
"standard": 5166523964218309461,
"timestamp": -3600
},
"type": "Reflect"
},
{
"data": {
"points": null,
"port1": 1,
"port2": 2,
"reverseStandard": false,
"standard": 4725361202400330854,
"timestamp": -3600
},
"type": "Through"
},
{
"data": {
"points": null,
"port1": 1,
"port2": 2,
"reverseStandard": false,
"standard": 10060287735088848795,
"timestamp": -3600
},
"type": "Line"
}
],
"ports": null,
"type": "None",
"version": "1.4.0-b20e5598b"
}

View File

@ -0,0 +1,34 @@
{
"Description": "TRL test calibration kit",
"Manufacturer": "Hugen",
"Serialnumber": "-",
"standards": [
{
"params": {
"id": 5166523964218309461,
"isShort": true,
"name": "TRL"
},
"type": "Reflect"
},
{
"params": {
"Z0": 50.0,
"delay": 0.0,
"id": 4725361202400330854,
"loss": 0.0,
"name": "TRL"
},
"type": "Through"
},
{
"params": {
"delay": 1e-09,
"id": 10060287735088848795,
"name": "TRL"
},
"type": "Line"
}
],
"version": "1.4.0-b20e5598b"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
{
"Description": "TRL test calibration kit",
"Manufacturer": "Hugen",
"Serialnumber": "-",
"standards": [
{
"params": {
"id": 5166523964218309461,
"isShort": true,
"name": "TRL"
},
"type": "Reflect"
},
{
"params": {
"Z0": 50.0,
"delay": 0.0,
"id": 4725361202400330854,
"loss": 0.0,
"name": "TRL"
},
"type": "Through"
},
{
"params": {
"delay": 1e-09,
"id": 10060287735088848795,
"name": "TRL"
},
"type": "Line"
}
],
"version": "1.4.0-b20e5598b"
}