#include "vna.h" #include "unit.h" #include "CustomWidgets/toggleswitch.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 "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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include VNA::VNA(AppWindow *window, QString name) : Mode(window, name, "VNA"), deembedding(traceModel), deembedding_active(false), tiles(new TileWidget(traceModel)), central(new QScrollArea) { central->setWidget(tiles); central->setWidgetResizable(true); averages = 1; singleSweep = false; calMeasuring = false; calWaitFirst = false; calDialog = nullptr; changingSettings = false; settings.sweepType = SweepType::Frequency; settings.zerospan = false; traceModel.setSource(TraceModel::DataSource::VNA); configurationTimer.setSingleShot(true); connect(&configurationTimer, &QTimer::timeout, this, [=](){ ConfigureDevice(configurationTimerResetTraces); }); // 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(); if(window->getDevice() && !cal.validForDevice(window->getDevice()->getSerial())) { InformationBox::ShowMessage("Invalid calibration", "The selected calibration was created for a different device. You can still load it but the resulting " "data likely isn't useful."); } if(cal.getCaltype().type != Calibration::Type::None) { if(InformationBox::AskQuestion("Adjust span?", "Do you want to adjust the span to match the loaded calibration file?", false)) { SpanMatchCal(); } } }); 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(); } }); cal.getKit().setIdealDefault(); // 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..."); confDeembed->setMenuRole(QAction::NoRole); 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()->getSerial(); 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); LoadCalibration(filename); } } }); connect(removeDefaultCal, &QAction::triggered, [=](){ QSettings settings; settings.remove("DefaultCalibration"+window->getDevice()->getSerial()); removeDefaultCal->setEnabled(false); }); // Sweep toolbar auto tb_sweep = new QToolBar("Sweep"); std::vector frequencySweepActions; std::vector 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:")); 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()).horizontalAdvance("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)); 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)); bMatchCal = new QPushButton("Cal"); bMatchCal->setToolTip("Match span of calibration"); bMatchCal->setMaximumWidth(28); bMatchCal->setMaximumHeight(24); bMatchCal->setEnabled(false); connect(bMatchCal, &QPushButton::clicked, this, &VNA::SpanMatchCal); frequencySweepActions.push_back(tb_sweep->addWidget(bMatchCal)); 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()).horizontalAdvance("-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(&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()).horizontalAdvance("-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(&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()).horizontalAdvance("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()).horizontalAdvance("-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(&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(&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", " kM", 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(&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(&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); bMatchCal->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); bMatchCal->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(&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()) { d->setObjectName(d->windowTitle()); } for(auto t : findChildren()) { 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 = "" + lo + "";} if (settings.Freq.stop > cal.getMaxFreq() ) { hi = "" + hi + "";} txt = "limits: " + lo + " - " + hi + "
" + "points: " + QString::number(cal.getNumPoints()) + "
" "file: " + cal.getCurrentCalibrationFile(); break; } case Calibration::InterpolationType::NoCalibration: txt = "none"; break; } return txt; } void VNA::deactivate() { StoreSweepSettings(); Mode::deactivate(); } static void SetComboBoxItemEnabled(QComboBox * comboBox, int index, bool enabled) { auto * model = qobject_cast(comboBox->model()); assert(model); if(!model) return; auto * item = model->item(index); assert(item); if(!item) return; item->setEnabled(enabled); } void VNA::initializeDevice() { if(!window->getDevice()->supports(DeviceDriver::Feature::VNA)) { InformationBox::ShowError("Unsupported", "The connected device does not support VNA mode"); return; } cbLogSweep->setEnabled(window->getDevice()->supports(DeviceDriver::Feature::VNALogSweep)); if(!window->getDevice()->supports(DeviceDriver::Feature::VNALogSweep)) { SetLogSweep(false); } bZero->setEnabled(window->getDevice()->supports(DeviceDriver::Feature::VNAZeroSpan)); SetComboBoxItemEnabled(cbSweepType, 0, window->getDevice()->supports(DeviceDriver::Feature::VNAFrequencySweep)); SetComboBoxItemEnabled(cbSweepType, 1, window->getDevice()->supports(DeviceDriver::Feature::VNAPowerSweep)); defaultCalMenu->setEnabled(true); connect(window->getDevice(), &DeviceDriver::VNAmeasurementReceived, this, &VNA::NewDatapoint, Qt::UniqueConnection); // Check if default calibration exists and attempt to load it QSettings s; auto key = "DefaultCalibration"+window->getDevice()->getSerial(); 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 ConstrainAndUpdateFrequencies(); SettingsChanged(true); emit deviceInitialized(); if(window->getDevice() && !cal.validForDevice(window->getDevice()->getSerial())) { InformationBox::ShowMessage("Invalid calibration", "The current calibration was created for a different device. You can still use it but the resulting " "data likely isn't useful."); } } 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"] = tiles->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")) { tiles->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(DeviceDriver::VNAMeasurement m) { if(isActive != true) { // ignore return; } if(changingSettings) { // already setting new sweep settings, ignore incoming points from old settings return; } // Calculate sweep time if(m.pointNum == 0) { // new sweep started static auto lastStart = QDateTime::currentDateTimeUtc(); auto now = QDateTime::currentDateTimeUtc(); auto sweepTime = lastStart.msecsTo(now); lastStart = now; qDebug() << "Sweep took"< 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; delete calDialog; calDialog = nullptr; cal.measurementsComplete(); } } } int percentage = (((average.currentSweep() - 1) * 100) + (m_avg.pointNum + 1) * 100 / settings.npoints) / averages; emit calibrationMeasurementPercentage(percentage); } cal.correctMeasurement(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, false); if(deembedding_active) { deembedding.Deembed(m_avg); traceModel.addVNAData(m_avg, type, true); } 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) { if( settings.activeSegment < settings.segments - 1) { settings.activeSegment++; } else { settings.activeSegment = 0; } SettingsChanged(false, 0); } } void VNA::UpdateAverageCount() { lAverages->setText(QString::number(average.getLevel()) + "/"); } void VNA::SettingsChanged(bool resetTraces, int delay) { configurationTimer.start(delay); changingSettings = true; configurationTimerResetTraces = resetTraces; if(resetTraces) { 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 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 <= DeviceDriver::getInfo(window->getDevice()).Limits.VNA.minFreq) { // would shift start frequency below minimum settings.Freq.start = 0; settings.Freq.stop = 2 * freq; } else if(freq + old_span / 2 >= DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxFreq) { // would shift stop frequency above maximum settings.Freq.start = 2 * freq - DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxFreq; settings.Freq.stop = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxFreq; } else { settings.Freq.start = freq - old_span / 2; settings.Freq.stop = freq + old_span / 2; } ConstrainAndUpdateFrequencies(); } void VNA::SetSpan(double span) { auto maxFreq = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxFreq; auto old_center = (settings.Freq.start + settings.Freq.stop) / 2; if(old_center < DeviceDriver::getInfo(window->getDevice()).Limits.VNA.minFreq + span / 2) { // would shift start frequency below minimum settings.Freq.start = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.minFreq; settings.Freq.stop = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.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 = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.minFreq; settings.Freq.stop = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.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(); } bool VNA::SpanMatchCal() { if(cal.getCaltype().type == Calibration::Type::None) { // no cal, nothing to adjust return false; } SetStartFreq(cal.getMinFreq()); SetStopFreq(cal.getMaxFreq()); SetPoints(cal.getNumPoints()); return true; } 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 > DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxdBm) { level = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxdBm; } else if(level < DeviceDriver::getInfo(window->getDevice()).Limits.VNA.mindBm) { level = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.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 : DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxPoints; if(points > maxPoints) { points = maxPoints; } else if (points < 2) { points = 2; } if (points > DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxPoints) { // needs segmented sweep settings.segments = ceil((double) points / DeviceDriver::getInfo(window->getDevice()).Limits.VNA.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 > DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxIFBW) { bandwidth = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxIFBW; } else if(bandwidth < DeviceDriver::getInfo(window->getDevice()).Limits.VNA.minIFBW) { bandwidth = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.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;igetDevice()).Limits.VNA.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 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 = new QProgressDialog(); 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); // 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); connect(calDialog, &QProgressDialog::canceled, this, [=]() { // the user aborted the calibration measurement calMeasuring = false; cal.clearMeasurements(calMeasurements); cal.measurementsAbort(); delete calDialog; }, Qt::UniqueConnection); // 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 tiles->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); })); SCPINode::add(&deembedding); } void VNA::ConstrainAndUpdateFrequencies() { if(settings.Freq.stop > DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxFreq) { settings.Freq.stop = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.maxFreq; } if(settings.Freq.start > settings.Freq.stop) { settings.Freq.start = settings.Freq.stop; } if(settings.Freq.start < DeviceDriver::getInfo(window->getDevice()).Limits.VNA.minFreq) { settings.Freq.start = DeviceDriver::getInfo(window->getDevice()).Limits.VNA.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(settings.Freq.start)); s.setValue("SweepFreqStop", static_cast(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(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, 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 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]; } }; if(ports > 1) { vector> plots; for(int i=0;i()); for(int j=0;jupdateSpan(settings.Freq.start, settings.Freq.stop); plot->enableTrace(trace, true); plots[i].push_back(plot); } } // Add created graphs to tiles tiles->clear(); TileWidget *tile = tiles; for(int i=0;isplitVertically(); row = tile->Child1(); tile = tile->Child2(); } else { row = tile; } for(int j=0;jsplitHorizontally(); 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 = tiles; for(int i=0;isetSplitPercentage(100 / (ports - i)); rowTile = tile->Child1(); } else { rowTile = tile; } for(int j=0;jsetSplitPercentage(100 / (ports - j)); rowTile = rowTile->Child2(); } tile = tile->Child2(); } } } else if(ports == 1) { tiles->clear(); tiles->splitHorizontally(); auto trace = new Trace("S11", getDefaultColor(ports, 0, 0), "S11"); traceModel.addTrace(trace); auto smithchart = new TraceSmithChart(traceModel); auto xyplot = new TraceXYPlot(traceModel); smithchart->updateSpan(settings.Freq.start, settings.Freq.stop); smithchart->enableTrace(trace, true); xyplot->updateSpan(settings.Freq.start, settings.Freq.stop); xyplot->enableTrace(trace, true); tiles->Child1()->setPlot(smithchart); tiles->Child2()->setPlot(xyplot); } } void VNA::EnableDeembedding(bool enable) { deembedding_active = enable; enableDeembeddingAction->blockSignals(true); enableDeembeddingAction->setChecked(enable); enableDeembeddingAction->blockSignals(false); for(auto t : traceModel.getLiveTraces()) { if(enable) { t->setDeembeddingActive(true); } else { t->clearDeembedding(); } } } 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(DeviceDriver::getInfo(window->getDevice()).Limits.VNA.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 cb) { if(running) { if (resetTraces) { ResetLiveTraces(); } changingSettings = true; // assemble VNA protocol settings DeviceDriver::VNASettings s = {}; s.IFBW = settings.bandwidth; if(Preferences::getInstance().Acquisition.alwaysExciteAllPorts) { for(unsigned int i=1;i<=DeviceDriver::getInfo(window->getDevice()).Limits.VNA.ports;i++) { s.excitedPorts.push_back(i); } } else { for(unsigned int i=1;i<=DeviceDriver::getInfo(window->getDevice()).Limits.VNA.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(segmentStartPoint, 0, settings.npoints - 1, start, stop); auto seg_stop = Util::Scale(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); }