diff --git a/Documentation/UserManual/manual.pdf b/Documentation/UserManual/manual.pdf index c833b45..be2e63b 100644 Binary files a/Documentation/UserManual/manual.pdf and b/Documentation/UserManual/manual.pdf differ diff --git a/Documentation/UserManual/manual.tex b/Documentation/UserManual/manual.tex index dbd1e46..eac74fe 100644 --- a/Documentation/UserManual/manual.tex +++ b/Documentation/UserManual/manual.tex @@ -245,6 +245,8 @@ This dock shows the debug output of the \vna{} (the same messages as on the inte \subsubsection{File Menu} Changing the default setup of the application can require a lot of configuration, especially if some of the more complicated math or de-embedding options are used. To simplify this process, setup-files can be saved and opened, which perform these steps automatically. A setup file contains all settings and configuration of these elements: \begin{itemize} +\item Sweep and Acquisition settings (start/stop frequency, IF bandwidth, ...) +\item The currently active mode (VNA, Signalgenerator or Spectrumanalyzer) \item Traces (Name, Color, Parameters, ...) \item Math operations applied to the traces \item Markers diff --git a/Software/PC_Application/Generator/generator.cpp b/Software/PC_Application/Generator/generator.cpp index 04dbc6f..8106618 100644 --- a/Software/PC_Application/Generator/generator.cpp +++ b/Software/PC_Application/Generator/generator.cpp @@ -40,6 +40,19 @@ void Generator::initializeDevice() updateDevice(); } +nlohmann::json Generator::toJSON() +{ + return central->toJSON(); +} + +void Generator::fromJSON(nlohmann::json j) +{ + if(j.is_null()) { + return; + } + central->fromJSON(j); +} + void Generator::updateDevice() { if(!window->getDevice() || Mode::getActiveMode() != this) { diff --git a/Software/PC_Application/Generator/generator.h b/Software/PC_Application/Generator/generator.h index 3418a1b..0897bf4 100644 --- a/Software/PC_Application/Generator/generator.h +++ b/Software/PC_Application/Generator/generator.h @@ -13,8 +13,8 @@ public: void initializeDevice() override; // Nothing to do for now - virtual nlohmann::json toJSON() override {return nlohmann::json();}; - virtual void fromJSON(nlohmann::json j) override {Q_UNUSED(j)}; + virtual nlohmann::json toJSON() override; + virtual void fromJSON(nlohmann::json j) override; private slots: void updateDevice(); diff --git a/Software/PC_Application/Generator/signalgenwidget.cpp b/Software/PC_Application/Generator/signalgenwidget.cpp index 01b0218..4b93192 100644 --- a/Software/PC_Application/Generator/signalgenwidget.cpp +++ b/Software/PC_Application/Generator/signalgenwidget.cpp @@ -157,6 +157,44 @@ Protocol::GeneratorSettings SignalgeneratorWidget::getDeviceStatus() return s; } +nlohmann::json SignalgeneratorWidget::toJSON() +{ + nlohmann::json j; + j["frequency"] = ui->frequency->value(); + j["power"] = ui->levelSpin->value(); + if(ui->EnablePort1->isChecked()) { + j["port"] = 1; + } else if(ui->EnablePort2->isChecked()) { + j["port"] = 2; + } else { + j["port"] = 0; + } + nlohmann::json sweep; + sweep["span"] = ui->span->value(); + sweep["steps"] = ui->steps->value(); + sweep["dwell"] = ui->dwell->value(); + sweep["enabled"] = ui->EnabledSweep->isChecked(); + j["sweep"] = sweep; + return j; +} + +void SignalgeneratorWidget::fromJSON(nlohmann::json j) +{ + setFrequency(j.value("frequency", ui->frequency->value())); + setLevel(j.value("power", ui->levelSpin->value())); + setPort(j.value("port", 0)); + if(j.contains("sweep")) { + auto sweep = j["sweep"]; + // extract sweep settings, keeping current values as default + ui->span->setValue(sweep.value("span", ui->span->value())); + ui->steps->setValue(sweep.value("steps", ui->steps->value())); + ui->dwell->setValue(sweep.value("dwell", ui->dwell->value())); + ui->EnabledSweep->setChecked(sweep.value("enabled", false)); + } else { + ui->EnabledSweep->setChecked(false); + } +} + void SignalgeneratorWidget::setLevel(double level) { // TODO constrain to frequency dependent levels diff --git a/Software/PC_Application/Generator/signalgenwidget.h b/Software/PC_Application/Generator/signalgenwidget.h index 90b72bf..1ebcec9 100644 --- a/Software/PC_Application/Generator/signalgenwidget.h +++ b/Software/PC_Application/Generator/signalgenwidget.h @@ -3,12 +3,13 @@ #include #include "Device/device.h" +#include "savable.h" namespace Ui { class SignalgeneratorWidget; } -class SignalgeneratorWidget : public QWidget +class SignalgeneratorWidget : public QWidget, public Savable { Q_OBJECT @@ -17,6 +18,8 @@ public: ~SignalgeneratorWidget(); Protocol::GeneratorSettings getDeviceStatus(); + virtual nlohmann::json toJSON() override; + virtual void fromJSON(nlohmann::json j) override; signals: void SettingsChanged(); diff --git a/Software/PC_Application/Generator/signalgenwidget.ui b/Software/PC_Application/Generator/signalgenwidget.ui index a1bfe94..a8da534 100644 --- a/Software/PC_Application/Generator/signalgenwidget.ui +++ b/Software/PC_Application/Generator/signalgenwidget.ui @@ -181,7 +181,7 @@ - 0MHz + @@ -195,7 +195,7 @@ - 100 + @@ -209,7 +209,7 @@ - 100ms + @@ -226,7 +226,7 @@ false - 0MHz + true diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index 79fe142..7778968 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -297,6 +297,37 @@ void SpectrumAnalyzer::initializeDevice() nlohmann::json SpectrumAnalyzer::toJSON() { nlohmann::json j; + // save current sweep/acquisition settings + nlohmann::json sweep; + nlohmann::json freq; + freq["start"] = settings.f_start; + freq["stop"] = settings.f_stop; + sweep["frequency"] = freq; + nlohmann::json acq; + acq["RBW"] = settings.RBW; + acq["window"] = WindowToString((Window) settings.WindowType).toStdString(); + acq["detector"] = DetectorToString((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.trackingGeneratorPort ? 2 : 1; + tracking["offset"] = settings.trackingGeneratorOffset; + tracking["power"] = settings.trackingPower; + 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(); + norm["port1"] = normalize.port1Correction; + norm["port2"] = normalize.port2Correction; + sweep["normalization"] = norm; + } + j["sweep"] = sweep; + j["traces"] = traceModel.toJSON(); j["tiles"] = central->toJSON(); j["markers"] = markerModel->toJSON(); @@ -305,6 +336,9 @@ nlohmann::json SpectrumAnalyzer::toJSON() void SpectrumAnalyzer::fromJSON(nlohmann::json j) { + if(j.is_null()) { + return; + } if(j.contains("traces")) { traceModel.fromJSON(j["traces"]); } @@ -314,6 +348,63 @@ void SpectrumAnalyzer::fromJSON(nlohmann::json j) 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.f_start)); + SetStartFreq(freq.value("start", settings.f_start)); + } + if(sweep.contains("acquisition")) { + auto acq = sweep["acquisition"]; + SetRBW(acq.value("RBW", settings.RBW)); + auto w = WindowFromString(QString::fromStdString(acq.value("window", ""))); + if(w == Window::Last) { + // invalid, keep current value + w = (Window) settings.WindowType; + } + SetWindow(w); + auto d = DetectorFromString(QString::fromStdString(acq.value("detector", ""))); + if(d == Detector::Last) { + // invalid, keep current value + d = (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.trackingGeneratorOffset)); + } + if(sweep.contains("normalization")) { + auto norm = sweep["normalization"]; + // restore normalization data + normalize.port1Correction.clear(); + for(double p1 : norm["port1"]) { + normalize.port1Correction.push_back(p1); + } + normalize.port2Correction.clear(); + for(double p2 : norm["port2"]) { + normalize.port2Correction.push_back(p2); + } + 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())); + if((normalize.port1Correction.size() == normalize.points) && (normalize.port1Correction.size() == normalize.points)) { + // got the correct number of points + EnableNormalization(true); + } else { + EnableNormalization(false); + } + } + } } using namespace std; @@ -570,7 +661,7 @@ void SpectrumAnalyzer::SetTGEnabled(bool enabled) void SpectrumAnalyzer::SetTGPort(int port) { - if(port < 01 || port > 1) { + if(port < 0 || port > 1) { return; } if(port != settings.trackingGeneratorPort) { @@ -971,3 +1062,48 @@ void SpectrumAnalyzer::updateGraphColors() { emit graphColorsChanged(); } + +QString SpectrumAnalyzer::WindowToString(SpectrumAnalyzer::Window w) +{ + switch(w) { + case Window::None: return "None"; + case Window::Kaiser: return "Kaiser"; + case Window::Hann: return "Hann"; + case Window::FlatTop: return "FlatTop"; + default: return "Unknown"; + } +} + +SpectrumAnalyzer::Window SpectrumAnalyzer::WindowFromString(QString s) +{ + for(int i=0;i<(int)Window::Last;i++) { + if(WindowToString((Window) i) == s) { + return (Window) i; + } + } + // not found + return Window::Last; +} + +QString SpectrumAnalyzer::DetectorToString(SpectrumAnalyzer::Detector d) +{ + switch(d) { + case Detector::PPeak: return "+Peak"; + case Detector::NPeak: return "-Peak"; + case Detector::Sample: return "Sample"; + case Detector::Normal: return "Normal"; + case Detector::Average: return "Average"; + default: return "Unknown"; + } +} + +SpectrumAnalyzer::Detector SpectrumAnalyzer::DetectorFromString(QString s) +{ + for(int i=0;i<(int)Detector::Last;i++) { + if(DetectorToString((Detector) i) == s) { + return (Detector) i; + } + } + // not found + return Detector::Last; +} diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h index 48bfff1..e319e3d 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h @@ -32,7 +32,8 @@ private: None = 0, Kaiser = 1, Hann = 2, - FlatTop = 3 + FlatTop = 3, + Last }; enum class Detector { PPeak = 0, @@ -40,8 +41,14 @@ private: Sample = 2, Normal = 3, Average = 4, + Last }; + static QString WindowToString(Window w); + static Window WindowFromString(QString s); + static QString DetectorToString(Detector d); + static Detector DetectorFromString(QString s); + private slots: void NewDatapoint(Protocol::SpectrumAnalyzerResult d); void StartImpedanceMatching(); diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index 8466471..83039fa 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -560,6 +560,8 @@ 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; @@ -678,6 +680,23 @@ void VNA::shutdown() 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; + sweep["frequency"] = freq; + nlohmann::json power; + power["start"] = settings.Power.start; + power["stop"] = settings.Power.stop; + power["frequency"] = settings.Power.frequency; + sweep["power"] = power; + sweep["points"] = settings.npoints; + sweep["IFBW"] = settings.bandwidth; + j["sweep"] = sweep; + j["traces"] = traceModel.toJSON(); j["tiles"] = central->toJSON(); j["markers"] = markerModel->toJSON(); @@ -688,6 +707,9 @@ nlohmann::json VNA::toJSON() void VNA::fromJSON(nlohmann::json j) { + if(j.is_null()) { + return; + } if(j.contains("traces")) { traceModel.fromJSON(j["traces"]); } @@ -703,6 +725,32 @@ void VNA::fromJSON(nlohmann::json j) } else { EnableDeembedding(false); } + + // sweep configuration has to go last sog 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)); + } + 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); + } } using namespace std; @@ -735,6 +783,7 @@ void VNA::NewDatapoint(Protocol::Datapoint d) TraceMath::DataType type; switch(settings.sweepType) { + case SweepType::Last: case SweepType::Frequency: type = TraceMath::DataType::Frequency; break; @@ -793,16 +842,21 @@ void VNA::SettingsChanged(std::function cb) s.cdbm_excitation_stop = settings.Power.stop * 100; } if(window->getDevice() && Mode::getActiveMode() == this) { - window->getDevice()->Configure(s, [=](Device::TransmissionResult res){ - // device received command, reset traces now - average.reset(s.points); - traceModel.clearLiveData(); - UpdateAverageCount(); - UpdateCalWidget(); - if(cb) { - cb(res); - } - }); + if(s.excitePort1 == 0 && s.excitePort2 == 0) { + // no signal at either port, just set the device to idel + window->getDevice()->SetIdle(); + } else { + window->getDevice()->Configure(s, [=](Device::TransmissionResult res){ + // device received command, reset traces now + average.reset(s.points); + traceModel.clearLiveData(); + UpdateAverageCount(); + UpdateCalWidget(); + if(cb) { + cb(res); + } + }); + } } emit traceModel.SpanChanged(s.f_start, s.f_stop); } @@ -1374,3 +1428,23 @@ void VNA::updateGraphColors() { emit graphColorsChanged(); } + +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; +} diff --git a/Software/PC_Application/VNA/vna.h b/Software/PC_Application/VNA/vna.h index d3b5bf9..eccba3f 100644 --- a/Software/PC_Application/VNA/vna.h +++ b/Software/PC_Application/VNA/vna.h @@ -32,7 +32,12 @@ public: enum class SweepType { Frequency = 0, Power = 1, + Last, }; + + static QString SweepTypeToString(SweepType sw); + static SweepType SweepTypeFromString(QString s); + using Settings = struct { SweepType sweepType; struct { diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index 1564697..918093f 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -911,6 +911,7 @@ void AppWindow::FrequencyCalibrationDialog() nlohmann::json AppWindow::SaveSetup() { nlohmann::json j; + j["activeMode"] = Mode::getActiveMode()->getName().toStdString(); j["VNA"] = vna->toJSON(); j["Generator"] = generator->toJSON(); j["SpectrumAnalyzer"] = spectrumAnalyzer->toJSON(); @@ -924,6 +925,16 @@ void AppWindow::LoadSetup(nlohmann::json j) vna->fromJSON(j["VNA"]); generator->fromJSON(j["Generator"]); spectrumAnalyzer->fromJSON(j["SpectrumAnalyzer"]); + + // activate the correct mode + QString modeName = QString::fromStdString(j.value("activeMode", "")); + std::vector modes = {vna, generator, spectrumAnalyzer}; + for(auto m : modes) { + if(m->getName() == modeName) { + m->activate(); + break; + } + } } Device *AppWindow::getDevice() const diff --git a/Software/PC_Application/mode.cpp b/Software/PC_Application/mode.cpp index 4a7cb6d..12e32f6 100644 --- a/Software/PC_Application/mode.cpp +++ b/Software/PC_Application/mode.cpp @@ -88,6 +88,14 @@ void Mode::activate() } activeMode = this; + // force activation of correct pushbutton in case the mode switch was done via script/setup load. + // This will trigger a second activation of this mode in the signal of the button, but since it is + // already the active mode, this function will just return -> no recursion + for(auto b : modeButtonGroup->buttons()) { + if(b->text() == name) { + b->click(); + } + } if(window->getDevice()) { initializeDevice();