#include "spectrumanalyzer.h" #include "unit.h" #include "CustomWidgets/toggleswitch.h" #include "Device/manualcontroldialog.h" #include "Traces/tracemodel.h" #include "tracewidgetsa.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 "ui_main.h" #include "Device/virtualdevice.h" #include "preferences.h" #include "Generator/signalgenwidget.h" #include #include #include #include #include "CustomWidgets/informationbox.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window, QString name) : Mode(window, name, "SA"), central(new TileWidget(traceModel, window)), firstPointTime(0) { changingSettings = false; averages = 1; singleSweep = false; settings = {}; normalize.active = false; normalize.measuring = false; normalize.points = 0; normalize.dialog.reset(); normalize.f_start = 0; normalize.f_stop = 0; normalize.points = 0; normalize.Level = nullptr; normalize.measure = nullptr; normalize.enable = nullptr; traceModel.setSource(TraceModel::DataSource::SA); // Create default traces preset(); // Create menu entries and connections // Sweep toolbar auto tb_sweep = new QToolBar("Sweep"); auto bSingle = new QPushButton("Single"); bSingle->setToolTip("Single sweep"); bSingle->setCheckable(true); connect(bSingle, &QPushButton::toggled, this, &SpectrumAnalyzer::SetSingleSweep); connect(this, &SpectrumAnalyzer::singleSweepChanged, bSingle, &QPushButton::setChecked); tb_sweep->addWidget(bSingle); 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, &SpectrumAnalyzer::SetStartFreq); connect(this, &SpectrumAnalyzer::startFreqChanged, eStart, &SIUnitEdit::setValueQuiet); tb_sweep->addWidget(new QLabel("Start:")); tb_sweep->addWidget(eStart); auto eCenter = new SIUnitEdit("Hz", " kMG", 6); eCenter->setFixedWidth(width); eCenter->setToolTip("Center frequency"); connect(eCenter, &SIUnitEdit::valueChanged, this, &SpectrumAnalyzer::SetCenterFreq); connect(this, &SpectrumAnalyzer::centerFreqChanged, eCenter, &SIUnitEdit::setValueQuiet); tb_sweep->addWidget(new QLabel("Center:")); tb_sweep->addWidget(eCenter); auto eStop = new SIUnitEdit("Hz", " kMG", 6); eStop->setFixedWidth(width); eStop->setToolTip("Stop frequency"); connect(eStop, &SIUnitEdit::valueChanged, this, &SpectrumAnalyzer::SetStopFreq); connect(this, &SpectrumAnalyzer::stopFreqChanged, eStop, &SIUnitEdit::setValueQuiet); tb_sweep->addWidget(new QLabel("Stop:")); tb_sweep->addWidget(eStop); auto eSpan = new SIUnitEdit("Hz", " kMG", 6); eSpan->setFixedWidth(width); eSpan->setToolTip("Span"); connect(eSpan, &SIUnitEdit::valueChanged, this, &SpectrumAnalyzer::SetSpan); connect(this, &SpectrumAnalyzer::spanChanged, eSpan, &SIUnitEdit::setValueQuiet); tb_sweep->addWidget(new QLabel("Span:")); 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, &SpectrumAnalyzer::SetFullSpan); 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, &SpectrumAnalyzer::SpanZoomIn); 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, &SpectrumAnalyzer::SpanZoomOut); tb_sweep->addWidget(bZoomOut); auto bZero = new QPushButton("0"); bZero->setToolTip("Zero span"); bZero->setMaximumWidth(28); bZero->setMaximumHeight(24); connect(bZero, &QPushButton::clicked, this, &SpectrumAnalyzer::SetZeroSpan); tb_sweep->addWidget(bZero); window->addToolBar(tb_sweep); toolbars.insert(tb_sweep); // Acquisition toolbar auto tb_acq = new QToolBar("Acquisition"); auto eBandwidth = new SIUnitEdit("Hz", " k", 3); eBandwidth->setFixedWidth(70); eBandwidth->setToolTip("RBW"); connect(eBandwidth, &SIUnitEdit::valueChanged, this, &SpectrumAnalyzer::SetRBW); connect(this, &SpectrumAnalyzer::RBWChanged, eBandwidth, &SIUnitEdit::setValueQuiet); tb_acq->addWidget(new QLabel("RBW:")); tb_acq->addWidget(eBandwidth); tb_acq->addWidget(new QLabel("Window:")); cbWindowType = new QComboBox(); cbWindowType->addItem("None"); cbWindowType->addItem("Kaiser"); cbWindowType->addItem("Hann"); cbWindowType->addItem("Flat Top"); cbWindowType->setCurrentIndex(1); connect(cbWindowType, qOverload(&QComboBox::currentIndexChanged), [=](int index) { SetWindow((VirtualDevice::SASettings::Window) index); }); tb_acq->addWidget(cbWindowType); tb_acq->addWidget(new QLabel("Detector:")); cbDetector = new QComboBox(); cbDetector->addItem("+Peak"); cbDetector->addItem("-Peak"); cbDetector->addItem("Sample"); cbDetector->addItem("Normal"); cbDetector->addItem("Average"); cbDetector->setCurrentIndex(0); connect(cbDetector, qOverload(&QComboBox::currentIndexChanged), [=](int index) { SetDetector((VirtualDevice::SASettings::Detector) index); }); tb_acq->addWidget(cbDetector); tb_acq->addWidget(new QLabel("Averaging:")); lAverages = new QLabel("0/"); tb_acq->addWidget(lAverages); auto sbAverages = new QSpinBox; sbAverages->setRange(1, 100); sbAverages->setRange(1, 99); sbAverages->setFixedWidth(40); connect(sbAverages, qOverload(&QSpinBox::valueChanged), this, &SpectrumAnalyzer::SetAveraging); connect(this, &SpectrumAnalyzer::averagingChanged, sbAverages, &QSpinBox::setValue); tb_acq->addWidget(sbAverages); cbSignalID = new QCheckBox("Signal ID"); connect(cbSignalID, &QCheckBox::toggled, this, &SpectrumAnalyzer::SetSignalID); tb_acq->addWidget(cbSignalID); window->addToolBar(tb_acq); toolbars.insert(tb_acq); // Tracking generator toolbar auto tb_trackgen = new QToolBar("Tracking Generator"); auto cbTrackGenEnable = new QCheckBox("Tracking Generator"); connect(cbTrackGenEnable, &QCheckBox::toggled, this, &SpectrumAnalyzer::SetTGEnabled); connect(this, &SpectrumAnalyzer::TGStateChanged, cbTrackGenEnable, &QCheckBox::setChecked); tb_trackgen->addWidget(cbTrackGenEnable); cbTrackGenPort = new QComboBox(); cbTrackGenPort->addItem("Port 1"); cbTrackGenPort->addItem("Port 2"); cbTrackGenPort->setCurrentIndex(0); connect(cbTrackGenPort, qOverload(&QComboBox::currentIndexChanged), this, &SpectrumAnalyzer::SetTGPort); connect(this, &SpectrumAnalyzer::TGPortChanged, cbTrackGenPort, qOverload(&QComboBox::setCurrentIndex)); tb_trackgen->addWidget(cbTrackGenPort); auto dbm = new QDoubleSpinBox(); dbm->setFixedWidth(95); dbm->setRange(-100.0, 100.0); dbm->setSingleStep(0.25); dbm->setSuffix("dbm"); dbm->setToolTip("Level"); dbm->setKeyboardTracking(false); connect(dbm, qOverload(&QDoubleSpinBox::valueChanged), this, &SpectrumAnalyzer::SetTGLevel); connect(this, &SpectrumAnalyzer::TGLevelChanged, dbm, &QDoubleSpinBox::setValue); tb_trackgen->addWidget(new QLabel("Level:")); tb_trackgen->addWidget(dbm); auto tgOffset = new SIUnitEdit("Hz", " kMG", 6); tgOffset->setFixedWidth(width); tgOffset->setToolTip("Tracking generator offset"); connect(tgOffset, &SIUnitEdit::valueChanged, this, &SpectrumAnalyzer::SetTGOffset); connect(this, &SpectrumAnalyzer::TGOffsetChanged, tgOffset, &SIUnitEdit::setValueQuiet); tb_trackgen->addWidget(new QLabel("Offset:")); tb_trackgen->addWidget(tgOffset); normalize.enable = new QCheckBox("Normalize"); tb_trackgen->addWidget(normalize.enable); connect(normalize.enable, &QCheckBox::toggled, this, &SpectrumAnalyzer::EnableNormalization); normalize.Level = new SIUnitEdit("dBm", " ", 3); normalize.Level->setFixedWidth(width); normalize.Level->setValue(0); normalize.Level->setToolTip("Level to normalize to"); tb_trackgen->addWidget(new QLabel("To:")); tb_trackgen->addWidget(normalize.Level); normalize.measure = new QPushButton("Measure"); normalize.measure->setToolTip("Perform normalization measurement"); connect(normalize.measure, &QPushButton::clicked, this, &SpectrumAnalyzer::MeasureNormalization); tb_trackgen->addWidget(normalize.measure); window->addToolBar(tb_trackgen); toolbars.insert(tb_trackgen); markerModel = new MarkerModel(traceModel, this); auto tracesDock = new QDockWidget("Traces"); traceWidget = new TraceWidgetSA(traceModel, window); 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); // Set initial GUI state deviceInfoUpdated(); SetupSCPI(); // Set initial TG settings SetTGLevel(-20.0); SetTGOffset(0); SetTGEnabled(false); // 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.freqStart = pref.Startup.SA.start; settings.freqStop = pref.Startup.SA.stop; ConstrainAndUpdateFrequencies(); SetRBW(pref.Startup.SA.RBW); SetAveraging(pref.Startup.SA.averaging); settings.points = 1001; SetWindow((VirtualDevice::SASettings::Window) pref.Startup.SA.window); SetDetector((VirtualDevice::SASettings::Detector) pref.Startup.SA.detector); SetSignalID(pref.Startup.SA.signalID); } finalize(central); } void SpectrumAnalyzer::deactivate() { StoreSweepSettings(); Mode::deactivate(); } void SpectrumAnalyzer::initializeDevice() { connect(window->getDevice(), &VirtualDevice::SAmeasurementReceived, this, &SpectrumAnalyzer::NewDatapoint, Qt::UniqueConnection); // Configure initial state of device SettingsChanged(); } nlohmann::json SpectrumAnalyzer::toJSON() { nlohmann::json j; // save current sweep/acquisition settings nlohmann::json sweep; nlohmann::json freq; freq["start"] = settings.freqStart; freq["stop"] = settings.freqStop; sweep["frequency"] = freq; sweep["single"] = singleSweep; nlohmann::json acq; acq["RBW"] = settings.RBW; acq["window"] = WindowToString((VirtualDevice::SASettings::Window) settings.window).toStdString(); acq["detector"] = DetectorToString((VirtualDevice::SASettings::Detector) settings.detector).toStdString(); acq["signal ID"] = settings.signalID ? true : false; sweep["acquisition"] = acq; nlohmann::json tracking; tracking["enabled"] = settings.trackingGenerator ? true : false; tracking["port"] = settings.trackingPort ? 2 : 1; tracking["offset"] = settings.trackingOffset; tracking["power"] = (double) settings.trackingPower / 100.0; // convert to dBm sweep["trackingGenerator"] = tracking; if(normalize.active) { nlohmann::json norm; norm["start"] = normalize.f_start; norm["stop"] = normalize.f_stop; norm["points"] = normalize.points; norm["level"] = normalize.Level->value(); nlohmann::json jCorr; for(auto m : normalize.portCorrection) { jCorr[m.first.toStdString()] = m.second; } norm["corrections"] = jCorr; sweep["normalization"] = norm; } j["sweep"] = sweep; j["traces"] = traceModel.toJSON(); j["tiles"] = central->toJSON(); j["markers"] = markerModel->toJSON(); return j; } void SpectrumAnalyzer::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("sweep")) { // restore sweep settings auto sweep = j["sweep"]; if(sweep.contains("frequency")) { auto freq = sweep["frequency"]; SetStartFreq(freq.value("start", settings.freqStart)); SetStopFreq(freq.value("stop", settings.freqStop)); } if(sweep.contains("acquisition")) { auto acq = sweep["acquisition"]; SetRBW(acq.value("RBW", settings.RBW)); auto w = WindowFromString(QString::fromStdString(acq.value("window", ""))); if(w == VirtualDevice::SASettings::Window::Last) { // invalid, keep current value w = (VirtualDevice::SASettings::Window) settings.window; } SetWindow(w); auto d = DetectorFromString(QString::fromStdString(acq.value("detector", ""))); if(d == VirtualDevice::SASettings::Detector::Last) { // invalid, keep current value d = (VirtualDevice::SASettings::Detector) settings.detector; } SetDetector(d); SetSignalID(acq.value("signal ID", settings.signalID ? true : false)); } if(sweep.contains("trackingGenerator")) { auto tracking = sweep["trackingGenerator"]; SetTGEnabled(tracking.value("enabled", settings.trackingGenerator ? true : false)); int port = tracking.value("port", 1); // Function expects 0 for port1, 1 for port2 SetTGPort(port - 1); SetTGLevel(tracking.value("power", settings.trackingPower)); SetTGOffset(tracking.value("offset", settings.trackingOffset)); } if(sweep.contains("normalization")) { auto norm = sweep["normalization"]; // restore normalization data normalize.portCorrection.clear(); if(norm.contains("corrections")) { for(auto& el : norm["corrections"].items()) { normalize.portCorrection[QString::fromStdString(el.key())] = {}; for(auto p : el.value()) { normalize.portCorrection[QString::fromStdString(el.key())].push_back(p); } } } normalize.f_start = norm.value("start", normalize.f_start); normalize.f_stop = norm.value("stop", normalize.f_stop); normalize.points = norm.value("points", normalize.points); normalize.Level->setValue(norm.value("level", normalize.Level->value())); // check correction vector size bool correctSize = true; for(auto c : normalize.portCorrection) { if(c.second.size() != normalize.points) { correctSize = false; break; } } EnableNormalization(correctSize); } SetSingleSweep(sweep.value("single", singleSweep)); } } using namespace std; void SpectrumAnalyzer::NewDatapoint(VirtualDevice::SAMeasurement m) { if(isActive != true) { return; } if(changingSettings) { // already setting new sweep settings, ignore incoming points from old settings return; } if(singleSweep && average.getLevel() == averages) { changingSettings = true; // single sweep finished window->getDevice()->setIdle([=](bool){ changingSettings = false; }); } if(m.pointNum >= settings.points) { qWarning() << "Ignoring point with too large point number (" << m.pointNum << ")"; return; } auto m_avg = average.process(m); if(settings.freqStart == settings.freqStop) { // keep track of first point time if(m_avg.pointNum == 0) { firstPointTime = m_avg.us; m_avg.us = 0; } else { m_avg.us -= firstPointTime; } } if(normalize.measuring) { if(average.currentSweep() == averages) { // this is the last averaging sweep, use values for normalization if(normalize.portCorrection.size() > 0 || m_avg.pointNum == 0) { // add measurement for(auto m : m_avg.measurements) { normalize.portCorrection[m.first].push_back(m.second); } if(m_avg.pointNum == settings.points - 1) { // this was the last point normalize.measuring = false; normalize.f_start = settings.freqStart; normalize.f_stop = settings.freqStop; normalize.points = settings.points; EnableNormalization(true); qDebug() << "Normalization measurement complete"; } } } int percentage = (((average.currentSweep() - 1) * 100) + (m_avg.pointNum + 1) * 100 / settings.points) / averages; normalize.dialog.setValue(percentage); } if(normalize.active) { double corr = pow(10.0, normalize.Level->value() / 20.0); for(auto &m : m_avg.measurements) { m.second /= normalize.portCorrection[m.first][m_avg.pointNum]; m.second *= corr; } } traceModel.addSAData(m_avg, settings); emit dataChanged(); if(m_avg.pointNum == settings.points - 1) { UpdateAverageCount(); markerModel->updateMarkers(); } static 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; } void SpectrumAnalyzer::SettingsChanged() { changingSettings = true; if(settings.freqStop - settings.freqStart >= 1000 || settings.freqStop - settings.freqStart <= 0) { settings.points = 1001; } else { settings.points = settings.freqStop - settings.freqStart + 1; } if(settings.trackingGenerator && settings.freqStop >= 25000000) { // Check point spacing. // The highband PLL used as the tracking generator is not able to reach every frequency exactly. This // could lead to sharp drops in the spectrum at certain frequencies. If the span is wide enough with // respect to the point number, it is ensured that every displayed point has at least one sample with // a reachable PLL frequency in it. Display a warning message if this is not the case with the current // settings. auto pointSpacing = (settings.freqStop - settings.freqStart) / (settings.points - 1); // The frequency resolution of the PLL is frequency dependent (due to PLL divider). // This code assumes some knowledge of the actual hardware and probably should be moved // onto the device at some point double minSpacing = 25000; auto stop = settings.freqStop; while(stop <= 3000000000) { minSpacing /= 2; stop *= 2; } if(pointSpacing < minSpacing) { auto requiredMinSpan = minSpacing * (settings.points - 1); auto message = QString() + "Due to PLL limitations, the tracking generator can not reach every frequency exactly. " "With your current span, this could result in the signal not being detected at some bands. A minimum" " span of " + Unit::ToString(requiredMinSpan, "Hz", " kMG") + " is recommended at this stop frequency."; InformationBox::ShowMessage("Warning", message, "TrackingGeneratorSpanTooSmallWarning"); } } if(normalize.active) { // check if normalization is still valid if(normalize.f_start != settings.freqStart || normalize.f_stop != settings.freqStop || normalize.points != settings.points) { // normalization was taken at different settings, disable EnableNormalization(false); InformationBox::ShowMessage("Information", "Normalization was disabled because the span has been changed"); } } if(window->getDevice() && isActive) { window->getDevice()->setSA(settings, [=](bool){ // device received command changingSettings = false; }); } average.reset(settings.points); UpdateAverageCount(); traceModel.clearLiveData(); emit traceModel.SpanChanged(settings.freqStart, settings.freqStop); } void SpectrumAnalyzer::SetStartFreq(double freq) { settings.freqStart = freq; if(settings.freqStop < freq) { settings.freqStop = freq; } ConstrainAndUpdateFrequencies(); } void SpectrumAnalyzer::SetStopFreq(double freq) { settings.freqStop = freq; if(settings.freqStart > freq) { settings.freqStart = freq; } ConstrainAndUpdateFrequencies(); } void SpectrumAnalyzer::SetCenterFreq(double freq) { auto old_span = settings.freqStop - settings.freqStart; if (freq - old_span / 2 <= VirtualDevice::getInfo(window->getDevice()).Limits.minFreq) { // would shift start frequency below minimum settings.freqStart = 0; settings.freqStop = 2 * freq; } else if(freq + old_span / 2 >= VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq) { // would shift stop frequency above maximum settings.freqStart = 2 * freq - VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq; settings.freqStop = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq; } else { settings.freqStart = freq - old_span / 2; settings.freqStop = freq + old_span / 2; } ConstrainAndUpdateFrequencies(); } void SpectrumAnalyzer::SetSpan(double span) { auto old_center = (settings.freqStart + settings.freqStop) / 2; if(old_center < VirtualDevice::getInfo(window->getDevice()).Limits.minFreq + span / 2) { // would shift start frequency below minimum settings.freqStart = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq; settings.freqStop = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq + span; } else if(old_center > VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq - span / 2) { // would shift stop frequency above maximum settings.freqStart = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq - span; settings.freqStop = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq; } else { settings.freqStart = old_center - span / 2; settings.freqStop = settings.freqStart + span; } ConstrainAndUpdateFrequencies(); } void SpectrumAnalyzer::SetFullSpan() { settings.freqStart = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq; settings.freqStop = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq; ConstrainAndUpdateFrequencies(); } void SpectrumAnalyzer::SetZeroSpan() { auto center = (settings.freqStart + settings.freqStop) / 2; SetStartFreq(center); SetStopFreq(center); } void SpectrumAnalyzer::SpanZoomIn() { auto center = (settings.freqStart + settings.freqStop) / 2; auto old_span = settings.freqStop - settings.freqStart; settings.freqStart = center - old_span / 4; settings.freqStop = center + old_span / 4; ConstrainAndUpdateFrequencies(); } void SpectrumAnalyzer::SpanZoomOut() { auto center = (settings.freqStart + settings.freqStop) / 2; auto old_span = settings.freqStop - settings.freqStart; if(center > old_span) { settings.freqStart = center - old_span; } else { settings.freqStart = 0; } settings.freqStop = center + old_span; ConstrainAndUpdateFrequencies(); } void SpectrumAnalyzer::SetSingleSweep(bool single) { if(singleSweep != single) { singleSweep = single; emit singleSweepChanged(single); } SettingsChanged(); } void SpectrumAnalyzer::SetRBW(double bandwidth) { if(bandwidth > VirtualDevice::getInfo(window->getDevice()).Limits.maxRBW) { bandwidth = VirtualDevice::getInfo(window->getDevice()).Limits.maxRBW; } else if(bandwidth < VirtualDevice::getInfo(window->getDevice()).Limits.minRBW) { bandwidth = VirtualDevice::getInfo(window->getDevice()).Limits.minRBW; } settings.RBW = bandwidth; emit RBWChanged(settings.RBW); SettingsChanged(); } void SpectrumAnalyzer::SetWindow(VirtualDevice::SASettings::Window w) { settings.window = w; cbWindowType->setCurrentIndex((int) w); SettingsChanged(); } void SpectrumAnalyzer::SetDetector(VirtualDevice::SASettings::Detector d) { settings.detector = d; cbDetector->setCurrentIndex((int) d); SettingsChanged(); } void SpectrumAnalyzer::SetAveraging(unsigned int averages) { this->averages = averages; average.setAverages(averages); emit averagingChanged(averages); SettingsChanged(); } void SpectrumAnalyzer::SetSignalID(bool enabled) { settings.signalID = enabled ? 1 : 0; cbSignalID->setChecked(enabled); SettingsChanged(); } void SpectrumAnalyzer::SetTGEnabled(bool enabled) { if(enabled != settings.trackingGenerator) { settings.trackingGenerator = enabled; emit TGStateChanged(enabled); SettingsChanged(); } normalize.Level->setEnabled(enabled); normalize.enable->setEnabled(enabled); normalize.measure->setEnabled(enabled); if(!enabled && normalize.active) { // disable normalization when TG is turned off EnableNormalization(false); } } void SpectrumAnalyzer::SetTGPort(int port) { if(port < 0 || port >= cbTrackGenPort->count()) { return; } if(port != settings.trackingPort) { settings.trackingPort = port; emit TGPortChanged(port); if(settings.trackingGenerator) { SettingsChanged(); } } } void SpectrumAnalyzer::SetTGLevel(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 TGLevelChanged(level); settings.trackingPower = level * 100; if(settings.trackingGenerator) { SettingsChanged(); } } void SpectrumAnalyzer::SetTGOffset(double offset) { settings.trackingOffset = offset; ConstrainAndUpdateFrequencies(); if(settings.trackingGenerator) { SettingsChanged(); } } void SpectrumAnalyzer::MeasureNormalization() { if(!window->getDevice()) { return; } normalize.active = false; normalize.portCorrection.clear(); for(auto m : window->getDevice()->availableSAMeasurements()) { normalize.portCorrection[m] = {}; } normalize.measuring = true; normalize.dialog.setLabelText("Taking normalization measurement..."); normalize.dialog.setCancelButtonText("Abort"); normalize.dialog.setWindowTitle("Normalization"); normalize.dialog.setValue(0); normalize.dialog.setWindowModality(Qt::ApplicationModal); // always show the dialog normalize.dialog.setMinimumDuration(0); connect(&normalize.dialog, &QProgressDialog::canceled, this, &SpectrumAnalyzer::AbortNormalization); // trigger beginning of next sweep SettingsChanged(); } void SpectrumAnalyzer::AbortNormalization() { EnableNormalization(false); ClearNormalization(); normalize.dialog.reset(); } void SpectrumAnalyzer::EnableNormalization(bool enabled) { if(enabled != normalize.active) { if(enabled) { // check if measurements already taken if(normalize.f_start == settings.freqStart && normalize.f_stop == settings.freqStop && normalize.points == settings.points) { // same settings as with normalization measurement, can enable normalize.active = true; } else { // needs to take measurement first MeasureNormalization(); } } else { // disabled normalize.active = false; } } normalize.enable->blockSignals(true); normalize.enable->setChecked(normalize.active); normalize.enable->blockSignals(false); } void SpectrumAnalyzer::ClearNormalization() { EnableNormalization(false); normalize.active = false; normalize.measuring = false; normalize.points = 0; normalize.portCorrection.clear(); normalize.f_start = 0; normalize.f_stop = 0; } void SpectrumAnalyzer::SetNormalizationLevel(double level) { normalize.Level->setValueQuiet(level); emit NormalizationLevelChanged(level); } void SpectrumAnalyzer::SetupSCPI() { 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.freqStop - settings.freqStart, '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.freqStart, '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.freqStart + settings.freqStop)/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.freqStop, 'f', 0); })); scpi_freq->add(new SCPICommand("FULL", [=](QStringList params) -> QString { Q_UNUSED(params) SetFullSpan(); 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_acq = new SCPINode("ACQuisition"); SCPINode::add(scpi_acq); scpi_acq->add(new SCPICommand("RBW", [=](QStringList params) -> QString { unsigned long long newval; if(!SCPI::paramToULongLong(params, 0, newval)) { return SCPI::getResultName(SCPI::Result::Error); } else { SetRBW(newval); return SCPI::getResultName(SCPI::Result::Empty); } }, [=](QStringList) -> QString { return QString::number(settings.RBW); })); scpi_acq->add(new SCPICommand("WINDow", [=](QStringList params) -> QString { if (params.size() != 1) { return SCPI::getResultName(SCPI::Result::Error); } if (params[0] == "NONE") { SetWindow(VirtualDevice::SASettings::Window::None); } else if(params[0] == "KAISER") { SetWindow(VirtualDevice::SASettings::Window::Kaiser); } else if(params[0] == "HANN") { SetWindow(VirtualDevice::SASettings::Window::Hann); } else if(params[0] == "FLATTOP") { SetWindow(VirtualDevice::SASettings::Window::FlatTop); } else { return "INVALID WINDOW"; } return SCPI::getResultName(SCPI::Result::Empty); }, [=](QStringList) -> QString { switch((VirtualDevice::SASettings::Window) settings.window) { case VirtualDevice::SASettings::Window::None: return "NONE"; case VirtualDevice::SASettings::Window::Kaiser: return "KAISER"; case VirtualDevice::SASettings::Window::Hann: return "HANN"; case VirtualDevice::SASettings::Window::FlatTop: return "FLATTOP"; default: return SCPI::getResultName(SCPI::Result::Error); } })); scpi_acq->add(new SCPICommand("DETector", [=](QStringList params) -> QString { if (params.size() != 1) { return SCPI::getResultName(SCPI::Result::Error); } if (params[0] == "+PEAK") { SetDetector(VirtualDevice::SASettings::Detector::PPeak); } else if(params[0] == "-PEAK") { SetDetector(VirtualDevice::SASettings::Detector::NPeak); } else if(params[0] == "NORMAL") { SetDetector(VirtualDevice::SASettings::Detector::Normal); } else if(params[0] == "SAMPLE") { SetDetector(VirtualDevice::SASettings::Detector::Sample); } else if(params[0] == "AVERAGE") { SetDetector(VirtualDevice::SASettings::Detector::Average); } else { return "INVALID MDOE"; } return SCPI::getResultName(SCPI::Result::Empty); }, [=](QStringList) -> QString { switch((VirtualDevice::SASettings::Detector) settings.detector) { case VirtualDevice::SASettings::Detector::PPeak: return "+PEAK"; case VirtualDevice::SASettings::Detector::NPeak: return "-PEAK"; case VirtualDevice::SASettings::Detector::Normal: return "NORMAL"; case VirtualDevice::SASettings::Detector::Sample: return "SAMPLE"; case VirtualDevice::SASettings::Detector::Average: return "AVERAGE"; default: return SCPI::getResultName(SCPI::Result::Error); } })); 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 ? "TRUE" : "FALSE"; })); scpi_acq->add(new SCPICommand("LIMit", nullptr, [=](QStringList) -> QString { return central->allLimitsPassing() ? "PASS" : "FAIL"; })); scpi_acq->add(new SCPICommand("SIGid", [=](QStringList params) -> QString { if (params.size() != 1) { return SCPI::getResultName(SCPI::Result::Error); } if(params[0] == "1" || params[0] == "TRUE") { SetSignalID(true); } else if(params[0] == "0" || params[0] == "FALSE") { SetSignalID(false); } else { return SCPI::getResultName(SCPI::Result::Error); } return SCPI::getResultName(SCPI::Result::Empty); }, [=](QStringList) -> QString { return settings.signalID ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); })); 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); })); auto scpi_tg = new SCPINode("TRACKing"); SCPINode::add(scpi_tg); scpi_tg->add(new SCPICommand("ENable", [=](QStringList params) -> QString { if (params.size() != 1) { return SCPI::getResultName(SCPI::Result::Error); } if(params[0] == "1" || params[0] == "TRUE") { SetTGEnabled(true); } else if(params[0] == "0" || params[0] == "FALSE") { SetTGEnabled(false); } else { return SCPI::getResultName(SCPI::Result::Error); } return SCPI::getResultName(SCPI::Result::Empty); }, [=](QStringList) -> QString { return settings.trackingGenerator ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); })); scpi_tg->add(new SCPICommand("Port", [=](QStringList params) -> QString { if (params.size() != 1) { return SCPI::getResultName(SCPI::Result::Error); } unsigned long long newval; if(!SCPI::paramToULongLong(params, 0, newval)) { return SCPI::getResultName(SCPI::Result::Error); } else if(newval <= VirtualDevice::getInfo(window->getDevice()).ports){ SetTGPort(newval); return SCPI::getResultName(SCPI::Result::Empty); } else { // invalid port number return SCPI::getResultName(SCPI::Result::Error); } return SCPI::getResultName(SCPI::Result::Empty); }, [=](QStringList) -> QString { return QString::number(settings.trackingPort); })); scpi_tg->add(new SCPICommand("LVL", [=](QStringList params) -> QString { double newval; if(!SCPI::paramToDouble(params, 0, newval)) { return SCPI::getResultName(SCPI::Result::Error); } else { SetTGLevel(newval); return SCPI::getResultName(SCPI::Result::Empty); } }, [=](QStringList) -> QString { return QString::number(settings.trackingPower / 100.0); })); scpi_tg->add(new SCPICommand("OFFset", [=](QStringList params) -> QString { long newval; if(!SCPI::paramToLong(params, 0, newval)) { return SCPI::getResultName(SCPI::Result::Error); } else { SetTGOffset(newval); return SCPI::getResultName(SCPI::Result::Empty); } }, [=](QStringList) -> QString { return QString::number(settings.trackingOffset); })); auto scpi_norm = new SCPINode("NORMalize"); scpi_tg->add(scpi_norm); scpi_norm->add(new SCPICommand("ENable", [=](QStringList params) -> QString { if (params.size() != 1) { return SCPI::getResultName(SCPI::Result::Error); } if(params[0] == "1" || params[0] == "TRUE") { EnableNormalization(true); } else if(params[0] == "0" || params[0] == "FALSE") { EnableNormalization(false); } else { return SCPI::getResultName(SCPI::Result::Error); } return SCPI::getResultName(SCPI::Result::Empty); }, [=](QStringList) -> QString { return normalize.active ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); })); scpi_norm->add(new SCPICommand("MEASure", [=](QStringList params) -> QString { Q_UNUSED(params) MeasureNormalization(); return ""; }, nullptr)); scpi_norm->add(new SCPICommand("LVL", [=](QStringList params) -> QString { double newval; if(!SCPI::paramToDouble(params, 0, newval)) { return SCPI::getResultName(SCPI::Result::Error); } else { SetNormalizationLevel(newval); return SCPI::getResultName(SCPI::Result::Empty); } }, [=](QStringList) -> QString { return QString::number(normalize.Level->value()); })); SCPINode::add(traceWidget); } void SpectrumAnalyzer::UpdateAverageCount() { lAverages->setText(QString::number(average.getLevel()) + "/"); } void SpectrumAnalyzer::ConstrainAndUpdateFrequencies() { if(settings.freqStop > VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq) { settings.freqStop = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq; } if(settings.freqStart > settings.freqStop) { settings.freqStart = settings.freqStop; } if(settings.freqStart < VirtualDevice::getInfo(window->getDevice()).Limits.minFreq) { settings.freqStart = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq; } bool trackingOffset_limited = false; if(settings.freqStop + settings.trackingOffset > VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq) { trackingOffset_limited = true; settings.trackingOffset = VirtualDevice::getInfo(window->getDevice()).Limits.maxFreq - settings.freqStop; } if(settings.freqStart + settings.trackingOffset < VirtualDevice::getInfo(window->getDevice()).Limits.minFreq) { trackingOffset_limited = true; settings.trackingOffset = VirtualDevice::getInfo(window->getDevice()).Limits.minFreq - settings.freqStart; } if(trackingOffset_limited) { InformationBox::ShowMessage("Warning", "The selected tracking generator offset is not reachable for all frequencies with the current span. " "The tracking generator offset has been constrained according to the selected start and stop frequencies"); } emit startFreqChanged(settings.freqStart); emit stopFreqChanged(settings.freqStop); emit spanChanged(settings.freqStop - settings.freqStart); emit centerFreqChanged((settings.freqStop + settings.freqStart)/2); emit TGOffsetChanged(settings.trackingOffset); SettingsChanged(); } void SpectrumAnalyzer::LoadSweepSettings() { QSettings s; auto& pref = Preferences::getInstance(); settings.freqStart = s.value("SAStart", pref.Startup.SA.start).toULongLong(); settings.freqStop = s.value("SAStop", pref.Startup.SA.stop).toULongLong(); ConstrainAndUpdateFrequencies(); SetRBW(s.value("SARBW", pref.Startup.SA.RBW).toUInt()); settings.points = 1001; SetWindow((VirtualDevice::SASettings::Window) s.value("SAWindow", pref.Startup.SA.window).toInt()); SetDetector((VirtualDevice::SASettings::Detector) s.value("SADetector", pref.Startup.SA.detector).toInt()); SetSignalID(s.value("SASignalID", pref.Startup.SA.signalID).toBool()); SetAveraging(s.value("SAAveraging", pref.Startup.SA.averaging).toInt()); } void SpectrumAnalyzer::StoreSweepSettings() { QSettings s; s.setValue("SAStart", static_cast(settings.freqStart)); s.setValue("SAStop", static_cast(settings.freqStop)); s.setValue("SARBW", settings.RBW); s.setValue("SAWindow", (int) settings.window); s.setValue("SADetector", (int) settings.detector); s.setValue("SAAveraging", averages); s.setValue("SASignalID", static_cast(settings.signalID)); } void SpectrumAnalyzer::createDefaultTracesAndGraphs(int ports) { central->clear(); auto traceXY = new TraceXYPlot(traceModel); traceXY->setYAxis(0, YAxis::Type::Magnitude, false, false, -120,0,10); traceXY->setYAxis(1, YAxis::Type::Disabled, false, true, 0,0,1); traceXY->updateSpan(settings.freqStart, settings.freqStop); central->setPlot(traceXY); QColor defaultColors[] = {Qt::yellow, Qt::blue, Qt::red, Qt::green, Qt::gray, Qt::cyan, Qt::magenta, Qt::white}; for(int i=0;ienableTrace(trace, true); } } void SpectrumAnalyzer::setAveragingMode(Averaging::Mode mode) { average.setMode(mode); } void SpectrumAnalyzer::preset() { for(auto t : traceModel.getTraces()) { if(Trace::isSAParameter(t->name())) { traceModel.removeTrace(t); } } // Create default traces createDefaultTracesAndGraphs(VirtualDevice::getInfo(window->getDevice()).ports); } void SpectrumAnalyzer::deviceInfoUpdated() { // new device connected, throw away normalization ClearNormalization(); auto tgPort = cbTrackGenPort->currentIndex(); cbTrackGenPort->clear(); for(unsigned int i=0;igetDevice()).ports;i++) { cbTrackGenPort->addItem("Port "+QString::number(i+1)); } SetTGPort(tgPort); } QString SpectrumAnalyzer::WindowToString(VirtualDevice::SASettings::Window w) { switch(w) { case VirtualDevice::SASettings::Window::None: return "None"; case VirtualDevice::SASettings::Window::Kaiser: return "Kaiser"; case VirtualDevice::SASettings::Window::Hann: return "Hann"; case VirtualDevice::SASettings::Window::FlatTop: return "FlatTop"; default: return "Unknown"; } } VirtualDevice::SASettings::Window SpectrumAnalyzer::WindowFromString(QString s) { for(int i=0;i<(int)VirtualDevice::SASettings::Window::Last;i++) { if(WindowToString((VirtualDevice::SASettings::Window) i) == s) { return (VirtualDevice::SASettings::Window) i; } } // not found return VirtualDevice::SASettings::Window::Last; } QString SpectrumAnalyzer::DetectorToString(VirtualDevice::SASettings::Detector d) { switch(d) { case VirtualDevice::SASettings::Detector::PPeak: return "+Peak"; case VirtualDevice::SASettings::Detector::NPeak: return "-Peak"; case VirtualDevice::SASettings::Detector::Sample: return "Sample"; case VirtualDevice::SASettings::Detector::Normal: return "Normal"; case VirtualDevice::SASettings::Detector::Average: return "Average"; default: return "Unknown"; } } VirtualDevice::SASettings::Detector SpectrumAnalyzer::DetectorFromString(QString s) { for(int i=0;i<(int)VirtualDevice::SASettings::Detector::Last;i++) { if(DetectorToString((VirtualDevice::SASettings::Detector) i) == s) { return (VirtualDevice::SASettings::Detector) i; } } // not found return VirtualDevice::SASettings::Detector::Last; }