diff --git a/Documentation/UserManual/ProgrammingGuide.pdf b/Documentation/UserManual/ProgrammingGuide.pdf index 69b4737..7ccece1 100644 Binary files a/Documentation/UserManual/ProgrammingGuide.pdf and b/Documentation/UserManual/ProgrammingGuide.pdf differ diff --git a/Documentation/UserManual/ProgrammingGuide.tex b/Documentation/UserManual/ProgrammingGuide.tex index bf2bf35..79b7599 100644 --- a/Documentation/UserManual/ProgrammingGuide.tex +++ b/Documentation/UserManual/ProgrammingGuide.tex @@ -573,6 +573,9 @@ These commands change or query spectrum analyzer settings. Although most of them \subsubsection{SA:FREQuency:FULL} \event{Sets the device to the maximum span possible}{SA:FREQuency:FULL}{None} +\subsubsection{SA:FREQuency:ZERO} +\event{Sets the device to zero span mode}{SA:FREQuency:ZERO}{None} + \subsubsection{SA:ACQuisition:RBW} \event{Sets the resolution bandwidth}{SA:ACQuisition:IFBW}{, in Hz} \query{Queries the currently selected resolution bandwidth}{SA:ACQuisition:IFBW?}{None}{resolution bandwidth in Hz} diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index 701b396..d0f290a 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -135,6 +135,13 @@ SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window, QString name) 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); @@ -450,6 +457,16 @@ void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d) d = average.process(d); + if(settings.f_start == settings.f_stop) { + // keep track of first point time + if(d.pointNum == 0) { + firstPointTime = d.us; + d.us = 0; + } else { + d.us -= firstPointTime; + } + } + if(normalize.measuring) { if(average.currentSweep() == averages) { // this is the last averaging sweep, use values for normalization @@ -464,7 +481,7 @@ void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d) normalize.f_stop = settings.f_stop; normalize.points = settings.pointNum; EnableNormalization(true); - qDebug() << "Normalization measuremen complete"; + qDebug() << "Normalization measurement complete"; } } } @@ -496,7 +513,7 @@ void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d) void SpectrumAnalyzer::SettingsChanged() { changingSettings = true; - if(settings.f_stop - settings.f_start >= 1000) { + if(settings.f_stop - settings.f_start >= 1000 || settings.f_stop - settings.f_start <= 0) { settings.pointNum = 1001; } else { settings.pointNum = settings.f_stop - settings.f_start + 1; @@ -505,10 +522,16 @@ void SpectrumAnalyzer::SettingsChanged() settings.applySourceCorrection = 1; auto pref = Preferences::getInstance(); - if(!settings.trackingGenerator && pref.Acquisition.useDFTinSAmode && settings.RBW <= pref.Acquisition.RBWLimitForDFT) { - // Enable DFT if below RBW threshold and TG is not enabled - settings.UseDFT = 1; + if(settings.f_stop > settings.f_start) { + // non-zerospan, check usability of DFT + if(!settings.trackingGenerator && pref.Acquisition.useDFTinSAmode && settings.RBW <= pref.Acquisition.RBWLimitForDFT) { + // Enable DFT if below RBW threshold and TG is not enabled + settings.UseDFT = 1; + } else { + settings.UseDFT = 0; + } } else { + // zerospan, DFT not usable settings.UseDFT = 0; } @@ -620,6 +643,13 @@ void SpectrumAnalyzer::SetFullSpan() ConstrainAndUpdateFrequencies(); } +void SpectrumAnalyzer::SetZeroSpan() +{ + auto center = (settings.f_start + settings.f_stop) / 2; + SetStartFreq(center); + SetStopFreq(center); +} + void SpectrumAnalyzer::SpanZoomIn() { auto center = (settings.f_start + settings.f_stop) / 2; @@ -853,6 +883,11 @@ void SpectrumAnalyzer::SetupSCPI() SetFullSpan(); return ""; }, nullptr)); + scpi_freq->add(new SCPICommand("ZERO", [=](QStringList params) -> QString { + Q_UNUSED(params) + SetZeroSpan(); + return ""; + }, nullptr)); auto scpi_acq = new SCPINode("ACQuisition"); SCPINode::add(scpi_acq); scpi_acq->add(new SCPICommand("RBW", [=](QStringList params) -> QString { diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h index 6a39a12..d247d25 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h @@ -61,6 +61,7 @@ private slots: void SetCenterFreq(double freq); void SetSpan(double span); void SetFullSpan(); + void SetZeroSpan(); void SpanZoomIn(); void SpanZoomOut(); void SetSingleSweep(bool single); @@ -92,6 +93,7 @@ private: bool changingSettings; unsigned int averages; bool singleSweep; + double firstPointTime; // timestamp of the first point in the sweep, only use when zerospan is used TraceModel traceModel; TraceWidget *traceWidget; MarkerModel *markerModel; diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index ba1a4fd..74365db 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -120,11 +120,16 @@ void Trace::addData(const Trace::Data& d, DataType domain, double reference_impe emit outputSamplesChanged(index, index + 1); } -void Trace::addData(const Trace::Data &d, const Protocol::SpectrumAnalyzerSettings &s) +void Trace::addData(const Trace::Data &d, const Protocol::SpectrumAnalyzerSettings &s, int index) { settings.SA = s; settings.valid = true; - addData(d, DataType::Frequency); + auto domain = DataType::Frequency; + if (s.f_start == s.f_stop) { + // in zerospan mode + domain = DataType::TimeZeroSpan; + } + addData(d, domain, 50.0, index); } void Trace::setName(QString name) { diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index 46ff999..efeeaf7 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -46,15 +46,15 @@ public: void clear(); void addData(const Data& d, DataType domain, double reference_impedance = 50.0, int index = -1); - void addData(const Data& d, const Protocol::SpectrumAnalyzerSettings& s); + void addData(const Data& d, const Protocol::SpectrumAnalyzerSettings& s, int index = -1); void setName(QString name); void setVelocityFactor(double v); void fillFromTouchstone(Touchstone &t, unsigned int parameter); QString fillFromCSV(CSV &csv, unsigned int parameter); // returns the suggested trace name (not yet set in member data) static void fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector &data); void fromLivedata(LivedataType type, LiveParameter param); - QString name() { return _name; }; - QColor color() { return _color; }; + QString name() { return _name; } + QColor color() { return _color; } bool isVisible(); void pause(); void resume(); @@ -65,7 +65,7 @@ public: bool isReflection(); LiveParameter liveParameter() { return _liveParam; } LivedataType liveType() { return _liveType; } - TraceMath::DataType outputType() const { return lastMath->getDataType(); }; + TraceMath::DataType outputType() const { return lastMath->getDataType(); } unsigned int size() const; double minX(); double maxX(); @@ -123,7 +123,7 @@ public: virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; - Type getType() override {return Type::Last;}; // can return invalid type, this will never be called + Type getType() override {return Type::Last;} // can return invalid type, this will never be called // Traces are referenced by pointers throughout this project (e.g. when added to a graph) // When saving the current graph configuration, the pointer is not useful. Instead a trace diff --git a/Software/PC_Application/Traces/tracemodel.cpp b/Software/PC_Application/Traces/tracemodel.cpp index 7f343cd..d3915a7 100644 --- a/Software/PC_Application/Traces/tracemodel.cpp +++ b/Software/PC_Application/Traces/tracemodel.cpp @@ -248,8 +248,15 @@ void TraceModel::addSAData(const Protocol::SpectrumAnalyzerResult& d, const Prot source = DataSource::SA; for(auto t : traces) { if (t->isLive() && !t->isPaused()) { + int index = -1; Trace::Data td; - td.x = d.frequency; + if(settings.f_start == settings.f_stop) { + // in zerospan mode, insert data by index + index = d.pointNum; + td.x = (double) d.us / 1000000.0; + } else { + td.x = d.frequency; + } switch(t->liveParameter()) { case Trace::LiveParameter::Port1: td.y = complex(d.port1, 0); break; case Trace::LiveParameter::Port2: td.y = complex(d.port2, 0); break; @@ -257,7 +264,7 @@ void TraceModel::addSAData(const Protocol::SpectrumAnalyzerResult& d, const Prot // not a SA trace, skip continue; } - t->addData(td, settings); + t->addData(td, settings, index); } } } diff --git a/Software/VNA_embedded/Application/Communication/Protocol.hpp b/Software/VNA_embedded/Application/Communication/Protocol.hpp index 8e31400..424a70c 100644 --- a/Software/VNA_embedded/Application/Communication/Protocol.hpp +++ b/Software/VNA_embedded/Application/Communication/Protocol.hpp @@ -152,7 +152,16 @@ using SpectrumAnalyzerSettings = struct _spectrumAnalyzerSettings { using SpectrumAnalyzerResult = struct _spectrumAnalyzerResult { float port1; float port2; - uint64_t frequency; + union { + struct { + // for non-zero span + uint64_t frequency; + }; + struct { + // for zero span + uint64_t us; // time in us since first datapoint + }; + }; uint16_t pointNum; }; diff --git a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp index 23d9ebd..d125da0 100644 --- a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp +++ b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp @@ -36,6 +36,10 @@ static uint8_t attenuator; static int64_t trackingFreq; static bool trackingLowband; +static uint64_t firstPointTime; +static bool firstPoint; +static bool zerospan; + static void StartNextSample() { uint64_t freq = s.f_start + (s.f_stop - s.f_start) * pointCnt / (points - 1); freq = Cal::FrequencyCorrectionToDevice(freq); @@ -205,6 +209,8 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) { points += s.pointNum - points % s.pointNum; binSize = points / s.pointNum; LOG_DEBUG("%u displayed points, resulting in %lu points and bins of size %u", s.pointNum, points, binSize); + + zerospan = (s.f_start == s.f_stop); // set initial state pointCnt = 0; // enable the required hardware resources @@ -256,6 +262,7 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) { } lastLO2 = 0; + firstPoint = true; active = true; StartNextSample(); } @@ -375,7 +382,19 @@ void SA::Work() { // Send result to application p.type = Protocol::PacketType::SpectrumAnalyzerResult; // measurements are already up to date, fill remaining fields - p.spectrumResult.frequency = s.f_start + (s.f_stop - s.f_start) * binIndex / (s.pointNum - 1); + if(zerospan) { + uint64_t timestamp = HW::getLastISRTimestamp(); + if(firstPoint) { + p.spectrumResult.us = 0; + firstPointTime = timestamp; + firstPoint = false; + } else { + p.spectrumResult.us = timestamp - firstPointTime; + } + } else { + // non-zero span, set frequency + p.spectrumResult.frequency = s.f_start + (s.f_stop - s.f_start) * binIndex / (s.pointNum - 1); + } // scale approximately (constant determined empirically) p.spectrumResult.port1 /= 253000000.0; p.spectrumResult.port2 /= 253000000.0;