Take automatic calibration measurements

This commit is contained in:
Jan Käberich 2022-09-11 23:22:11 +02:00
parent cb0e553a17
commit 8301448343
10 changed files with 285 additions and 60 deletions

View File

@ -218,14 +218,203 @@ void LibreCALDialog::updateDeviceStatus()
} }
void LibreCALDialog::startCalibration() void LibreCALDialog::startCalibration()
{
disableUI();
ui->progressCal->setValue(0);
ui->lCalibrationStatus->setText("Creating calibration kit from coefficients...");
ui->lCalibrationStatus->setStyleSheet("QLabel { color : black; }");
auto& kit = cal->getKit();
kit.clearStandards();
kit.manufacturer = "LibreCAL ("+coeffSet.name+")";
kit.serialnumber = device->serial();
kit.description = "Automatically created from LibreCAL module";
std::vector<CalStandard::Virtual*> openStandards;
std::vector<CalStandard::Virtual*> shortStandards;
std::vector<CalStandard::Virtual*> loadStandards;
std::vector<CalStandard::Virtual*> throughStandards;
for(int i=1;i<=device->getNumPorts();i++) {
if(coeffSet.opens[i-1]->t.points() > 0) {
auto o = new CalStandard::Open();
o->setName("Port "+QString::number(i));
o->setMeasurement(coeffSet.opens[i-1]->t);
openStandards.push_back(o);
kit.standards.push_back(o);
}
if(coeffSet.shorts[i-1]->t.points() > 0) {
auto o = new CalStandard::Short();
o->setName("Port "+QString::number(i));
o->setMeasurement(coeffSet.shorts[i-1]->t);
shortStandards.push_back(o);
kit.standards.push_back(o);
}
if(coeffSet.loads[i-1]->t.points() > 0) {
auto o = new CalStandard::Load();
o->setName("Port "+QString::number(i));
o->setMeasurement(coeffSet.loads[i-1]->t);
loadStandards.push_back(o);
kit.standards.push_back(o);
}
for(int j=i+1;j<=device->getNumPorts();j++) {
auto c = coeffSet.getThrough(i,j);
if(!c) {
continue;
}
if(c->t.points() > 0) {
auto o = new CalStandard::Through();
o->setName("Port "+QString::number(i)+" to "+QString::number(j));
o->setMeasurement(c->t);
throughStandards.push_back(o);
kit.standards.push_back(o);
}
}
}
ui->lCalibrationStatus->setText("Creating calibration measurements...");
cal->reset();
auto vnaPorts = VirtualDevice::getInfo(VirtualDevice::getConnected()).ports;
set<CalibrationMeasurement::Base*> openMeasurements;
set<CalibrationMeasurement::Base*> shortMeasurements;
set<CalibrationMeasurement::Base*> loadMeasurements;
vector<CalibrationMeasurement::TwoPort*> throughMeasurements;
for(int p=0;p<vnaPorts;p++) {
if(portAssignment[p] == 0) {
continue;
}
// Create SOL measurements with correct port of calkit
auto open = new CalibrationMeasurement::Open(cal);
open->setPort(p+1);
open->setStandard(openStandards[portAssignment[p]-1]);
openMeasurements.insert(open);
cal->measurements.push_back(open);
auto _short = new CalibrationMeasurement::Short(cal);
_short->setPort(p+1);
_short->setStandard(shortStandards[portAssignment[p]-1]);
shortMeasurements.insert(_short);
cal->measurements.push_back(_short);
auto load = new CalibrationMeasurement::Load(cal);
load->setPort(p+1);
load->setStandard(loadStandards[portAssignment[p]-1]);
loadMeasurements.insert(load);
cal->measurements.push_back(load);
for(int p2=p+1;p2<vnaPorts;p2++) {
if(portAssignment[p2] == 0) {
continue;
}
auto through = new CalibrationMeasurement::Through(cal);
through->setPort1(p+1);
through->setPort2(p2+1);
// find correct through standard
int libreCALp1 = portAssignment[p];
int libreCALp2 = portAssignment[p2];
QString forwardName = "Port "+QString::number(libreCALp1)+" to "+QString::number(libreCALp2);
QString reverseName = "Port "+QString::number(libreCALp2)+" to "+QString::number(libreCALp1);
for(auto ts : throughStandards) {
if(ts->getName() == forwardName) {
through->setStandard(ts);
through->setReverseStandard(false);
} else if(ts->getName() == reverseName) {
through->setStandard(ts);
through->setReverseStandard(true);
}
}
throughMeasurements.push_back(through);
cal->measurements.push_back(through);
}
}
ui->lCalibrationStatus->setText("Taking calibration measurements...");
measurementsTaken = 0;
auto setTerminationOnAllUsedPorts = [=](CalDevice::Standard s) {
for(auto p : portAssignment) {
if(p > 0) {
device->setStandard(p, s);
}
}
};
auto startNextCalibrationStep = [=]() {
// indicate calibration percentage
auto totalMeasurements = 3 + throughMeasurements.size();
ui->progressCal->setValue(measurementsTaken * 100 / totalMeasurements);
switch(measurementsTaken) {
case 0:
setTerminationOnAllUsedPorts(CalDevice::Standard::Open);
emit cal->startMeasurements(openMeasurements);
break;
case 1:
setTerminationOnAllUsedPorts(CalDevice::Standard::Short);
emit cal->startMeasurements(shortMeasurements);
break;
case 2:
setTerminationOnAllUsedPorts(CalDevice::Standard::Load);
emit cal->startMeasurements(loadMeasurements);
break;
default: {
// into through measurements now
int throughIndex = measurementsTaken - 3;
if(throughIndex >= throughMeasurements.size()) {
// this was the last measurement
// Try to apply the calibration
Calibration::CalType type;
type.type = Calibration::Type::SOLT;
for(int i=0;i<vnaPorts;i++) {
if(portAssignment[i] > 0) {
// this VNA port was used in the calibration
type.usedPorts.push_back(i+1);
}
}
auto res = cal->compute(type);
if(res) {
ui->progressCal->setValue(100);
ui->lCalibrationStatus->setText("Calibration activated.");
} else {
ui->progressCal->setValue(0);
ui->lCalibrationStatus->setText("Failed to activate calibration.");
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
}
// severe connection to this function
disconnect(cal, &Calibration::measurementsUpdated, this, nullptr);
enableUI();
break;
}
setTerminationOnAllUsedPorts(CalDevice::Standard::None);
auto m = throughMeasurements[throughIndex];
device->setStandard(m->getPort1(), CalDevice::Standard::Through);
device->setStandard(m->getPort2(), CalDevice::Standard::Through);
emit cal->startMeasurements({m});
}
break;
}
measurementsTaken++;
};
connect(cal, &Calibration::measurementsUpdated, this, startNextCalibrationStep);
startNextCalibrationStep();
}
void LibreCALDialog::disableUI()
{ {
ui->cbDevice->setEnabled(false); ui->cbDevice->setEnabled(false);
ui->cbCoefficients->setEnabled(false); ui->cbCoefficients->setEnabled(false);
ui->start->setEnabled(false); ui->start->setEnabled(false);
for(auto cb : portAssignmentComboBoxes) {
cb->setEnabled(false);
}
}
ui->lCalibrationStatus->setText("Creating calibration kit from coefficients..."); void LibreCALDialog::enableUI()
auto& kit = cal->getKit(); {
kit = Calkit::fromLibreCAL(device, coeffSet); ui->cbDevice->setEnabled(true);
ui->cbCoefficients->setEnabled(true);
ui->start->setEnabled(true);
for(auto cb : portAssignmentComboBoxes) {
cb->setEnabled(true);
}
} }
void LibreCALDialog::createPortAssignmentUI() void LibreCALDialog::createPortAssignmentUI()
@ -233,6 +422,7 @@ void LibreCALDialog::createPortAssignmentUI()
auto layout = static_cast<QFormLayout*>(ui->assignmentBox->layout()); auto layout = static_cast<QFormLayout*>(ui->assignmentBox->layout());
// Clear any possible previous elements // Clear any possible previous elements
portAssignment.clear(); portAssignment.clear();
portAssignmentComboBoxes.clear();
while(layout->rowCount() > 1) { while(layout->rowCount() > 1) {
layout->removeRow(1); layout->removeRow(1);
} }
@ -255,7 +445,13 @@ void LibreCALDialog::createPortAssignmentUI()
emit portAssignmentChanged(); emit portAssignmentChanged();
}); });
// try to set the default // try to set the default
if(comboBox->count() > p) {
comboBox->setCurrentIndex(p); comboBox->setCurrentIndex(p);
} else {
// port not available, set to unused
comboBox->setCurrentIndex(0);
}
layout->addRow(label, comboBox); layout->addRow(label, comboBox);
portAssignmentComboBoxes.push_back(comboBox);
} }
} }

View File

@ -6,6 +6,7 @@
#include <QDialog> #include <QDialog>
#include <QTimer> #include <QTimer>
#include <QComboBox>
namespace Ui { namespace Ui {
class LibreCALDialog; class LibreCALDialog;
@ -27,6 +28,8 @@ private slots:
void updateDeviceStatus(); void updateDeviceStatus();
void startCalibration(); void startCalibration();
private: private:
void disableUI();
void enableUI();
void createPortAssignmentUI(); void createPortAssignmentUI();
Ui::LibreCALDialog *ui; Ui::LibreCALDialog *ui;
Calibration *cal; Calibration *cal;
@ -35,6 +38,9 @@ private:
QTimer updateTimer; QTimer updateTimer;
bool busy; bool busy;
std::vector<int> portAssignment; std::vector<int> portAssignment;
std::vector<QComboBox*> portAssignmentComboBoxes;
int measurementsTaken;
}; };
#endif // LIBRECALDIALOG_H #endif // LIBRECALDIALOG_H

View File

@ -209,22 +209,25 @@ void Calibration::edit()
updateCalButtons(); updateCalButtons();
}); });
connect(ui->activate, &QPushButton::clicked, [=](){ connect(this, &Calibration::activated, d, [=](){
auto cal = availableCals[ui->calibrationList->currentRow()];
if(compute(cal)) {
updateCalibrationList(); updateCalibrationList();
updateCalStatistics(); updateCalStatistics();
updateCalButtons(); updateCalButtons();
}
}); });
connect(ui->deactivate, &QPushButton::clicked, [=](){ connect(this, &Calibration::deactivated, d, [=](){
deactivate();
updateCalibrationList(); updateCalibrationList();
updateCalStatistics(); updateCalStatistics();
updateCalButtons(); updateCalButtons();
}); });
connect(ui->activate, &QPushButton::clicked, [=](){
auto cal = availableCals[ui->calibrationList->currentRow()];
compute(cal);
});
connect(ui->deactivate, &QPushButton::clicked, this, &Calibration::deactivate);
ui->table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); ui->table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
auto updateTableEditButtons = [=](){ auto updateTableEditButtons = [=](){
@ -263,6 +266,9 @@ void Calibration::edit()
if(measurements.size() > 0) { if(measurements.size() > 0) {
if(!InformationBox::AskQuestion("Create default entries?", "Do you want to remove all existing entries and create default calibration measurements instead?", true)) { if(!InformationBox::AskQuestion("Create default entries?", "Do you want to remove all existing entries and create default calibration measurements instead?", true)) {
// user aborted // user aborted
ui->createDefault->blockSignals(true);
ui->createDefault->setCurrentIndex(0);
ui->createDefault->blockSignals(false);
return; return;
} }
measurements.clear(); measurements.clear();
@ -345,6 +351,10 @@ void Calibration::edit()
d->show(); d->show();
}); });
connect(ui->editCalkit, &QPushButton::clicked, [=](){
kit.edit();
});
QObject::connect(ui->table, &QTableWidget::currentCellChanged, updateTableEditButtons); QObject::connect(ui->table, &QTableWidget::currentCellChanged, updateTableEditButtons);
auto addMenu = new QMenu(); auto addMenu = new QMenu();

View File

@ -9,6 +9,8 @@
class Calibration : public QObject, public Savable class Calibration : public QObject, public Savable
{ {
Q_OBJECT Q_OBJECT
friend class LibreCALDialog;
public: public:
Calibration(); Calibration();
@ -20,7 +22,7 @@ public:
class CalType { class CalType {
public: public:
Type type; Type type;
std::vector<int> usedPorts; std::vector<int> usedPorts; // port count starts at 1
QString getReadableDescription(); QString getReadableDescription();
QString getShortString(); QString getShortString();

View File

@ -167,6 +167,21 @@ Calibration</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="editCalkit">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Edit
Calibration
Kit</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -6,6 +6,7 @@
#include <QComboBox> #include <QComboBox>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QCheckBox>
using namespace std; using namespace std;
@ -366,6 +367,8 @@ QWidget *CalibrationMeasurement::TwoPort::createSettingsWidget()
auto cbPort1 = new QComboBox(); auto cbPort1 = new QComboBox();
auto label2 = new QLabel(" to "); auto label2 = new QLabel(" to ");
auto cbPort2 = new QComboBox(); auto cbPort2 = new QComboBox();
auto cbReverse = new QCheckBox("Reversed");
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) {
for(int i=1;i<=dev->getInfo().ports;i++) { for(int i=1;i<=dev->getInfo().ports;i++) {
@ -389,12 +392,16 @@ QWidget *CalibrationMeasurement::TwoPort::createSettingsWidget()
cbPort2->addItem(QString::number(port2)); cbPort2->addItem(QString::number(port2));
cbPort2->setCurrentText(QString::number(port2)); cbPort2->setCurrentText(QString::number(port2));
} }
cbReverse->setChecked(reverseStandard);
connect(cbPort1, qOverload<int>(&QComboBox::currentIndexChanged), [=](){ connect(cbPort1, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
setPort1(cbPort1->currentText().toInt()); setPort1(cbPort1->currentText().toInt());
}); });
connect(cbPort2, qOverload<int>(&QComboBox::currentIndexChanged), [=](){ connect(cbPort2, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
setPort2(cbPort2->currentText().toInt()); setPort2(cbPort2->currentText().toInt());
}); });
connect(cbReverse, &QCheckBox::toggled, [=](){
setReverseStandard(cbReverse->isChecked());
});
connect(this, &TwoPort::port1Changed, cbPort1, [=](){ connect(this, &TwoPort::port1Changed, cbPort1, [=](){
auto string = QString::number(port1); auto string = QString::number(port1);
if(cbPort1->findText(string) < 0) { if(cbPort1->findText(string) < 0) {
@ -411,6 +418,9 @@ QWidget *CalibrationMeasurement::TwoPort::createSettingsWidget()
} }
cbPort2->setCurrentText(string); cbPort2->setCurrentText(string);
}); });
connect(this, &TwoPort::reverseStandardChanged, cbReverse, [=](){
cbReverse->setChecked(reverseStandard);
});
auto ret = new QWidget(); auto ret = new QWidget();
auto layout = new QHBoxLayout; auto layout = new QHBoxLayout;
layout->setContentsMargins(0,0,0,0); layout->setContentsMargins(0,0,0,0);
@ -418,6 +428,7 @@ QWidget *CalibrationMeasurement::TwoPort::createSettingsWidget()
layout->addWidget(cbPort1); layout->addWidget(cbPort1);
layout->addWidget(label2); layout->addWidget(label2);
layout->addWidget(cbPort2); layout->addWidget(cbPort2);
layout->addWidget(cbReverse);
layout->setStretch(1, 1); layout->setStretch(1, 1);
layout->setStretch(3, 1); layout->setStretch(3, 1);
ret->setLayout(layout); ret->setLayout(layout);
@ -429,6 +440,7 @@ nlohmann::json CalibrationMeasurement::TwoPort::toJSON()
auto j = Base::toJSON(); auto j = Base::toJSON();
j["port1"] = port1; j["port1"] = port1;
j["port2"] = port2; j["port2"] = port2;
j["reverseStandard"] = reverseStandard;
nlohmann::json jpoints; nlohmann::json jpoints;
for(auto &p : points) { for(auto &p : points) {
nlohmann::json jpoint; nlohmann::json jpoint;
@ -446,6 +458,7 @@ void CalibrationMeasurement::TwoPort::fromJSON(nlohmann::json j)
Base::fromJSON(j); Base::fromJSON(j);
port1 = j.value("port1", 0); port1 = j.value("port1", 0);
port2 = j.value("port2", 0); port2 = j.value("port2", 0);
reverseStandard = j.value("reverseStandard", false);
if(j.contains("points")) { if(j.contains("points")) {
for(auto jpoint : j["points"]) { for(auto jpoint : j["points"]) {
Point p; Point p;
@ -479,7 +492,12 @@ Sparam CalibrationMeasurement::TwoPort::getMeasured(double frequency)
Sparam CalibrationMeasurement::TwoPort::getActual(double frequency) Sparam CalibrationMeasurement::TwoPort::getActual(double frequency)
{ {
return static_cast<CalStandard::TwoPort*>(standard)->toSparam(frequency); auto param = static_cast<CalStandard::TwoPort*>(standard)->toSparam(frequency);
if(reverseStandard) {
swap(param.m11, param.m22);
swap(param.m12, param.m21);
}
return param;
} }
int CalibrationMeasurement::TwoPort::getPort2() const int CalibrationMeasurement::TwoPort::getPort2() const
@ -503,6 +521,14 @@ void CalibrationMeasurement::TwoPort::setPort2(int p)
} }
} }
void CalibrationMeasurement::TwoPort::setReverseStandard(bool reverse)
{
if(reverseStandard != reverse) {
reverseStandard = reverse;
emit reverseStandardChanged(reverse);
}
}
int CalibrationMeasurement::TwoPort::getPort1() const int CalibrationMeasurement::TwoPort::getPort1() const
{ {
return port1; return port1;

View File

@ -141,7 +141,8 @@ public:
TwoPort(Calibration *cal) : TwoPort(Calibration *cal) :
Base(cal), Base(cal),
port1(0), port1(0),
port2(0){} port2(0),
reverseStandard(false){}
virtual double minFreq() override; virtual double minFreq() override;
virtual double maxFreq() override; virtual double maxFreq() override;
@ -164,12 +165,15 @@ public:
public slots: public slots:
void setPort1(int p); void setPort1(int p);
void setPort2(int p); void setPort2(int p);
void setReverseStandard(bool reverse);
protected: protected:
signals: signals:
void port1Changed(int p); void port1Changed(int p);
void port2Changed(int p); void port2Changed(int p);
void reverseStandardChanged(bool r);
protected: protected:
int port1, port2; int port1, port2;
bool reverseStandard; // Set to true if standard is defined with ports swapped
class Point { class Point {
public: public:
double frequency; double frequency;

View File

@ -328,47 +328,6 @@ Calkit Calkit::fromFile(QString filename)
return c; return c;
} }
Calkit Calkit::fromLibreCAL(CalDevice *device, CalDevice::CoefficientSet s)
{
Calkit ret;
ret.manufacturer = "LibreCAL ("+s.name+")";
ret.serialnumber = device->serial();
ret.description = "Automatically created from LibreCAL module";
for(int i=1;i<=device->getNumPorts();i++) {
if(s.opens[i-1]->t.points() > 0) {
auto o = new CalStandard::Open();
o->setName("Port "+QString::number(i)+" Open");
o->setMeasurement(s.opens[i-1]->t);
ret.standards.push_back(o);
}
if(s.shorts[i-1]->t.points() > 0) {
auto o = new CalStandard::Short();
o->setName("Port "+QString::number(i)+" Short");
o->setMeasurement(s.shorts[i-1]->t);
ret.standards.push_back(o);
}
if(s.loads[i-1]->t.points() > 0) {
auto o = new CalStandard::Load();
o->setName("Port "+QString::number(i)+" Load");
o->setMeasurement(s.loads[i-1]->t);
ret.standards.push_back(o);
}
for(int j=i+1;j<=device->getNumPorts();j++) {
auto c = s.getThrough(i,j);
if(!c) {
continue;
}
if(c->t.points() > 0) {
auto o = new CalStandard::Through();
o->setName("Port "+QString::number(i)+" to "+QString::number(j)+" Through");
o->setMeasurement(c->t);
ret.standards.push_back(o);
}
}
}
return ret;
}
void Calkit::edit(std::function<void (void)> updateCal) void Calkit::edit(std::function<void (void)> updateCal)
{ {
auto dialog = new CalkitDialog(*this); auto dialog = new CalkitDialog(*this);

View File

@ -15,6 +15,7 @@
class Calkit : public Savable class Calkit : public Savable
{ {
friend class CalkitDialog; friend class CalkitDialog;
friend class LibreCALDialog;
public: public:
Calkit(); Calkit();
Calkit(const Calkit&) = default; Calkit(const Calkit&) = default;
@ -43,7 +44,6 @@ public:
void toFile(QString filename); void toFile(QString filename);
static Calkit fromFile(QString filename); static Calkit fromFile(QString filename);
static Calkit fromLibreCAL(CalDevice *device, CalDevice::CoefficientSet s);
void edit(std::function<void(void)> updateCal = nullptr); void edit(std::function<void(void)> updateCal = nullptr);
std::vector<CalStandard::Virtual *> getStandards() const; std::vector<CalStandard::Virtual *> getStandards() const;

View File

@ -19,6 +19,7 @@
#include "CustomWidgets/informationbox.h" #include "CustomWidgets/informationbox.h"
#include "Deembedding/manualdeembeddingdialog.h" #include "Deembedding/manualdeembeddingdialog.h"
#include "Calibration/manualcalibrationdialog.h" #include "Calibration/manualcalibrationdialog.h"
#include "Calibration/LibreCAL/librecaldialog.h"
#include "Util/util.h" #include "Util/util.h"
#include "Tools/parameters.h" #include "Tools/parameters.h"
@ -111,6 +112,12 @@ VNA::VNA(AppWindow *window, QString name)
// }); // });
}); });
auto calElectronic = calMenu->addAction("Electronic Calibration");
connect(calElectronic, &QAction::triggered, [=](){
auto d = new LibreCALDialog(&cal);
d->show();
});
calMenu->addSeparator(); calMenu->addSeparator();
auto calImportTerms = calMenu->addAction("Import error terms as traces"); auto calImportTerms = calMenu->addAction("Import error terms as traces");