1763 lines
62 KiB
C++
1763 lines
62 KiB
C++
#include "vna.h"
|
|
|
|
#include "unit.h"
|
|
#include "CustomWidgets/toggleswitch.h"
|
|
#include "Device/manualcontroldialog.h"
|
|
#include "Traces/tracemodel.h"
|
|
#include "tracewidgetvna.h"
|
|
#include "Traces/tracesmithchart.h"
|
|
#include "Traces/tracexyplot.h"
|
|
#include "Traces/traceimportdialog.h"
|
|
#include "CustomWidgets/tilewidget.h"
|
|
#include "CustomWidgets/siunitedit.h"
|
|
#include "Traces/Marker/markerwidget.h"
|
|
#include "Tools/impedancematchdialog.h"
|
|
#include "Tools/mixedmodeconversion.h"
|
|
#include "ui_main.h"
|
|
#include "Device/firmwareupdatedialog.h"
|
|
#include "preferences.h"
|
|
#include "Generator/signalgenwidget.h"
|
|
#include "CustomWidgets/informationbox.h"
|
|
#include "Deembedding/manualdeembeddingdialog.h"
|
|
#include "Calibration/manualcalibrationdialog.h"
|
|
#include "Calibration/LibreCAL/librecaldialog.h"
|
|
#include "Util/util.h"
|
|
#include "Tools/parameters.h"
|
|
|
|
#include <QGridLayout>
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QPushButton>
|
|
#include <math.h>
|
|
#include <QToolBar>
|
|
#include <QMenu>
|
|
#include <QToolButton>
|
|
#include <QActionGroup>
|
|
#include <QSpinBox>
|
|
#include <QCheckBox>
|
|
#include <QComboBox>
|
|
#include <QSettings>
|
|
#include <algorithm>
|
|
#include <QMessageBox>
|
|
#include <QFileDialog>
|
|
#include <QFile>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <QDateTime>
|
|
#include <QDockWidget>
|
|
#include <queue>
|
|
#include <QDesktopWidget>
|
|
#include <QApplication>
|
|
#include <QActionGroup>
|
|
#include <QErrorMessage>
|
|
#include <QDebug>
|
|
#include <QStyle>
|
|
|
|
VNA::VNA(AppWindow *window, QString name)
|
|
: Mode(window, name, "VNA"),
|
|
deembedding(traceModel),
|
|
deembedding_active(false),
|
|
central(new TileWidget(traceModel))
|
|
{
|
|
averages = 1;
|
|
singleSweep = false;
|
|
calMeasuring = false;
|
|
calWaitFirst = false;
|
|
calDialog.reset();
|
|
// A modal QProgressDialog calls processEvents() in setValue(). Needs to use a queued connection to update the progress
|
|
// value from within the NewDatapoint slot to prevent possible re-entrancy.
|
|
connect(this, &VNA::calibrationMeasurementPercentage, &calDialog, &QProgressDialog::setValue, Qt::QueuedConnection);
|
|
changingSettings = false;
|
|
settings.sweepType = SweepType::Frequency;
|
|
settings.zerospan = false;
|
|
|
|
traceModel.setSource(TraceModel::DataSource::VNA);
|
|
|
|
configurationTimer.setSingleShot(true);
|
|
connect(&configurationTimer, &QTimer::timeout, this, [=](){
|
|
ConfigureDevice();
|
|
});
|
|
|
|
// Create default traces
|
|
createDefaultTracesAndGraphs(2);
|
|
|
|
connect(&traceModel, &TraceModel::requiredExcitation, this, &VNA::ExcitationRequired);
|
|
|
|
// Create menu entries and connections
|
|
auto calMenu = new QMenu("Calibration", window);
|
|
window->menuBar()->insertMenu(window->getUi()->menuWindow->menuAction(), calMenu);
|
|
actions.insert(calMenu->menuAction());
|
|
auto calLoad = calMenu->addAction("Load");
|
|
saveCal = calMenu->addAction("Save");
|
|
calMenu->addSeparator();
|
|
|
|
connect(calLoad, &QAction::triggered, [=](){
|
|
LoadCalibration();
|
|
});
|
|
|
|
connect(saveCal, &QAction::triggered, [=](){
|
|
SaveCalibration();
|
|
});
|
|
|
|
connect(&cal, &Calibration::startMeasurements, this, &VNA::StartCalibrationMeasurements);
|
|
|
|
auto calData = calMenu->addAction("Calibration Measurements");
|
|
connect(calData, &QAction::triggered, [=](){
|
|
cal.edit();
|
|
});
|
|
|
|
auto calEditKit = calMenu->addAction("Edit Calibration Kit");
|
|
connect(calEditKit, &QAction::triggered, [=](){
|
|
cal.getKit().edit([=](){
|
|
if(cal.getCaltype().type != Calibration::Type::None) {
|
|
cal.compute(cal.getCaltype());
|
|
}
|
|
});
|
|
});
|
|
|
|
auto calElectronic = calMenu->addAction("Electronic Calibration");
|
|
connect(calElectronic, &QAction::triggered, [=](){
|
|
auto d = new LibreCALDialog(&cal);
|
|
d->show();
|
|
});
|
|
|
|
calMenu->addSeparator();
|
|
|
|
auto calImportTerms = calMenu->addAction("Import error terms as traces");
|
|
calImportTerms->setEnabled(false);
|
|
connect(calImportTerms, &QAction::triggered, [=](){
|
|
auto import = new TraceImportDialog(traceModel, cal.getErrorTermTraces());
|
|
if(AppWindow::showGUI()) {
|
|
import->show();
|
|
}
|
|
});
|
|
auto calImportMeas = calMenu->addAction("Import measurements as traces");
|
|
calImportMeas->setEnabled(false);
|
|
connect(calImportMeas, &QAction::triggered, [=](){
|
|
auto import = new TraceImportDialog(traceModel, cal.getMeasurementTraces());
|
|
if(AppWindow::showGUI()) {
|
|
import->show();
|
|
}
|
|
});
|
|
|
|
calMenu->addSeparator();
|
|
auto calApplyToTraces = calMenu->addAction("Apply to traces...");
|
|
calApplyToTraces->setEnabled(false);
|
|
connect(calApplyToTraces, &QAction::triggered, [=]() {
|
|
auto manualCalibration = new ManualCalibrationDialog(traceModel, &cal);
|
|
if(AppWindow::showGUI()) {
|
|
manualCalibration->show();
|
|
}
|
|
});
|
|
|
|
// portExtension.setCalkit(&cal.getCalibrationKit());
|
|
|
|
// De-embedding menu
|
|
auto menuDeembed = new QMenu("De-embedding", window);
|
|
window->menuBar()->insertMenu(window->getUi()->menuWindow->menuAction(), menuDeembed);
|
|
actions.insert(menuDeembed->menuAction());
|
|
auto confDeembed = menuDeembed->addAction("Setup...");
|
|
connect(confDeembed, &QAction::triggered, &deembedding, &Deembedding::configure);
|
|
|
|
enableDeembeddingAction = menuDeembed->addAction("De-embed VNA samples");
|
|
enableDeembeddingAction->setCheckable(true);
|
|
enableDeembeddingAction->setEnabled(false);
|
|
connect(enableDeembeddingAction, &QAction::toggled, this, &VNA::EnableDeembedding);
|
|
|
|
auto manualDeembed = menuDeembed->addAction("De-embed traces...");
|
|
manualDeembed->setEnabled(false);
|
|
connect(manualDeembed, &QAction::triggered, [=]() {
|
|
auto manualDeembedding = new ManualDeembeddingDialog(traceModel, &deembedding);
|
|
if(AppWindow::showGUI()) {
|
|
manualDeembedding->show();
|
|
}
|
|
});
|
|
|
|
connect(&deembedding, &Deembedding::optionAdded, [=](){
|
|
EnableDeembedding(true);
|
|
enableDeembeddingAction->setEnabled(true);
|
|
manualDeembed->setEnabled(true);
|
|
});
|
|
connect(&deembedding, &Deembedding::allOptionsCleared, [=](){
|
|
EnableDeembedding(false);
|
|
enableDeembeddingAction->setEnabled(false);
|
|
manualDeembed->setEnabled(false);
|
|
});
|
|
|
|
// Tools menu
|
|
auto toolsMenu = new QMenu("Tools", window);
|
|
window->menuBar()->insertMenu(window->getUi()->menuWindow->menuAction(), toolsMenu);
|
|
actions.insert(toolsMenu->menuAction());
|
|
auto impedanceMatching = toolsMenu->addAction("Impedance Matching");
|
|
connect(impedanceMatching, &QAction::triggered, this, &VNA::StartImpedanceMatching);
|
|
auto mixedMode = toolsMenu->addAction("Mixed Mode Conversion");
|
|
connect(mixedMode, &QAction::triggered, this, &VNA::StartMixedModeConversion);
|
|
|
|
defaultCalMenu = new QMenu("Default Calibration", window);
|
|
assignDefaultCal = defaultCalMenu->addAction("Assign...");
|
|
removeDefaultCal = defaultCalMenu->addAction("Remove");
|
|
removeDefaultCal->setEnabled(false);
|
|
defaultCalMenu->setEnabled(false);
|
|
|
|
actions.insert(window->getUi()->menuDevice->addSeparator());
|
|
window->getUi()->menuDevice->addMenu(defaultCalMenu);
|
|
actions.insert(defaultCalMenu->menuAction());
|
|
|
|
connect(assignDefaultCal, &QAction::triggered, [=](){
|
|
if(window->getDevice()) {
|
|
auto key = "DefaultCalibration"+window->getDevice()->serial();
|
|
QSettings settings;
|
|
auto filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", settings.value(key).toString(), "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog);
|
|
if(!filename.isEmpty()) {
|
|
settings.setValue(key, filename);
|
|
removeDefaultCal->setEnabled(true);
|
|
}
|
|
}
|
|
});
|
|
connect(removeDefaultCal, &QAction::triggered, [=](){
|
|
QSettings settings;
|
|
settings.remove("DefaultCalibration"+window->getDevice()->serial());
|
|
removeDefaultCal->setEnabled(false);
|
|
});
|
|
|
|
|
|
// Sweep toolbar
|
|
auto tb_sweep = new QToolBar("Sweep");
|
|
|
|
std::vector<QAction*> frequencySweepActions;
|
|
std::vector<QAction*> powerSweepActions;
|
|
|
|
auto bRun = new QPushButton("Run/Stop");
|
|
bRun->setToolTip("Pause/continue sweep");
|
|
bRun->setCheckable(true);
|
|
running = true;
|
|
connect(bRun, &QPushButton::toggled, [=](){
|
|
if(bRun->isChecked()) {
|
|
Run();
|
|
} else {
|
|
Stop();
|
|
}
|
|
});
|
|
connect(this, &VNA::sweepStopped, [=](){
|
|
bRun->blockSignals(true);
|
|
bRun->setChecked(false);
|
|
bRun->setIcon(bRun->style()->standardIcon(QStyle::SP_MediaPause));
|
|
bRun->blockSignals(false);
|
|
});
|
|
connect(this, &VNA::sweepStarted, [=](){
|
|
bRun->blockSignals(true);
|
|
bRun->setChecked(true);
|
|
bRun->setIcon(bRun->style()->standardIcon(QStyle::SP_MediaPlay));
|
|
bRun->blockSignals(false);
|
|
});
|
|
tb_sweep->addWidget(bRun);
|
|
|
|
auto bSingle = new QPushButton("Single");
|
|
bSingle->setToolTip("Single sweep");
|
|
bSingle->setCheckable(true);
|
|
connect(bSingle, &QPushButton::toggled, this, &VNA::SetSingleSweep);
|
|
connect(this, &VNA::singleSweepChanged, bSingle, &QPushButton::setChecked);
|
|
tb_sweep->addWidget(bSingle);
|
|
|
|
tb_sweep->addWidget(new QLabel("Sweep type:"));
|
|
auto cbSweepType = new QComboBox();
|
|
cbSweepType->addItem("Frequency");
|
|
cbSweepType->addItem("Power");
|
|
tb_sweep->addWidget(cbSweepType);
|
|
|
|
auto eStart = new SIUnitEdit("Hz", " kMG", 6);
|
|
// calculate width required with expected string length
|
|
auto width = QFontMetrics(eStart->font()).width("3.00000GHz") + 15;
|
|
eStart->setFixedWidth(width);
|
|
eStart->setToolTip("Start frequency");
|
|
connect(eStart, &SIUnitEdit::valueChanged, this, &VNA::SetStartFreq);
|
|
connect(this, &VNA::startFreqChanged, eStart, &SIUnitEdit::setValueQuiet);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(new QLabel("Start:")));
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(eStart));
|
|
|
|
auto eCenter = new SIUnitEdit("Hz", " kMG", 6);
|
|
eCenter->setFixedWidth(width);
|
|
eCenter->setToolTip("Center frequency");
|
|
connect(eCenter, &SIUnitEdit::valueChanged, this, &VNA::SetCenterFreq);
|
|
connect(this, &VNA::centerFreqChanged, eCenter, &SIUnitEdit::setValueQuiet);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(new QLabel("Center:")));
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(eCenter));
|
|
|
|
auto eStop = new SIUnitEdit("Hz", " kMG", 6);
|
|
eStop->setFixedWidth(width);
|
|
eStop->setToolTip("Stop frequency");
|
|
connect(eStop, &SIUnitEdit::valueChanged, this, &VNA::SetStopFreq);
|
|
connect(this, &VNA::stopFreqChanged, eStop, &SIUnitEdit::setValueQuiet);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(new QLabel("Stop:")));
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(eStop));
|
|
|
|
auto eSpan = new SIUnitEdit("Hz", " kMG", 6);
|
|
eSpan->setFixedWidth(width);
|
|
eSpan->setToolTip("Span");
|
|
connect(eSpan, &SIUnitEdit::valueChanged, this, &VNA::SetSpan);
|
|
connect(this, &VNA::spanChanged, eSpan, &SIUnitEdit::setValueQuiet);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(new QLabel("Span:")));
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(eSpan));
|
|
|
|
auto bFull = new QPushButton(QIcon::fromTheme("zoom-fit-best", QIcon(":/icons/zoom-fit.png")), "");
|
|
bFull->setToolTip("Full span");
|
|
connect(bFull, &QPushButton::clicked, this, &VNA::SetFullSpan);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(bFull));
|
|
|
|
auto bZoomIn = new QPushButton(QIcon::fromTheme("zoom-in", QIcon(":/icons/zoom-in.png")), "");
|
|
bZoomIn->setToolTip("Zoom in");
|
|
connect(bZoomIn, &QPushButton::clicked, this, &VNA::SpanZoomIn);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(bZoomIn));
|
|
|
|
auto bZoomOut = new QPushButton(QIcon::fromTheme("zoom-out", QIcon(":/icons/zoom-out.png")), "");
|
|
bZoomOut->setToolTip("Zoom out");
|
|
connect(bZoomOut, &QPushButton::clicked, this, &VNA::SpanZoomOut);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(bZoomOut));
|
|
|
|
auto bZero = new QPushButton("0");
|
|
bZero->setToolTip("Zero span");
|
|
bZero->setMaximumWidth(28);
|
|
bZero->setMaximumHeight(24);
|
|
connect(bZero, &QPushButton::clicked, this, &VNA::SetZeroSpan);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(bZero));
|
|
|
|
auto cbLogSweep = new QCheckBox("Log");
|
|
cbLogSweep->setToolTip("Logarithmic sweep");
|
|
connect(cbLogSweep, &QCheckBox::toggled, this, &VNA::SetLogSweep);
|
|
connect(this, &VNA::logSweepChanged, cbLogSweep, &QCheckBox::setChecked);
|
|
frequencySweepActions.push_back(tb_sweep->addWidget(cbLogSweep));
|
|
|
|
// power sweep widgets
|
|
auto sbPowerLow = new QDoubleSpinBox();
|
|
width = QFontMetrics(sbPowerLow->font()).width("-30.00dBm") + 20;
|
|
sbPowerLow->setFixedWidth(width);
|
|
sbPowerLow->setRange(-100.0, 100.0);
|
|
sbPowerLow->setSingleStep(0.25);
|
|
sbPowerLow->setSuffix("dbm");
|
|
sbPowerLow->setToolTip("Stimulus level");
|
|
sbPowerLow->setKeyboardTracking(false);
|
|
connect(sbPowerLow, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &VNA::SetStartPower);
|
|
connect(this, &VNA::startPowerChanged, sbPowerLow, &QDoubleSpinBox::setValue);
|
|
powerSweepActions.push_back(tb_sweep->addWidget(new QLabel("From:")));
|
|
powerSweepActions.push_back(tb_sweep->addWidget(sbPowerLow));
|
|
|
|
auto sbPowerHigh = new QDoubleSpinBox();
|
|
width = QFontMetrics(sbPowerHigh->font()).width("-30.00dBm") + 20;
|
|
sbPowerHigh->setFixedWidth(width);
|
|
sbPowerHigh->setRange(-100.0, 100.0);
|
|
sbPowerHigh->setSingleStep(0.25);
|
|
sbPowerHigh->setSuffix("dbm");
|
|
sbPowerHigh->setToolTip("Stimulus level");
|
|
sbPowerHigh->setKeyboardTracking(false);
|
|
connect(sbPowerHigh, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &VNA::SetStopPower);
|
|
connect(this, &VNA::stopPowerChanged, sbPowerHigh, &QDoubleSpinBox::setValue);
|
|
powerSweepActions.push_back(tb_sweep->addWidget(new QLabel("To:")));
|
|
powerSweepActions.push_back(tb_sweep->addWidget(sbPowerHigh));
|
|
|
|
auto ePowerFreq = new SIUnitEdit("Hz", " kMG", 6);
|
|
width = QFontMetrics(ePowerFreq->font()).width("3.00000GHz") + 15;
|
|
ePowerFreq->setFixedWidth(width);
|
|
ePowerFreq->setToolTip("Start frequency");
|
|
connect(ePowerFreq, &SIUnitEdit::valueChanged, this, &VNA::SetPowerSweepFrequency);
|
|
connect(this, &VNA::powerSweepFrequencyChanged, ePowerFreq, &SIUnitEdit::setValueQuiet);
|
|
powerSweepActions.push_back(tb_sweep->addWidget(new QLabel("at:")));
|
|
powerSweepActions.push_back(tb_sweep->addWidget(ePowerFreq));
|
|
|
|
window->addToolBar(tb_sweep);
|
|
toolbars.insert(tb_sweep);
|
|
|
|
// Acquisition toolbar
|
|
auto tb_acq = new QToolBar("Acquisition");
|
|
auto dbm = new QDoubleSpinBox();
|
|
width = QFontMetrics(dbm->font()).width("-30.00dBm") + 20;
|
|
dbm->setFixedWidth(width);
|
|
dbm->setRange(-100.0, 100.0);
|
|
dbm->setSingleStep(0.25);
|
|
dbm->setSuffix("dbm");
|
|
dbm->setToolTip("Stimulus level");
|
|
dbm->setKeyboardTracking(false);
|
|
connect(dbm, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &VNA::SetSourceLevel);
|
|
connect(this, &VNA::sourceLevelChanged, dbm, &QDoubleSpinBox::setValue);
|
|
frequencySweepActions.push_back(tb_acq->addWidget(new QLabel("Level:")));
|
|
frequencySweepActions.push_back(tb_acq->addWidget(dbm));
|
|
|
|
auto points = new QSpinBox();
|
|
points->setFixedWidth(65);
|
|
points->setRange(1, UINT16_MAX);
|
|
points->setSingleStep(100);
|
|
points->setToolTip("Points/sweep");
|
|
points->setKeyboardTracking(false);
|
|
connect(points, qOverload<int>(&QSpinBox::valueChanged), this, &VNA::SetPoints);
|
|
connect(this, &VNA::pointsChanged, [=](int p) {
|
|
points->blockSignals(true);
|
|
points->setValue(p);
|
|
points->blockSignals(false);
|
|
});
|
|
tb_acq->addWidget(new QLabel("Points:"));
|
|
tb_acq->addWidget(points);
|
|
|
|
auto eBandwidth = new SIUnitEdit("Hz", " k", 3);
|
|
eBandwidth->setFixedWidth(70);
|
|
eBandwidth->setToolTip("IF bandwidth");
|
|
connect(eBandwidth, &SIUnitEdit::valueChanged, this, &VNA::SetIFBandwidth);
|
|
connect(this, &VNA::IFBandwidthChanged, eBandwidth, &SIUnitEdit::setValueQuiet);
|
|
tb_acq->addWidget(new QLabel("IF BW:"));
|
|
tb_acq->addWidget(eBandwidth);
|
|
|
|
tb_acq->addWidget(new QLabel("Averaging:"));
|
|
lAverages = new QLabel("0/");
|
|
tb_acq->addWidget(lAverages);
|
|
auto sbAverages = new QSpinBox;
|
|
sbAverages->setRange(1, 99);
|
|
sbAverages->setFixedWidth(40);
|
|
connect(sbAverages, qOverload<int>(&QSpinBox::valueChanged), this, &VNA::SetAveraging);
|
|
connect(this, &VNA::averagingChanged, sbAverages, &QSpinBox::setValue);
|
|
tb_acq->addWidget(sbAverages);
|
|
|
|
window->addToolBar(tb_acq);
|
|
toolbars.insert(tb_acq);
|
|
|
|
// Calibration toolbar (and populate calibration menu)
|
|
auto tb_cal = new QToolBar("Calibration");
|
|
calLabel = new QLabel("Calibration:");
|
|
UpdateCalWidget();
|
|
tb_cal->addWidget(calLabel);
|
|
auto cbEnableCal = new QCheckBox;
|
|
tb_cal->addWidget(cbEnableCal);
|
|
auto cbType = new QComboBox();
|
|
|
|
auto updateCalComboBox = [=](){
|
|
auto cals = cal.getAvailableCalibrations();
|
|
cbType->blockSignals(true);
|
|
cbType->clear();
|
|
for(auto c : cals) {
|
|
if(c.type == Calibration::Type::None) {
|
|
continue;
|
|
}
|
|
cbType->addItem(c.getShortString());
|
|
}
|
|
cbType->setCurrentText(cal.getCaltype().getShortString());
|
|
cbType->blockSignals(false);
|
|
};
|
|
|
|
connect(this, &VNA::deviceInitialized, updateCalComboBox);
|
|
|
|
updateCalComboBox();
|
|
|
|
auto calToolbarLambda = [=]() {
|
|
if(cbEnableCal->isChecked()) {
|
|
// Get requested calibration type from combobox
|
|
ApplyCalibration(Calibration::CalType::fromShortString(cbType->currentText()));
|
|
} else {
|
|
DisableCalibration();
|
|
}
|
|
};
|
|
|
|
// Calibration connections
|
|
connect(&cal, &Calibration::activated, this, &VNA::UpdateStatusbar);
|
|
connect(&cal, &Calibration::deactivated, this, &VNA::UpdateStatusbar);
|
|
connect(cbEnableCal, &QCheckBox::stateChanged, calToolbarLambda);
|
|
connect(cbType, qOverload<int>(&QComboBox::currentIndexChanged), calToolbarLambda);
|
|
connect(&cal, &Calibration::deactivated, [=](){
|
|
cbType->blockSignals(true);
|
|
cbEnableCal->blockSignals(true);
|
|
cbEnableCal->setCheckState(Qt::CheckState::Unchecked);
|
|
// visually indicate loss of calibration
|
|
// cal. file unknown at this moment
|
|
UpdateCalWidget();
|
|
cbType->blockSignals(false);
|
|
cbEnableCal->blockSignals(false);
|
|
calImportTerms->setEnabled(false);
|
|
calImportMeas->setEnabled(false);
|
|
calApplyToTraces->setEnabled(false);
|
|
// saveCal->setEnabled(false);
|
|
});
|
|
connect(&cal, &Calibration::activated, [=](Calibration::CalType applied){
|
|
cbType->blockSignals(true);
|
|
cbEnableCal->blockSignals(true);
|
|
cbType->setCurrentText(applied.getShortString());
|
|
cbEnableCal->setCheckState(Qt::CheckState::Checked);
|
|
// restore default look of widget
|
|
// on hover, show name of active cal. file
|
|
UpdateCalWidget();
|
|
cbType->blockSignals(false);
|
|
cbEnableCal->blockSignals(false);
|
|
calImportTerms->setEnabled(true);
|
|
calImportMeas->setEnabled(true);
|
|
calApplyToTraces->setEnabled(true);
|
|
saveCal->setEnabled(true);
|
|
});
|
|
|
|
tb_cal->addWidget(cbType);
|
|
|
|
window->addToolBar(tb_cal);
|
|
|
|
auto configureToolbarForFrequencySweep = [=](){
|
|
for(auto a : frequencySweepActions) {
|
|
a->setVisible(true);
|
|
}
|
|
for(auto a : powerSweepActions) {
|
|
a->setVisible(false);
|
|
}
|
|
// enable calibration menu entries
|
|
calData->setEnabled(true);
|
|
};
|
|
auto configureToolbarForPowerSweep = [=](){
|
|
for(auto a : frequencySweepActions) {
|
|
a->setVisible(false);
|
|
}
|
|
for(auto a : powerSweepActions) {
|
|
a->setVisible(true);
|
|
}
|
|
// disable calibration menu entries
|
|
calData->setEnabled(false);
|
|
};
|
|
|
|
connect(cbSweepType, qOverload<int>(&QComboBox::currentIndexChanged), [=](int index) {
|
|
SetSweepType((SweepType) index);
|
|
});
|
|
connect(this, &VNA::sweepTypeChanged, [=](SweepType sw) {
|
|
if(sw == SweepType::Frequency) {
|
|
configureToolbarForFrequencySweep();
|
|
} else if(sw == SweepType::Power) {
|
|
configureToolbarForPowerSweep();
|
|
}
|
|
cbSweepType->setCurrentIndex((int) sw);
|
|
});
|
|
configureToolbarForFrequencySweep();
|
|
// initial setup is frequency sweep
|
|
configureToolbarForFrequencySweep();
|
|
SetSweepType(SweepType::Frequency);
|
|
|
|
toolbars.insert(tb_cal);
|
|
|
|
markerModel = new MarkerModel(traceModel, this);
|
|
|
|
auto tracesDock = new QDockWidget("Traces");
|
|
traceWidget = new TraceWidgetVNA(traceModel, cal, deembedding);
|
|
tracesDock->setWidget(traceWidget);
|
|
window->addDockWidget(Qt::LeftDockWidgetArea, tracesDock);
|
|
docks.insert(tracesDock);
|
|
|
|
|
|
auto markerWidget = new MarkerWidget(*markerModel);
|
|
|
|
auto markerDock = new QDockWidget("Marker");
|
|
markerDock->setWidget(markerWidget);
|
|
window->addDockWidget(Qt::BottomDockWidgetArea, markerDock);
|
|
docks.insert(markerDock);
|
|
|
|
SetupSCPI();
|
|
|
|
// Set initial sweep settings
|
|
auto& pref = Preferences::getInstance();
|
|
|
|
if(pref.Acquisition.useMedianAveraging) {
|
|
average.setMode(Averaging::Mode::Median);
|
|
} else {
|
|
average.setMode(Averaging::Mode::Mean);
|
|
}
|
|
|
|
if(pref.Startup.RememberSweepSettings) {
|
|
LoadSweepSettings();
|
|
} else {
|
|
settings.Freq.start = pref.Startup.DefaultSweep.f_start;
|
|
settings.Freq.stop = pref.Startup.DefaultSweep.f_stop;
|
|
SetLogSweep(pref.Startup.DefaultSweep.logSweep);
|
|
SetSourceLevel(pref.Startup.DefaultSweep.f_excitation);
|
|
ConstrainAndUpdateFrequencies();
|
|
SetStartPower(pref.Startup.DefaultSweep.dbm_start);
|
|
SetStopPower(pref.Startup.DefaultSweep.dbm_stop);
|
|
SetPowerSweepFrequency(pref.Startup.DefaultSweep.dbm_freq);
|
|
SetIFBandwidth(pref.Startup.DefaultSweep.bandwidth);
|
|
SetAveraging(pref.Startup.DefaultSweep.averaging);
|
|
SetPoints(pref.Startup.DefaultSweep.points);
|
|
if(pref.Startup.DefaultSweep.type == "Power Sweep") {
|
|
SetSweepType(SweepType::Power);
|
|
} else {
|
|
SetSweepType(SweepType::Frequency);
|
|
}
|
|
}
|
|
|
|
// Set ObjectName for toolbars and docks
|
|
for(auto d : findChildren<QDockWidget*>()) {
|
|
d->setObjectName(d->windowTitle());
|
|
}
|
|
for(auto t : findChildren<QToolBar*>()) {
|
|
t->setObjectName(t->windowTitle());
|
|
}
|
|
|
|
finalize(central);
|
|
}
|
|
|
|
Calibration::InterpolationType VNA::getCalInterpolation()
|
|
{
|
|
double f_min, f_max;
|
|
switch(settings.sweepType) {
|
|
case SweepType::Last:
|
|
// should never get here, use frequency values just in case
|
|
case SweepType::Frequency:
|
|
f_min = settings.Freq.start;
|
|
f_max = settings.Freq.stop;
|
|
break;
|
|
case SweepType::Power:
|
|
f_min = settings.Power.frequency;
|
|
f_max = settings.Power.frequency;
|
|
break;
|
|
}
|
|
return cal.getInterpolation(f_min, f_max, settings.npoints);
|
|
}
|
|
|
|
QString VNA::getCalStyle()
|
|
{
|
|
auto interpol = getCalInterpolation();
|
|
QString style = "";
|
|
switch (interpol)
|
|
{
|
|
case Calibration::InterpolationType::Unchanged:
|
|
case Calibration::InterpolationType::Exact:
|
|
case Calibration::InterpolationType::Interpolate:
|
|
style = "";
|
|
break;
|
|
|
|
case Calibration::InterpolationType::Extrapolate:
|
|
style = "background-color: yellow";
|
|
break;
|
|
case Calibration::InterpolationType::NoCalibration:
|
|
style = "background-color: red";
|
|
break;
|
|
}
|
|
return style;
|
|
}
|
|
|
|
QString VNA::getCalToolTip()
|
|
{
|
|
auto interpol = getCalInterpolation();
|
|
QString txt = "";
|
|
switch (interpol)
|
|
{
|
|
case Calibration::InterpolationType::Unchanged:
|
|
case Calibration::InterpolationType::Exact:
|
|
case Calibration::InterpolationType::Interpolate:
|
|
case Calibration::InterpolationType::Extrapolate:
|
|
{
|
|
QString lo = Unit::ToString(cal.getMinFreq(), "", " kMG", 5);
|
|
QString hi = Unit::ToString(cal.getMaxFreq(), "", " kMG", 5);
|
|
if (settings.Freq.start < cal.getMinFreq() ) { lo = "<font color=\"red\">" + lo + "</font>";}
|
|
if (settings.Freq.stop > cal.getMaxFreq() ) { hi = "<font color=\"red\">" + hi + "</font>";}
|
|
txt =
|
|
"limits: " + lo + " - " + hi
|
|
+ "<br>"
|
|
+ "points: " + QString::number(cal.getNumPoints())
|
|
+ "<br>"
|
|
"file: " + cal.getCurrentCalibrationFile();
|
|
break;
|
|
}
|
|
case Calibration::InterpolationType::NoCalibration:
|
|
txt = "none";
|
|
break;
|
|
}
|
|
return txt;
|
|
}
|
|
|
|
void VNA::deactivate()
|
|
{
|
|
StoreSweepSettings();
|
|
Mode::deactivate();
|
|
}
|
|
|
|
void VNA::initializeDevice()
|
|
{
|
|
defaultCalMenu->setEnabled(true);
|
|
connect(window->getDevice(), &VirtualDevice::VNAmeasurementReceived, this, &VNA::NewDatapoint, Qt::UniqueConnection);
|
|
// Check if default calibration exists and attempt to load it
|
|
QSettings s;
|
|
auto key = "DefaultCalibration"+window->getDevice()->serial();
|
|
if (s.contains(key)) {
|
|
auto filename = s.value(key).toString();
|
|
qDebug() << "Attempting to load default calibration file " << filename;
|
|
if(QFile::exists(filename)) {
|
|
if(cal.fromFile(filename)) {
|
|
qDebug() << "Calibration successful from " << filename;
|
|
} else {
|
|
qDebug() << "Calibration not successfull from: " << filename;
|
|
}
|
|
} else {
|
|
qDebug() << "Calibration file not found: " << filename;
|
|
}
|
|
removeDefaultCal->setEnabled(true);
|
|
} else {
|
|
qDebug() << "No default calibration file set for this device";
|
|
removeDefaultCal->setEnabled(false);
|
|
}
|
|
// Configure initial state of device
|
|
SettingsChanged();
|
|
emit deviceInitialized();
|
|
}
|
|
|
|
void VNA::deviceDisconnected()
|
|
{
|
|
defaultCalMenu->setEnabled(false);
|
|
emit sweepStopped();
|
|
}
|
|
|
|
void VNA::shutdown()
|
|
{
|
|
if(cal.hasUnsavedChanges() && cal.getCaltype().type != Calibration::Type::None) {
|
|
auto save = InformationBox::AskQuestion("Save calibration?", "The calibration contains data that has not been saved yet. Do you want to save it before exiting?", false);
|
|
if(save) {
|
|
SaveCalibration();
|
|
}
|
|
}
|
|
}
|
|
|
|
nlohmann::json VNA::toJSON()
|
|
{
|
|
nlohmann::json j;
|
|
// save current sweep/acquisition settings
|
|
nlohmann::json sweep;
|
|
sweep["type"] = SweepTypeToString(settings.sweepType).toStdString();
|
|
nlohmann::json freq;
|
|
freq["start"] = settings.Freq.start;
|
|
freq["stop"] = settings.Freq.stop;
|
|
freq["power"] = settings.Freq.excitation_power;
|
|
freq["log"] = settings.Freq.logSweep;
|
|
sweep["frequency"] = freq;
|
|
sweep["single"] = singleSweep;
|
|
nlohmann::json power;
|
|
power["start"] = settings.Power.start;
|
|
power["stop"] = settings.Power.stop;
|
|
power["frequency"] = settings.Power.frequency;
|
|
sweep["power"] = power;
|
|
sweep["points"] = settings.npoints;
|
|
sweep["IFBW"] = settings.bandwidth;
|
|
j["sweep"] = sweep;
|
|
|
|
j["traces"] = traceModel.toJSON();
|
|
j["tiles"] = central->toJSON();
|
|
j["markers"] = markerModel->toJSON();
|
|
j["de-embedding"] = deembedding.toJSON();
|
|
j["de-embedding_enabled"] = deembedding_active;
|
|
return j;
|
|
}
|
|
|
|
void VNA::fromJSON(nlohmann::json j)
|
|
{
|
|
if(j.is_null()) {
|
|
return;
|
|
}
|
|
if(j.contains("traces")) {
|
|
traceModel.fromJSON(j["traces"]);
|
|
}
|
|
if(j.contains("tiles")) {
|
|
central->fromJSON(j["tiles"]);
|
|
}
|
|
if(j.contains("markers")) {
|
|
markerModel->fromJSON(j["markers"]);
|
|
}
|
|
if(j.contains("de-embedding")) {
|
|
deembedding.fromJSON(j["de-embedding"]);
|
|
EnableDeembedding(j.value("de-embedding_enabled", true));
|
|
} else {
|
|
EnableDeembedding(false);
|
|
}
|
|
|
|
// sweep configuration has to go last so graphs can catch events from changed sweep
|
|
if(j.contains("sweep")) {
|
|
auto sweep = j["sweep"];
|
|
// restore sweep settings, keep current value as default in case of missing entry
|
|
SetPoints(sweep.value("points", settings.npoints));
|
|
SetIFBandwidth(sweep.value("IFBW", settings.bandwidth));
|
|
if(sweep.contains("frequency")) {
|
|
auto freq = sweep["frequency"];
|
|
SetStartFreq(freq.value("start", settings.Freq.start));
|
|
SetStopFreq(freq.value("stop", settings.Freq.stop));
|
|
SetSourceLevel(freq.value("power", settings.Freq.excitation_power));
|
|
SetLogSweep(freq.value("log", settings.Freq.logSweep));
|
|
}
|
|
if(sweep.contains("power")) {
|
|
auto power = sweep["power"];
|
|
SetStartPower(power.value("start", settings.Power.start));
|
|
SetStopPower(power.value("stop", settings.Power.stop));
|
|
SetPowerSweepFrequency(power.value("frequency", settings.Power.frequency));
|
|
}
|
|
auto type = SweepTypeFromString(QString::fromStdString(sweep["type"]));
|
|
if(type == SweepType::Last) {
|
|
// default to frequency sweep
|
|
type = SweepType::Frequency;
|
|
}
|
|
SetSweepType(type);
|
|
SetSingleSweep(sweep.value("single", singleSweep));
|
|
}
|
|
}
|
|
|
|
using namespace std;
|
|
|
|
void VNA::NewDatapoint(VirtualDevice::VNAMeasurement m)
|
|
{
|
|
if(isActive != true) {
|
|
// ignore
|
|
return;
|
|
}
|
|
|
|
if(changingSettings) {
|
|
// already setting new sweep settings, ignore incoming points from old settings
|
|
return;
|
|
}
|
|
|
|
if(singleSweep && average.getLevel() == averages) {
|
|
Stop();
|
|
}
|
|
|
|
auto m_avg = m;
|
|
|
|
bool needsSegmentUpdate = false;
|
|
if (settings.segments > 1) {
|
|
// using multiple segments, adjust pointNum
|
|
auto pointsPerSegment = ceil((double) settings.npoints / settings.segments);
|
|
if (m_avg.pointNum == pointsPerSegment - 1) {
|
|
needsSegmentUpdate = true;
|
|
}
|
|
m_avg.pointNum += pointsPerSegment * settings.activeSegment;
|
|
if(m_avg.pointNum == settings.npoints - 1) {
|
|
needsSegmentUpdate = true;
|
|
}
|
|
}
|
|
|
|
if(m_avg.pointNum >= settings.npoints) {
|
|
qWarning() << "Ignoring point with too large point number (" << m.pointNum << ")";
|
|
return;
|
|
}
|
|
|
|
m_avg = average.process(m_avg);
|
|
|
|
if(calMeasuring) {
|
|
if(average.currentSweep() == averages) {
|
|
// this is the last averaging sweep, use values for calibration
|
|
if(!calWaitFirst || m_avg.pointNum == 0) {
|
|
calWaitFirst = false;
|
|
cal.addMeasurements(calMeasurements, m_avg);
|
|
if(m_avg.pointNum == settings.npoints - 1) {
|
|
calMeasuring = false;
|
|
cal.measurementsComplete();
|
|
calDialog.reset();
|
|
}
|
|
}
|
|
}
|
|
int percentage = (((average.currentSweep() - 1) * 100) + (m_avg.pointNum + 1) * 100 / settings.npoints) / averages;
|
|
emit calibrationMeasurementPercentage(percentage);
|
|
}
|
|
|
|
cal.correctMeasurement(m_avg);
|
|
|
|
if(deembedding_active) {
|
|
deembedding.Deembed(m_avg);
|
|
}
|
|
|
|
TraceMath::DataType type;
|
|
if(settings.zerospan) {
|
|
type = TraceMath::DataType::TimeZeroSpan;
|
|
|
|
// keep track of first point time
|
|
if(m_avg.pointNum == 0) {
|
|
settings.firstPointTime = m_avg.us;
|
|
m_avg.us = 0;
|
|
} else {
|
|
m_avg.us -= settings.firstPointTime;
|
|
}
|
|
} else {
|
|
switch(settings.sweepType) {
|
|
case SweepType::Last:
|
|
case SweepType::Frequency:
|
|
type = TraceMath::DataType::Frequency;
|
|
break;
|
|
case SweepType::Power:
|
|
type = TraceMath::DataType::Power;
|
|
break;
|
|
}
|
|
}
|
|
|
|
traceModel.addVNAData(m_avg, type);
|
|
emit dataChanged();
|
|
if(m_avg.pointNum == settings.npoints - 1) {
|
|
UpdateAverageCount();
|
|
markerModel->updateMarkers();
|
|
}
|
|
|
|
static unsigned int lastPoint = 0;
|
|
if(m_avg.pointNum > 0 && m_avg.pointNum != lastPoint + 1) {
|
|
qWarning() << "Got point" << m_avg.pointNum << "but last received point was" << lastPoint << "("<<(m_avg.pointNum-lastPoint-1)<<"missed points)";
|
|
}
|
|
lastPoint = m_avg.pointNum;
|
|
|
|
if (needsSegmentUpdate) {
|
|
changingSettings = true;
|
|
if( settings.activeSegment < settings.segments - 1) {
|
|
settings.activeSegment++;
|
|
} else {
|
|
settings.activeSegment = 0;
|
|
}
|
|
SettingsChanged();
|
|
}
|
|
}
|
|
|
|
void VNA::UpdateAverageCount()
|
|
{
|
|
lAverages->setText(QString::number(average.getLevel()) + "/");
|
|
}
|
|
|
|
void VNA::SettingsChanged()
|
|
{
|
|
configurationTimer.start(100);
|
|
changingSettings = true;
|
|
ResetLiveTraces();
|
|
}
|
|
|
|
void VNA::StartImpedanceMatching()
|
|
{
|
|
auto dialog = new ImpedanceMatchDialog(*markerModel);
|
|
if(AppWindow::showGUI()) {
|
|
dialog->show();
|
|
}
|
|
}
|
|
|
|
void VNA::StartMixedModeConversion()
|
|
{
|
|
auto dialog = new MixedModeConversion(traceModel);
|
|
connect(dialog, &MixedModeConversion::tracesCreated, [=](std::vector<Trace*> traces) {
|
|
auto d = new TraceImportDialog(traceModel, traces);
|
|
if(AppWindow::showGUI()) {
|
|
d->show();
|
|
}
|
|
});
|
|
if(AppWindow::showGUI()) {
|
|
dialog->show();
|
|
}
|
|
}
|
|
|
|
void VNA::SetSweepType(SweepType sw)
|
|
{
|
|
if(settings.sweepType != sw) {
|
|
settings.sweepType = sw;
|
|
emit sweepTypeChanged(sw);
|
|
ConstrainAndUpdateFrequencies();
|
|
SettingsChanged();
|
|
}
|
|
}
|
|
|
|
void VNA::SetStartFreq(double freq)
|
|
{
|
|
settings.Freq.start = freq;
|
|
if(settings.Freq.stop < freq) {
|
|
settings.Freq.stop = freq;
|
|
}
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SetStopFreq(double freq)
|
|
{
|
|
settings.Freq.stop = freq;
|
|
if(settings.Freq.start > freq) {
|
|
settings.Freq.start = freq;
|
|
}
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SetCenterFreq(double freq)
|
|
{
|
|
auto old_span = settings.Freq.stop - settings.Freq.start;
|
|
if (freq - old_span / 2 <= VirtualDevice::getInfo(window->getDevice()).Limits.minFreq) {
|
|
// would shift start frequency below minimum
|
|
settings.Freq.start = 0;
|
|
settings.Freq.stop = 2 * freq;
|
|
} else if(freq + old_span / 2 >= VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq) {
|
|
// would shift stop frequency above maximum
|
|
settings.Freq.start = 2 * freq - VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq;
|
|
settings.Freq.stop = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq;
|
|
} else {
|
|
settings.Freq.start = freq - old_span / 2;
|
|
settings.Freq.stop = freq + old_span / 2;
|
|
}
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SetSpan(double span)
|
|
{
|
|
auto maxFreq = Preferences::getInstance().Acquisition.harmonicMixing ? VirtualDevice::getInfo(window->getDevice()).Limits.maxFreqHarmonic : VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq;
|
|
auto old_center = (settings.Freq.start + settings.Freq.stop) / 2;
|
|
if(old_center < VirtualDevice::getInfo(window->getDevice()).Limits.minFreq + span / 2) {
|
|
// would shift start frequency below minimum
|
|
settings.Freq.start = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq;
|
|
settings.Freq.stop = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq + span;
|
|
} else if(old_center > maxFreq - span / 2) {
|
|
// would shift stop frequency above maximum
|
|
settings.Freq.start = maxFreq - span;
|
|
settings.Freq.stop = maxFreq;
|
|
} else {
|
|
settings.Freq.start = old_center - span / 2;
|
|
settings.Freq.stop = settings.Freq.start + span;
|
|
}
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SetFullSpan()
|
|
{
|
|
auto &pref = Preferences::getInstance();
|
|
if(pref.Acquisition.fullSpanCalibratedRange && cal.getNumPoints() > 0) {
|
|
// calibration is active, use it as the full span range
|
|
settings.Freq.start = cal.getMinFreq();
|
|
settings.Freq.stop = cal.getMaxFreq();
|
|
} else {
|
|
if(pref.Acquisition.fullSpanManual) {
|
|
settings.Freq.start = pref.Acquisition.fullSpanStart;
|
|
settings.Freq.stop = pref.Acquisition.fullSpanStop;
|
|
} else {
|
|
settings.Freq.start = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq;
|
|
settings.Freq.stop = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq;
|
|
}
|
|
}
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SetZeroSpan()
|
|
{
|
|
auto center = (settings.Freq.start + settings.Freq.stop) / 2;
|
|
SetStartFreq(center);
|
|
SetStopFreq(center);
|
|
}
|
|
|
|
void VNA::SpanZoomIn()
|
|
{
|
|
auto center = (settings.Freq.start + settings.Freq.stop) / 2;
|
|
auto old_span = settings.Freq.stop - settings.Freq.start;
|
|
settings.Freq.start = center - old_span / 4;
|
|
settings.Freq.stop = center + old_span / 4;
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SpanZoomOut()
|
|
{
|
|
auto center = (settings.Freq.start + settings.Freq.stop) / 2;
|
|
auto old_span = settings.Freq.stop - settings.Freq.start;
|
|
if(center > old_span) {
|
|
settings.Freq.start = center - old_span;
|
|
} else {
|
|
settings.Freq.start = 0;
|
|
}
|
|
settings.Freq.stop = center + old_span;
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SetLogSweep(bool log)
|
|
{
|
|
if(settings.Freq.logSweep != log) {
|
|
settings.Freq.logSweep = log;
|
|
emit logSweepChanged(log);
|
|
SettingsChanged();
|
|
}
|
|
}
|
|
|
|
|
|
void VNA::SetSourceLevel(double level)
|
|
{
|
|
if(level > VirtualDevice::getInfo(window->getDevice()).Limits.maxdBm) {
|
|
level = VirtualDevice::getInfo(window->getDevice()).Limits.maxdBm;
|
|
} else if(level < VirtualDevice::getInfo(window->getDevice()).Limits.mindBm) {
|
|
level = VirtualDevice::getInfo(window->getDevice()).Limits.mindBm;
|
|
}
|
|
emit sourceLevelChanged(level);
|
|
settings.Freq.excitation_power = level;
|
|
SettingsChanged();
|
|
}
|
|
|
|
void VNA::SetStartPower(double level)
|
|
{
|
|
settings.Power.start = level;
|
|
emit startPowerChanged(level);
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SetStopPower(double level)
|
|
{
|
|
settings.Power.stop = level;
|
|
emit stopPowerChanged(level);
|
|
ConstrainAndUpdateFrequencies();
|
|
}
|
|
|
|
void VNA::SetPowerSweepFrequency(double freq)
|
|
{
|
|
settings.Power.frequency = freq;
|
|
emit powerSweepFrequencyChanged(freq);
|
|
ConstrainAndUpdateFrequencies();
|
|
SettingsChanged();
|
|
}
|
|
|
|
void VNA::SetPoints(unsigned int points)
|
|
{
|
|
unsigned int maxPoints = Preferences::getInstance().Acquisition.allowSegmentedSweep ? UINT16_MAX : VirtualDevice::getInfo(window->getDevice()).Limits.maxPoints;
|
|
if(points > maxPoints) {
|
|
points = maxPoints;
|
|
} else if (points < 2) {
|
|
points = 2;
|
|
}
|
|
if (points > VirtualDevice::getInfo(window->getDevice()).Limits.maxPoints) {
|
|
// needs segmented sweep
|
|
settings.segments = ceil((double) points / VirtualDevice::getInfo(window->getDevice()).Limits.maxPoints);
|
|
settings.activeSegment = 0;
|
|
} else {
|
|
// can fit all points into one segment
|
|
settings.segments = 1;
|
|
settings.activeSegment = 0;
|
|
}
|
|
emit pointsChanged(points);
|
|
settings.npoints = points;
|
|
SettingsChanged();
|
|
}
|
|
|
|
void VNA::SetIFBandwidth(double bandwidth)
|
|
{
|
|
if(bandwidth > VirtualDevice::getInfo(window->getDevice()).Limits.maxIFBW) {
|
|
bandwidth = VirtualDevice::getInfo(window->getDevice()).Limits.maxIFBW;
|
|
} else if(bandwidth < VirtualDevice::getInfo(window->getDevice()).Limits.minIFBW) {
|
|
bandwidth = VirtualDevice::getInfo(window->getDevice()).Limits.minIFBW;
|
|
}
|
|
settings.bandwidth = bandwidth;
|
|
emit IFBandwidthChanged(settings.bandwidth);
|
|
SettingsChanged();
|
|
}
|
|
|
|
void VNA::SetAveraging(unsigned int averages)
|
|
{
|
|
this->averages = averages;
|
|
average.setAverages(averages);
|
|
emit averagingChanged(averages);
|
|
SettingsChanged();
|
|
}
|
|
|
|
void VNA::ExcitationRequired()
|
|
{
|
|
if(!Preferences::getInstance().Acquisition.alwaysExciteAllPorts) {
|
|
for(unsigned int i=1;i<VirtualDevice::getInfo(window->getDevice()).ports;i++) {
|
|
auto required = traceModel.PortExcitationRequired(i);
|
|
auto set = find(settings.excitedPorts.begin(), settings.excitedPorts.end(), i) != settings.excitedPorts.end();
|
|
if(required != set) {
|
|
// Required port excitation changed
|
|
SettingsChanged();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void VNA::DisableCalibration()
|
|
{
|
|
cal.deactivate();
|
|
}
|
|
|
|
void VNA::ApplyCalibration(Calibration::CalType type)
|
|
{
|
|
if(cal.canCompute(type)) {
|
|
try {
|
|
cal.compute(type);
|
|
} catch (runtime_error &e) {
|
|
InformationBox::ShowError("Calibration failure", e.what());
|
|
DisableCalibration();
|
|
}
|
|
} else {
|
|
if(settings.sweepType == SweepType::Frequency) {
|
|
// Not all required traces available
|
|
InformationBox::ShowMessageBlocking("Missing calibration measurements", "Not all calibration measurements for this type of calibration have been taken. The calibration can be enabled after the missing measurements have been acquired.");
|
|
DisableCalibration();
|
|
cal.edit();
|
|
} else {
|
|
// Not all required traces available
|
|
InformationBox::ShowMessageBlocking("Missing calibration measurements", "Not all calibration measurements for this type of calibration have been taken. Please switch to frequency sweep to take these measurements.");
|
|
DisableCalibration();
|
|
}
|
|
}
|
|
}
|
|
|
|
void VNA::StartCalibrationMeasurements(std::set<CalibrationMeasurement::Base*> m)
|
|
{
|
|
if(!window->getDevice()) {
|
|
return;
|
|
}
|
|
// Stop sweep
|
|
running = false;
|
|
ConfigureDevice();
|
|
calMeasurements = m;
|
|
// Delete any already captured data of this measurement
|
|
cal.clearMeasurements(m);
|
|
calWaitFirst = true;
|
|
// show messagebox
|
|
QString text = "Measuring ";
|
|
if(m.size() == 1) {
|
|
text.append("\"");
|
|
text.append(CalibrationMeasurement::Base::TypeToString((*m.begin())->getType()));
|
|
text.append("\" parameters.");
|
|
} else {
|
|
text.append("multiple calibration standards.");
|
|
}
|
|
calDialog.setLabelText(text);
|
|
calDialog.setCancelButtonText("Abort");
|
|
calDialog.setWindowTitle("Taking calibration measurement...");
|
|
calDialog.setValue(0);
|
|
calDialog.setWindowModality(Qt::ApplicationModal);
|
|
// always show the dialog
|
|
calDialog.setMinimumDuration(0);
|
|
connect(&calDialog, &QProgressDialog::canceled, [=]() {
|
|
// the user aborted the calibration measurement
|
|
calMeasuring = false;
|
|
cal.clearMeasurements(calMeasurements);
|
|
});
|
|
// Trigger sweep to start from beginning
|
|
running = true;
|
|
ConfigureDevice(true, [=](bool){
|
|
// enable calibration measurement only in transmission callback (prevents accidental sampling of data which was still being processed)
|
|
calMeasuring = true;
|
|
});
|
|
}
|
|
|
|
void VNA::SetupSCPI()
|
|
{
|
|
SCPINode::add(new SCPICommand("SWEEP", [=](QStringList params) -> QString {
|
|
if(params.size() >= 1) {
|
|
if(params[0] == "FREQUENCY") {
|
|
SetSweepType(SweepType::Frequency);
|
|
ResetLiveTraces();
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
} else if(params[0] == "POWER") {
|
|
SetSweepType(SweepType::Power);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}
|
|
// either no parameter or invalid
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
}, [=](QStringList) -> QString {
|
|
return settings.sweepType == SweepType::Frequency ? "FREQUENCY" : "POWER";
|
|
}));
|
|
auto scpi_freq = new SCPINode("FREQuency");
|
|
SCPINode::add(scpi_freq);
|
|
scpi_freq->add(new SCPICommand("SPAN", [=](QStringList params) -> QString {
|
|
unsigned long long newval;
|
|
if(!SCPI::paramToULongLong(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetSpan(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.Freq.stop - settings.Freq.start, 'f', 0);
|
|
}));
|
|
scpi_freq->add(new SCPICommand("START", [=](QStringList params) -> QString {
|
|
unsigned long long newval;
|
|
if(!SCPI::paramToULongLong(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetStartFreq(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.Freq.start, 'f', 0);
|
|
}));
|
|
scpi_freq->add(new SCPICommand("CENTer", [=](QStringList params) -> QString {
|
|
unsigned long long newval;
|
|
if(!SCPI::paramToULongLong(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetCenterFreq(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number((settings.Freq.start + settings.Freq.stop)/2, 'f', 0);
|
|
}));
|
|
scpi_freq->add(new SCPICommand("STOP", [=](QStringList params) -> QString {
|
|
unsigned long long newval;
|
|
if(!SCPI::paramToULongLong(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetStopFreq(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.Freq.stop, 'f', 0);
|
|
}));
|
|
scpi_freq->add(new SCPICommand("FULL", [=](QStringList params) -> QString {
|
|
Q_UNUSED(params)
|
|
SetFullSpan();
|
|
ResetLiveTraces();
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}, nullptr));
|
|
scpi_freq->add(new SCPICommand("ZERO", [=](QStringList params) -> QString {
|
|
Q_UNUSED(params)
|
|
SetZeroSpan();
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}, nullptr));
|
|
auto scpi_power = new SCPINode("POWer");
|
|
SCPINode::add(scpi_power);
|
|
scpi_power->add(new SCPICommand("START", [=](QStringList params) -> QString {
|
|
double newval;
|
|
if(!SCPI::paramToDouble(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetStartPower(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.Power.start);
|
|
}));
|
|
scpi_power->add(new SCPICommand("STOP", [=](QStringList params) -> QString {
|
|
double newval;
|
|
if(!SCPI::paramToDouble(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetStopPower(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.Power.stop);
|
|
}));
|
|
auto scpi_acq = new SCPINode("ACQuisition");
|
|
SCPINode::add(scpi_acq);
|
|
scpi_acq->add(new SCPICommand("IFBW", [=](QStringList params) -> QString {
|
|
unsigned long long newval;
|
|
if(!SCPI::paramToULongLong(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetIFBandwidth(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.bandwidth);
|
|
}));
|
|
scpi_acq->add(new SCPICommand("POINTS", [=](QStringList params) -> QString {
|
|
unsigned long long newval;
|
|
if(!SCPI::paramToULongLong(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetPoints(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.npoints);
|
|
}));
|
|
scpi_acq->add(new SCPICommand("AVG", [=](QStringList params) -> QString {
|
|
unsigned long long newval;
|
|
if(!SCPI::paramToULongLong(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetAveraging(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(averages);
|
|
}));
|
|
scpi_acq->add(new SCPICommand("AVGLEVel", nullptr, [=](QStringList) -> QString {
|
|
return QString::number(average.getLevel());
|
|
}));
|
|
scpi_acq->add(new SCPICommand("FINished", nullptr, [=](QStringList) -> QString {
|
|
return average.getLevel() == averages ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False);
|
|
}));
|
|
scpi_acq->add(new SCPICommand("LIMit", nullptr, [=](QStringList) -> QString {
|
|
return central->allLimitsPassing() ? "PASS" : "FAIL";
|
|
}));
|
|
scpi_acq->add(new SCPICommand("SINGLE", [=](QStringList params) -> QString {
|
|
bool single;
|
|
if(!SCPI::paramToBool(params, 0, single)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetSingleSweep(single);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return singleSweep ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False);
|
|
}));
|
|
scpi_acq->add(new SCPICommand("RUN", [=](QStringList) -> QString {
|
|
Run();
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}, [=](QStringList) -> QString {
|
|
return running ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False);
|
|
}));
|
|
scpi_acq->add(new SCPICommand("STOP", [=](QStringList) -> QString {
|
|
Stop();
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}, nullptr));
|
|
auto scpi_stim = new SCPINode("STIMulus");
|
|
SCPINode::add(scpi_stim);
|
|
scpi_stim->add(new SCPICommand("LVL", [=](QStringList params) -> QString {
|
|
double newval;
|
|
if(!SCPI::paramToDouble(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetSourceLevel(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.Freq.excitation_power);
|
|
}));
|
|
scpi_stim->add(new SCPICommand("FREQuency", [=](QStringList params) -> QString {
|
|
unsigned long long newval;
|
|
if(!SCPI::paramToULongLong(params, 0, newval)) {
|
|
return SCPI::getResultName(SCPI::Result::Error);
|
|
} else {
|
|
SetPowerSweepFrequency(newval);
|
|
return SCPI::getResultName(SCPI::Result::Empty);
|
|
}
|
|
}, [=](QStringList) -> QString {
|
|
return QString::number(settings.Power.frequency, 'f', 0);
|
|
}));
|
|
SCPINode::add(traceWidget);
|
|
SCPINode::add(&cal);
|
|
cal.add(new SCPICommand("BUSy", nullptr, [=](QStringList) -> QString {
|
|
return CalibrationMeasurementActive() ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False);
|
|
}));
|
|
}
|
|
|
|
void VNA::ConstrainAndUpdateFrequencies()
|
|
{
|
|
auto& pref = Preferences::getInstance();
|
|
double maxFreq;
|
|
if(pref.Acquisition.harmonicMixing) {
|
|
maxFreq = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreqHarmonic;
|
|
} else {
|
|
maxFreq = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq;
|
|
}
|
|
if(settings.Freq.stop > maxFreq) {
|
|
settings.Freq.stop = maxFreq;
|
|
}
|
|
if(settings.Freq.start > settings.Freq.stop) {
|
|
settings.Freq.start = settings.Freq.stop;
|
|
}
|
|
if(settings.Freq.start < VirtualDevice::getInfo(window->getDevice()).Limits.minFreq) {
|
|
settings.Freq.start = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq;
|
|
}
|
|
settings.zerospan = (settings.sweepType == SweepType::Frequency && settings.Freq.start == settings.Freq.stop)
|
|
|| (settings.sweepType == SweepType::Power && settings.Power.start == settings.Power.stop);
|
|
emit startFreqChanged(settings.Freq.start);
|
|
emit stopFreqChanged(settings.Freq.stop);
|
|
emit spanChanged(settings.Freq.stop - settings.Freq.start);
|
|
emit centerFreqChanged((settings.Freq.stop + settings.Freq.start)/2);
|
|
SettingsChanged();
|
|
}
|
|
|
|
void VNA::LoadSweepSettings()
|
|
{
|
|
auto& pref = Preferences::getInstance();
|
|
QSettings s;
|
|
// frequency sweep settings
|
|
settings.Freq.start = s.value("SweepFreqStart", pref.Startup.DefaultSweep.f_start).toULongLong();
|
|
settings.Freq.stop = s.value("SweepFreqStop", pref.Startup.DefaultSweep.f_stop).toULongLong();
|
|
SetSourceLevel(s.value("SweepFreqLevel", pref.Startup.DefaultSweep.f_excitation).toDouble());
|
|
SetLogSweep(s.value("SweepFreqLog", pref.Startup.DefaultSweep.logSweep).toBool());
|
|
// power sweep settings
|
|
SetStartPower(s.value("SweepPowerStart", pref.Startup.DefaultSweep.dbm_start).toDouble());
|
|
SetStopPower(s.value("SweepPowerStop", pref.Startup.DefaultSweep.dbm_stop).toDouble());
|
|
SetPowerSweepFrequency(s.value("SweepPowerFreq", pref.Startup.DefaultSweep.dbm_freq).toULongLong());
|
|
SetPoints(s.value("SweepPoints", pref.Startup.DefaultSweep.points).toInt());
|
|
SetIFBandwidth(s.value("SweepBandwidth", pref.Startup.DefaultSweep.bandwidth).toUInt());
|
|
SetAveraging(s.value("SweepAveraging", pref.Startup.DefaultSweep.averaging).toInt());
|
|
ConstrainAndUpdateFrequencies();
|
|
auto typeString = s.value("SweepType", pref.Startup.DefaultSweep.type).toString();
|
|
if(typeString == "Power") {
|
|
SetSweepType(SweepType::Power);
|
|
} else {
|
|
SetSweepType(SweepType::Frequency);
|
|
}
|
|
}
|
|
|
|
void VNA::StoreSweepSettings()
|
|
{
|
|
QSettings s;
|
|
s.setValue("SweepType", settings.sweepType == SweepType::Frequency ? "Frequency" : "Power");
|
|
s.setValue("SweepFreqStart", static_cast<unsigned long long>(settings.Freq.start));
|
|
s.setValue("SweepFreqStop", static_cast<unsigned long long>(settings.Freq.stop));
|
|
s.setValue("SweepFreqLevel", settings.Freq.excitation_power);
|
|
s.setValue("SweepFreqLog", settings.Freq.logSweep);
|
|
s.setValue("SweepPowerStart", settings.Power.start);
|
|
s.setValue("SweepPowerStop", settings.Power.stop);
|
|
s.setValue("SweepPowerFreq", static_cast<unsigned long long>(settings.Power.frequency));
|
|
s.setValue("SweepBandwidth", settings.bandwidth);
|
|
s.setValue("SweepPoints", settings.npoints);
|
|
s.setValue("SweepAveraging", averages);
|
|
}
|
|
|
|
void VNA::UpdateCalWidget()
|
|
{
|
|
calLabel->setStyleSheet(getCalStyle());
|
|
calLabel->setToolTip(getCalToolTip());
|
|
}
|
|
|
|
void VNA::createDefaultTracesAndGraphs(int ports)
|
|
{
|
|
auto getDefaultColor = [](int ports, int i, int j)->QColor {
|
|
// Default colors for up to four ports, ensures that e.g. S21 always has the same color
|
|
const array<vector<QColor>, 4> defaultColors = {{
|
|
{Qt::yellow},
|
|
{Qt::yellow, Qt::blue, Qt::green, Qt::red},
|
|
{Qt::yellow, Qt::blue, Qt::cyan, Qt::green, Qt::red, Qt::darkGreen, Qt::darkBlue, Qt::darkYellow, Qt::magenta},
|
|
{Qt::yellow, Qt::blue, Qt::cyan, Qt::darkCyan, Qt::green, Qt::red, Qt::darkGreen, Qt::gray, Qt::darkBlue, Qt::darkYellow, Qt::magenta, Qt::darkMagenta, Qt::cyan, Qt::darkGray, Qt::lightGray, Qt::darkRed},
|
|
}};
|
|
|
|
if(ports >= 1 && ports <= 4) {
|
|
return defaultColors[ports-1][i*ports+j];
|
|
} else {
|
|
// not enough predefined colors available for all ports, just cycle through list
|
|
const array<QColor, 16> list = {{
|
|
Qt::yellow, Qt::blue, Qt::green, Qt::red, Qt::cyan, Qt::magenta, Qt::yellow, Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::gray, Qt::darkCyan, Qt::darkMagenta, Qt::darkYellow, Qt::darkGray, Qt::lightGray
|
|
}};
|
|
auto index = (i*ports+j) % list.size();
|
|
return list[index];
|
|
}
|
|
};
|
|
|
|
vector<vector<TracePlot*>> plots;
|
|
for(int i=0;i<ports;i++) {
|
|
plots.push_back(vector<TracePlot*>());
|
|
for(int j=0;j<ports;j++) {
|
|
QString param = "S"+QString::number(i+1)+QString::number(j+1);
|
|
auto trace = new Trace(param, getDefaultColor(ports, i, j), param);
|
|
traceModel.addTrace(trace);
|
|
TracePlot *plot;
|
|
if(i == j) {
|
|
plot = new TraceSmithChart(traceModel);
|
|
} else {
|
|
plot = new TraceXYPlot(traceModel);
|
|
}
|
|
plot->updateSpan(settings.Freq.start, settings.Freq.stop);
|
|
plot->enableTrace(trace, true);
|
|
plots[i].push_back(plot);
|
|
}
|
|
}
|
|
// Add created graphs to tiles
|
|
central->clear();
|
|
TileWidget *tile = central;
|
|
for(int i=0;i<ports;i++) {
|
|
TileWidget *row;
|
|
if(i != ports - 1) {
|
|
// this is not the last row, split tile
|
|
tile->splitVertically();
|
|
row = tile->Child1();
|
|
tile = tile->Child2();
|
|
} else {
|
|
row = tile;
|
|
}
|
|
for(int j=0;j<ports;j++) {
|
|
TileWidget *graphTile;
|
|
if(j != ports - 1) {
|
|
row->splitHorizontally();
|
|
graphTile = row->Child1();
|
|
row = row->Child2();
|
|
} else {
|
|
graphTile = row;
|
|
}
|
|
graphTile->setPlot(plots[i][j]);
|
|
}
|
|
}
|
|
if(ports >= 3) {
|
|
// default split at the middle does not result in all plots being the same size, adjust
|
|
tile = central;
|
|
for(int i=0;i<ports;i++) {
|
|
TileWidget *rowTile;
|
|
if(i < ports - 1) {
|
|
tile->setSplitPercentage(100 / (ports - i));
|
|
rowTile = tile->Child1();
|
|
} else {
|
|
rowTile = tile;
|
|
}
|
|
for(int j=0;j<ports-1;j++) {
|
|
rowTile->setSplitPercentage(100 / (ports - j));
|
|
rowTile = rowTile->Child2();
|
|
}
|
|
tile = tile->Child2();
|
|
}
|
|
}
|
|
}
|
|
|
|
void VNA::EnableDeembedding(bool enable)
|
|
{
|
|
deembedding_active = enable;
|
|
enableDeembeddingAction->blockSignals(true);
|
|
enableDeembeddingAction->setChecked(enable);
|
|
enableDeembeddingAction->blockSignals(false);
|
|
}
|
|
|
|
void VNA::setAveragingMode(Averaging::Mode mode)
|
|
{
|
|
average.setMode(mode);
|
|
}
|
|
|
|
void VNA::preset()
|
|
{
|
|
for(auto t : traceModel.getTraces()) {
|
|
if(Trace::isVNAParameter(t->name())) {
|
|
traceModel.removeTrace(t);
|
|
}
|
|
}
|
|
// Create default traces
|
|
createDefaultTracesAndGraphs(VirtualDevice::getInfo(window->getDevice()).ports);
|
|
}
|
|
|
|
QString VNA::SweepTypeToString(VNA::SweepType sw)
|
|
{
|
|
switch(sw) {
|
|
case SweepType::Frequency: return "Frequency";
|
|
case SweepType::Power: return "Power";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
VNA::SweepType VNA::SweepTypeFromString(QString s)
|
|
{
|
|
for(int i=0;i<(int)SweepType::Last;i++) {
|
|
if(SweepTypeToString((SweepType) i) == s) {
|
|
return (SweepType) i;
|
|
}
|
|
}
|
|
// not found
|
|
return SweepType::Last;
|
|
}
|
|
|
|
|
|
void VNA::UpdateStatusbar()
|
|
{
|
|
if(cal.getCaltype().type != Calibration::Type::None) {
|
|
QFileInfo fi(cal.getCurrentCalibrationFile());
|
|
auto filename = fi.fileName();
|
|
if(filename.isEmpty()) {
|
|
filename = "Unsaved";
|
|
}
|
|
setStatusbarMessage("Calibration: "+filename);
|
|
} else {
|
|
setStatusbarMessage("Calibration: -");
|
|
}
|
|
}
|
|
|
|
void VNA::SetSingleSweep(bool single)
|
|
{
|
|
if(singleSweep != single) {
|
|
singleSweep = single;
|
|
emit singleSweepChanged(single);
|
|
}
|
|
if(single) {
|
|
Run();
|
|
}
|
|
}
|
|
|
|
void VNA::Run()
|
|
{
|
|
running = true;
|
|
ConfigureDevice();
|
|
}
|
|
|
|
void VNA::Stop()
|
|
{
|
|
running = false;
|
|
ConfigureDevice(false);
|
|
}
|
|
|
|
void VNA::ConfigureDevice(bool resetTraces, std::function<void(bool)> cb)
|
|
{
|
|
if(running) {
|
|
if (resetTraces) {
|
|
ResetLiveTraces();
|
|
}
|
|
changingSettings = true;
|
|
// assemble VNA protocol settings
|
|
VirtualDevice::VNASettings s = {};
|
|
s.IFBW = settings.bandwidth;
|
|
if(Preferences::getInstance().Acquisition.alwaysExciteAllPorts) {
|
|
for(unsigned int i=0;i<VirtualDevice::getInfo(window->getDevice()).ports;i++) {
|
|
s.excitedPorts.push_back(i);
|
|
}
|
|
} else {
|
|
for(unsigned int i=0;i<VirtualDevice::getInfo(window->getDevice()).ports;i++) {
|
|
if(traceModel.PortExcitationRequired(i))
|
|
s.excitedPorts.push_back(i);
|
|
}
|
|
}
|
|
settings.excitedPorts = s.excitedPorts;
|
|
|
|
double start = settings.sweepType == SweepType::Frequency ? settings.Freq.start : settings.Power.start;
|
|
double stop = settings.sweepType == SweepType::Frequency ? settings.Freq.stop : settings.Power.stop;
|
|
int npoints = settings.npoints;
|
|
emit traceModel.SpanChanged(start, stop);
|
|
if (settings.segments > 1) {
|
|
// more than one segment, adjust start/stop
|
|
npoints = ceil((double) settings.npoints / settings.segments);
|
|
unsigned int segmentStartPoint = npoints * settings.activeSegment;
|
|
unsigned int segmentStopPoint = segmentStartPoint + npoints - 1;
|
|
if(segmentStopPoint >= settings.npoints) {
|
|
segmentStopPoint = settings.npoints - 1;
|
|
npoints = settings.npoints - segmentStartPoint;
|
|
}
|
|
auto seg_start = Util::Scale<double>(segmentStartPoint, 0, settings.npoints - 1, start, stop);
|
|
auto seg_stop = Util::Scale<double>(segmentStopPoint, 0, settings.npoints - 1, start, stop);
|
|
start = seg_start;
|
|
stop = seg_stop;
|
|
}
|
|
|
|
if(settings.sweepType == SweepType::Frequency) {
|
|
s.freqStart = start;
|
|
s.freqStop = stop;
|
|
s.points = npoints;
|
|
s.dBmStart = settings.Freq.excitation_power;
|
|
s.dBmStop = settings.Freq.excitation_power;
|
|
s.logSweep = settings.Freq.logSweep;
|
|
} else if(settings.sweepType == SweepType::Power) {
|
|
s.freqStart = settings.Power.frequency;
|
|
s.freqStop = settings.Power.frequency;
|
|
s.points = npoints;
|
|
s.dBmStart = start;
|
|
s.dBmStop = stop;
|
|
s.logSweep = false;
|
|
}
|
|
if(window->getDevice() && isActive) {
|
|
window->getDevice()->setVNA(s, [=](bool res){
|
|
// device received command, reset traces now
|
|
if (resetTraces) {
|
|
ResetLiveTraces();
|
|
}
|
|
if(cb) {
|
|
cb(res);
|
|
}
|
|
changingSettings = false;
|
|
});
|
|
emit sweepStarted();
|
|
} else {
|
|
// no device, unable to start sweep
|
|
emit sweepStopped();
|
|
changingSettings = false;
|
|
}
|
|
} else {
|
|
if(window->getDevice()) {
|
|
changingSettings = true;
|
|
window->getDevice()->setIdle([=](bool){
|
|
changingSettings = false;
|
|
});
|
|
} else {
|
|
emit sweepStopped();
|
|
changingSettings = false;
|
|
}
|
|
emit sweepStopped();
|
|
}
|
|
}
|
|
|
|
void VNA::ResetLiveTraces()
|
|
{
|
|
settings.activeSegment = 0;
|
|
average.reset(settings.npoints);
|
|
traceModel.clearLiveData();
|
|
UpdateAverageCount();
|
|
UpdateCalWidget();
|
|
}
|
|
|
|
bool VNA::LoadCalibration(QString filename)
|
|
{
|
|
return cal.fromFile(filename);
|
|
}
|
|
|
|
bool VNA::SaveCalibration(QString filename)
|
|
{
|
|
return cal.toFile(filename);
|
|
}
|