diff --git a/FPGA/VNA/Sampling.vhd b/FPGA/VNA/Sampling.vhd index eb298b2..c50ef8a 100644 --- a/FPGA/VNA/Sampling.vhd +++ b/FPGA/VNA/Sampling.vhd @@ -201,6 +201,7 @@ begin clk_cnt <= 0; sample_cnt <= 0; window_sample_cnt <= 0; + window_index <= (others => '0'); phase <= (others => '0'); else -- when not idle, generate pulses for ADCs diff --git a/FPGA/VNA/VNA.gise b/FPGA/VNA/VNA.gise index 4aa995f..1009a69 100644 --- a/FPGA/VNA/VNA.gise +++ b/FPGA/VNA/VNA.gise @@ -266,7 +266,7 @@ - + @@ -288,7 +288,7 @@ - + @@ -297,7 +297,7 @@ - + @@ -311,7 +311,7 @@ - + @@ -325,7 +325,7 @@ - + @@ -371,7 +371,7 @@ - + diff --git a/Software/PC_Application/Application b/Software/PC_Application/Application index a13fbe4..cb7a605 100755 Binary files a/Software/PC_Application/Application and b/Software/PC_Application/Application differ diff --git a/Software/PC_Application/Application.pro b/Software/PC_Application/Application.pro index 4646932..828af30 100644 --- a/Software/PC_Application/Application.pro +++ b/Software/PC_Application/Application.pro @@ -15,6 +15,7 @@ HEADERS += \ Device/manualcontroldialog.h \ Generator/generator.h \ Generator/signalgenwidget.h \ + SpectrumAnalyzer/spectrumanalyzer.h \ Tools/eseries.h \ Tools/impedancematchdialog.h \ Traces/bodeplotaxisdialog.h \ @@ -57,6 +58,7 @@ SOURCES += \ Device/manualcontroldialog.cpp \ Generator/generator.cpp \ Generator/signalgenwidget.cpp \ + SpectrumAnalyzer/spectrumanalyzer.cpp \ Tools/eseries.cpp \ Tools/impedancematchdialog.cpp \ Traces/bodeplotaxisdialog.cpp \ diff --git a/Software/PC_Application/Device/device.cpp b/Software/PC_Application/Device/device.cpp index abe079f..2bcca34 100644 --- a/Software/PC_Application/Device/device.cpp +++ b/Software/PC_Application/Device/device.cpp @@ -203,6 +203,14 @@ bool Device::Configure(Protocol::SweepSettings settings) return SendPacket(p); } +bool Device::Configure(Protocol::SpectrumAnalyzerSettings settings) +{ + Protocol::PacketInfo p; + p.type = Protocol::PacketType::SpectrumAnalyzerSettings; + p.spectrumSettings = settings; + return SendPacket(p); +} + bool Device::SetManual(Protocol::ManualControl manual) { Protocol::PacketInfo p; @@ -364,6 +372,9 @@ void Device::ReceivedData() case Protocol::PacketType::Status: emit ManualStatusReceived(packet.status); break; + case Protocol::PacketType::SpectrumAnalyzerResult: + emit SpectrumResultReceived(packet.spectrumResult); + break; case Protocol::PacketType::DeviceInfo: lastInfo = packet.info; lastInfoValid = true; diff --git a/Software/PC_Application/Device/device.h b/Software/PC_Application/Device/device.h index b13c28d..ed64f30 100644 --- a/Software/PC_Application/Device/device.h +++ b/Software/PC_Application/Device/device.h @@ -14,6 +14,7 @@ Q_DECLARE_METATYPE(Protocol::Datapoint); Q_DECLARE_METATYPE(Protocol::ManualStatus); Q_DECLARE_METATYPE(Protocol::DeviceInfo); +Q_DECLARE_METATYPE(Protocol::SpectrumAnalyzerResult); class USBInBuffer : public QObject { Q_OBJECT; @@ -57,6 +58,7 @@ public: ~Device(); bool SendPacket(Protocol::PacketInfo packet, std::function cb = nullptr, unsigned int timeout = 10); bool Configure(Protocol::SweepSettings settings); + bool Configure(Protocol::SpectrumAnalyzerSettings settings); bool SetManual(Protocol::ManualControl manual); bool SendFirmwareChunk(Protocol::FirmwarePacket &fw); bool SendCommandWithoutPayload(Protocol::PacketType type); @@ -69,6 +71,7 @@ public: signals: void DatapointReceived(Protocol::Datapoint); void ManualStatusReceived(Protocol::ManualStatus); + void SpectrumResultReceived(Protocol::SpectrumAnalyzerResult); void DeviceInfoUpdated(); void ConnectionLost(); void AckReceived(); diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp new file mode 100644 index 0000000..a4a3413 --- /dev/null +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -0,0 +1,371 @@ +#include "spectrumanalyzer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unit.h" +#include "CustomWidgets/toggleswitch.h" +#include "Device/manualcontroldialog.h" +#include "Traces/tracemodel.h" +#include "Traces/tracewidget.h" +#include "Traces/tracesmithchart.h" +#include "Traces/tracebodeplot.h" +#include "Traces/traceimportdialog.h" +#include "CustomWidgets/tilewidget.h" +#include "CustomWidgets/siunitedit.h" +#include +#include "Traces/markerwidget.h" +#include "Tools/impedancematchdialog.h" +#include "Calibration/calibrationtracedialog.h" +#include "ui_main.h" +#include "Device/firmwareupdatedialog.h" +#include "preferences.h" +#include "Generator/signalgenwidget.h" +#include +#include +#include + +SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window) + : Mode(window, "Spectrum Analyzer"), + pref(window->getPreferenceRef()), + central(new TileWidget(traceModel)) +{ + averages = 1; + + // Create default traces + auto tPort1 = new Trace("Port1", Qt::yellow); + tPort1->fromLivedata(Trace::LivedataType::Overwrite, Trace::LiveParameter::Port1); + traceModel.addTrace(tPort1); + auto tPort2 = new Trace("Port2", Qt::blue); + tPort2->fromLivedata(Trace::LivedataType::Overwrite, Trace::LiveParameter::Port2); + traceModel.addTrace(tPort2); + + auto tracebode = new TraceBodePlot(traceModel); + tracebode->enableTrace(tPort1, true); + tracebode->enableTrace(tPort2, true); + tracebode->setYAxis(0, TraceBodePlot::YAxisType::Magnitude, false, false, -120,0,10); + tracebode->setYAxis(1, TraceBodePlot::YAxisType::Disabled, false, true, 0,0,1); + + central->setPlot(tracebode); + + // Create menu entries and connections + // Sweep toolbar + auto tb_sweep = new QToolBar("Sweep"); + auto eStart = new SIUnitEdit("Hz", " kMG", 6); + eStart->setFixedWidth(100); + 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(100); + 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(100); + 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(100); + 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"), ""); + bFull->setToolTip("Full span"); + connect(bFull, &QPushButton::clicked, this, &SpectrumAnalyzer::SetFullSpan); + tb_sweep->addWidget(bFull); + + auto bZoomIn = new QPushButton(QIcon::fromTheme("zoom-in"), ""); + bZoomIn->setToolTip("Zoom in"); + connect(bZoomIn, &QPushButton::clicked, this, &SpectrumAnalyzer::SpanZoomIn); + tb_sweep->addWidget(bZoomIn); + + auto bZoomOut = new QPushButton(QIcon::fromTheme("zoom-out"), ""); + bZoomOut->setToolTip("Zoom out"); + connect(bZoomOut, &QPushButton::clicked, this, &SpectrumAnalyzer::SpanZoomOut); + tb_sweep->addWidget(bZoomOut); + + window->addToolBar(tb_sweep); + toolbars.insert(tb_sweep); + + // Acquisition toolbar + auto tb_acq = new QToolBar("Acquisition"); + + auto eBandwidth = new SIUnitEdit("Hz", " k", 3); + eBandwidth->setValueQuiet(settings.RBW); + 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:")); + auto 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) { + settings.WindowType = index; + SettingsChanged(); + }); + tb_acq->addWidget(cbWindowType); + + tb_acq->addWidget(new QLabel("Detector:")); + auto 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) { + settings.Detector = index; + SettingsChanged(); + }); + tb_acq->addWidget(cbDetector); + + auto cbSignalID = new QCheckBox("Signal ID"); + connect(cbSignalID, &QCheckBox::toggled, [=](bool enabled) { + settings.SignalID = enabled; + SettingsChanged(); + }); + tb_acq->addWidget(cbSignalID); + + window->addToolBar(tb_acq); + toolbars.insert(tb_acq); + + + markerModel = new TraceMarkerModel(traceModel); + + auto tracesDock = new QDockWidget("Traces"); + tracesDock->setWidget(new TraceWidget(traceModel, this, true)); + 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); + + qRegisterMetaType("SpectrumResult"); + + // Set initial sweep settings + // TODO +// if(pref.Startup.RememberSweepSettings) { +// LoadSweepSettings(); +// } else { + settings.f_start = pref.Startup.DefaultSweep.start; + settings.f_stop = pref.Startup.DefaultSweep.stop; + ConstrainAndUpdateFrequencies(); + SetRBW(10000); + settings.WindowType = 1; + settings.Detector = 0; + settings.pointNum = 1001; + settings.SignalID = 0; +// } + + finalize(central); +} + +void SpectrumAnalyzer::deactivate() +{ + StoreSweepSettings(); + Mode::deactivate(); +} + +void SpectrumAnalyzer::initializeDevice() +{ + connect(window->getDevice(), &Device::SpectrumResultReceived, this, &SpectrumAnalyzer::NewDatapoint, Qt::UniqueConnection); + + // Configure initial state of device + window->getDevice()->Configure(settings); +} + +using namespace std; + +void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d) +{ + // TODO level adjustment in device + d.port1 /= pow(10.0, 7.5); + d.port2 /= pow(10.0, 7.5); + d = average.process(d); + traceModel.addSAData(d); + emit dataChanged(); +} + +void SpectrumAnalyzer::SettingsChanged() +{ + if(window->getDevice()) { + window->getDevice()->Configure(settings); + } + average.reset(); + traceModel.clearVNAData(); + TracePlot::UpdateSpan(settings.f_start, settings.f_stop); +} + +void SpectrumAnalyzer::StartImpedanceMatching() +{ + auto dialog = new ImpedanceMatchDialog(*markerModel); + dialog->show(); +} + +void SpectrumAnalyzer::SetStartFreq(double freq) +{ + settings.f_start = freq; + if(settings.f_stop < freq) { + settings.f_stop = freq; + } + ConstrainAndUpdateFrequencies(); +} + +void SpectrumAnalyzer::SetStopFreq(double freq) +{ + settings.f_stop = freq; + if(settings.f_start > freq) { + settings.f_start = freq; + } + ConstrainAndUpdateFrequencies(); +} + +void SpectrumAnalyzer::SetCenterFreq(double freq) +{ + auto old_span = settings.f_stop - settings.f_start; + if (freq > old_span / 2) { + settings.f_start = freq - old_span / 2; + settings.f_stop = freq + old_span / 2; + } else { + settings.f_start = 0; + settings.f_stop = 2 * freq; + } + ConstrainAndUpdateFrequencies(); +} + +void SpectrumAnalyzer::SetSpan(double span) +{ + auto old_center = (settings.f_start + settings.f_stop) / 2; + if(old_center > span / 2) { + settings.f_start = old_center - span / 2; + } else { + settings.f_start = 0; + } + settings.f_stop = old_center + span / 2; + ConstrainAndUpdateFrequencies(); +} + +void SpectrumAnalyzer::SetFullSpan() +{ + settings.f_start = 0; + settings.f_stop = 6000000000; + ConstrainAndUpdateFrequencies(); +} + +void SpectrumAnalyzer::SpanZoomIn() +{ + auto center = (settings.f_start + settings.f_stop) / 2; + auto old_span = settings.f_stop - settings.f_start; + settings.f_start = center - old_span / 4; + settings.f_stop = center + old_span / 4; + ConstrainAndUpdateFrequencies(); +} + +void SpectrumAnalyzer::SpanZoomOut() +{ + auto center = (settings.f_start + settings.f_stop) / 2; + auto old_span = settings.f_stop - settings.f_start; + if(center > old_span) { + settings.f_start = center - old_span; + } else { + settings.f_start = 0; + } + settings.f_stop = center + old_span; + ConstrainAndUpdateFrequencies(); +} + +void SpectrumAnalyzer::SetRBW(double bandwidth) +{ + settings.RBW = bandwidth; + emit RBWChanged(settings.RBW); + SettingsChanged(); +} + +void SpectrumAnalyzer::SetAveraging(unsigned int averages) +{ + this->averages = averages; + average.setAverages(averages); + emit averagingChanged(averages); + SettingsChanged(); +} + +void SpectrumAnalyzer::ConstrainAndUpdateFrequencies() +{ + // TODO central hardware limits + if(settings.f_stop > 6000000000) { + settings.f_stop = 6000000000; + } + if(settings.f_start > settings.f_stop) { + settings.f_start = settings.f_stop; + } + emit startFreqChanged(settings.f_start); + emit stopFreqChanged(settings.f_stop); + emit spanChanged(settings.f_stop - settings.f_start); + emit centerFreqChanged((settings.f_stop + settings.f_start)/2); + SettingsChanged(); +} + +void SpectrumAnalyzer::LoadSweepSettings() +{ + // TODO +// QSettings s; +// settings.f_start = s.value("SweepStart", pref.Startup.DefaultSweep.start).toULongLong(); +// settings.f_stop = s.value("SweepStop", pref.Startup.DefaultSweep.stop).toULongLong(); +// ConstrainAndUpdateFrequencies(); +// SetIFBandwidth(s.value("SweepBandwidth", pref.Startup.DefaultSweep.bandwidth).toUInt()); +// SetPoints(s.value("SweepPoints", pref.Startup.DefaultSweep.points).toInt()); +// SetSourceLevel(s.value("SweepLevel", pref.Startup.DefaultSweep.excitation).toDouble()); +} + +void SpectrumAnalyzer::StoreSweepSettings() +{ + // TODO +// QSettings s; +// s.setValue("SweepStart", static_cast(settings.f_start)); +// s.setValue("SweepStop", static_cast(settings.f_stop)); +// s.setValue("SweepBandwidth", settings.if_bandwidth); +// s.setValue("SweepPoints", settings.points); +// s.setValue("SweepLevel", (double) settings.cdbm_excitation / 100.0); +} diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h new file mode 100644 index 0000000..d813bf4 --- /dev/null +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h @@ -0,0 +1,63 @@ +#ifndef SPECTRUMANALYZER_H +#define SPECTRUMANALYZER_H + +#include +#include +#include "appwindow.h" +#include "mode.h" +#include "CustomWidgets/tilewidget.h" + +class SpectrumAnalyzer : public Mode +{ + Q_OBJECT +public: + SpectrumAnalyzer(AppWindow *window); + + void deactivate() override; + void initializeDevice() override; +private slots: + void NewDatapoint(Protocol::SpectrumAnalyzerResult d); + void StartImpedanceMatching(); + // Sweep control + void SetStartFreq(double freq); + void SetStopFreq(double freq); + void SetCenterFreq(double freq); + void SetSpan(double span); + void SetFullSpan(); + void SpanZoomIn(); + void SpanZoomOut(); + // Acquisition control + void SetRBW(double bandwidth); + void SetAveraging(unsigned int averages); + +signals: + +private: + void UpdateStatusPanel(); + void SettingsChanged(); + void ConstrainAndUpdateFrequencies(); + void LoadSweepSettings(); + void StoreSweepSettings(); + + Preferences &pref; + + Protocol::SpectrumAnalyzerSettings settings; + unsigned int averages; + TraceModel traceModel; + TraceMarkerModel *markerModel; + Averaging average; + + TileWidget *central; + +signals: + void dataChanged(); + void startFreqChanged(double freq); + void stopFreqChanged(double freq); + void centerFreqChanged(double freq); + void spanChanged(double span); + void RBWChanged(double RBW); + + void averagingChanged(unsigned int averages); +}; + +#endif // VNA_H diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index a1d12fd..ae9b192 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -2,10 +2,11 @@ using namespace std; -Trace::Trace(QString name, QColor color) +Trace::Trace(QString name, QColor color, LiveParameter live) : _name(name), _color(color), _liveType(LivedataType::Overwrite), + _liveParam(live), reflection(true), visible(true), paused(false), diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index daa9b4a..ef34475 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -21,7 +21,16 @@ public: std::complex S; }; - Trace(QString name = QString(), QColor color = Qt::darkYellow); + enum class LiveParameter { + S11, + S12, + S21, + S22, + Port1, + Port2, + }; + + Trace(QString name = QString(), QColor color = Qt::darkYellow, LiveParameter live = LiveParameter::S11); ~Trace(); enum class LivedataType { @@ -29,12 +38,7 @@ public: MaxHold, MinHold, }; - enum class LiveParameter { - S11, - S12, - S21, - S22, - }; + void clear(); void addData(Data d); diff --git a/Software/PC_Application/Traces/traceeditdialog.cpp b/Software/PC_Application/Traces/traceeditdialog.cpp index 6c45ceb..4f42a9d 100644 --- a/Software/PC_Application/Traces/traceeditdialog.cpp +++ b/Software/PC_Application/Traces/traceeditdialog.cpp @@ -58,11 +58,32 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) : case Trace::LivedataType::MinHold: ui->CLiveType->setCurrentIndex(2); break; } + switch(t.liveParameter()) { + case Trace::LiveParameter::S11: + case Trace::LiveParameter::S12: + case Trace::LiveParameter::S21: + case Trace::LiveParameter::S22: + VNAtrace = true; + ui->CLiveParam->addItem("S11"); + ui->CLiveParam->addItem("S12"); + ui->CLiveParam->addItem("S21"); + ui->CLiveParam->addItem("S22"); + break; + case Trace::LiveParameter::Port1: + case Trace::LiveParameter::Port2: + ui->CLiveParam->addItem("Port 1"); + ui->CLiveParam->addItem("Port 2"); + VNAtrace = false; + break; + } + switch(t.liveParameter()) { case Trace::LiveParameter::S11: ui->CLiveParam->setCurrentIndex(0); break; case Trace::LiveParameter::S12: ui->CLiveParam->setCurrentIndex(1); break; case Trace::LiveParameter::S21: ui->CLiveParam->setCurrentIndex(2); break; case Trace::LiveParameter::S22: ui->CLiveParam->setCurrentIndex(3); break; + case Trace::LiveParameter::Port1: ui->CLiveParam->setCurrentIndex(0); break; + case Trace::LiveParameter::Port2: ui->CLiveParam->setCurrentIndex(1); break; } connect(ui->GSource, qOverload(&QButtonGroup::buttonClicked), updateFileStatus); @@ -100,11 +121,18 @@ void TraceEditDialog::on_buttonBox_accepted() case 1: type = Trace::LivedataType::MaxHold; break; case 2: type = Trace::LivedataType::MinHold; break; } - switch(ui->CLiveParam->currentIndex()) { - case 0: param = Trace::LiveParameter::S11; break; - case 1: param = Trace::LiveParameter::S12; break; - case 2: param = Trace::LiveParameter::S21; break; - case 3: param = Trace::LiveParameter::S22; break; + if(VNAtrace) { + switch(ui->CLiveParam->currentIndex()) { + case 0: param = Trace::LiveParameter::S11; break; + case 1: param = Trace::LiveParameter::S12; break; + case 2: param = Trace::LiveParameter::S21; break; + case 3: param = Trace::LiveParameter::S22; break; + } + } else { + switch(ui->CLiveParam->currentIndex()) { + case 0: param = Trace::LiveParameter::Port1; break; + case 1: param = Trace::LiveParameter::Port2; break; + } } trace.fromLivedata(type, param); } diff --git a/Software/PC_Application/Traces/traceeditdialog.h b/Software/PC_Application/Traces/traceeditdialog.h index 60652dd..6149886 100644 --- a/Software/PC_Application/Traces/traceeditdialog.h +++ b/Software/PC_Application/Traces/traceeditdialog.h @@ -24,6 +24,7 @@ private: void setColor(QColor c); Ui::TraceEditDialog *ui; Trace &trace; + bool VNAtrace; }; #endif // TRACEEDITDIALOG_H diff --git a/Software/PC_Application/Traces/traceeditdialog.ui b/Software/PC_Application/Traces/traceeditdialog.ui index b8aeb47..1f33371 100644 --- a/Software/PC_Application/Traces/traceeditdialog.ui +++ b/Software/PC_Application/Traces/traceeditdialog.ui @@ -119,28 +119,7 @@ - - - - S11 - - - - - S12 - - - - - S21 - - - - - S22 - - - + diff --git a/Software/PC_Application/Traces/tracemodel.cpp b/Software/PC_Application/Traces/tracemodel.cpp index af3cb92..c3b235a 100644 --- a/Software/PC_Application/Traces/tracemodel.cpp +++ b/Software/PC_Application/Traces/tracemodel.cpp @@ -148,6 +148,27 @@ void TraceModel::addVNAData(Protocol::Datapoint d) case Trace::LiveParameter::S12: td.S = complex(d.real_S12, d.imag_S12); break; case Trace::LiveParameter::S21: td.S = complex(d.real_S21, d.imag_S21); break; case Trace::LiveParameter::S22: td.S = complex(d.real_S22, d.imag_S22); break; + default: + // not a VNA trace, skip + continue; + } + t->addData(td); + } + } +} + +void TraceModel::addSAData(Protocol::SpectrumAnalyzerResult d) +{ + for(auto t : traces) { + if (t->isLive()) { + Trace::Data td; + td.frequency = d.frequency; + switch(t->liveParameter()) { + case Trace::LiveParameter::Port1: td.S = complex(d.port1, 0); break; + case Trace::LiveParameter::Port2: td.S = complex(d.port2, 0); break; + default: + // not a SA trace, skip + continue; } t->addData(td); } diff --git a/Software/PC_Application/Traces/tracemodel.h b/Software/PC_Application/Traces/tracemodel.h index 06c50c7..28bcde9 100644 --- a/Software/PC_Application/Traces/tracemodel.h +++ b/Software/PC_Application/Traces/tracemodel.h @@ -33,6 +33,7 @@ signals: public slots: void clearVNAData(); void addVNAData(Protocol::Datapoint d); + void addSAData(Protocol::SpectrumAnalyzerResult d); private: std::vector traces; diff --git a/Software/PC_Application/Traces/tracewidget.cpp b/Software/PC_Application/Traces/tracewidget.cpp index b0d6fba..c935fd9 100644 --- a/Software/PC_Application/Traces/tracewidget.cpp +++ b/Software/PC_Application/Traces/tracewidget.cpp @@ -7,10 +7,11 @@ #include "traceexportdialog.h" #include -TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) : +TraceWidget::TraceWidget(TraceModel &model, QWidget *parent, bool SA) : QWidget(parent), ui(new Ui::TraceWidget), - model(model) + model(model), + SA(SA) { ui->setupUi(this); ui->view->setModel(&model); @@ -27,7 +28,8 @@ TraceWidget::~TraceWidget() void TraceWidget::on_add_clicked() { createCount++; - auto t = new Trace("Trace #"+QString::number(createCount)); + auto liveParam = SA ? Trace::LiveParameter::Port1 : Trace::LiveParameter::S11; + auto t = new Trace("Trace #"+QString::number(createCount), Qt::darkYellow, liveParam); t->setColor(QColor::fromHsl((createCount * 50) % 360, 250, 128)); model.addTrace(t); } diff --git a/Software/PC_Application/Traces/tracewidget.h b/Software/PC_Application/Traces/tracewidget.h index 100758e..c34d1fa 100644 --- a/Software/PC_Application/Traces/tracewidget.h +++ b/Software/PC_Application/Traces/tracewidget.h @@ -13,7 +13,7 @@ class TraceWidget : public QWidget Q_OBJECT public: - explicit TraceWidget(TraceModel &model, QWidget *parent = nullptr); + explicit TraceWidget(TraceModel &model, QWidget *parent = nullptr, bool SA = false); ~TraceWidget(); public slots: @@ -36,6 +36,7 @@ private: Ui::TraceWidget *ui; TraceModel &model; int createCount; + bool SA; }; #endif // TRACEWIDGET_H diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index b7086bd..9990461 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -85,9 +85,6 @@ VNA::VNA(AppWindow *window) central->Child2()->Child1()->setPlot(tracebode2); central->Child2()->Child2()->setPlot(tracesmith2); - // central widget is constructed, mode can be finalized - finalize(central); - // Create menu entries and connections auto calMenu = new QMenu("Calibration"); window->menuBar()->insertMenu(window->getUi()->menuWindow->menuAction(), calMenu); @@ -437,6 +434,16 @@ VNA::VNA(AppWindow *window) SetIFBandwidth(pref.Startup.DefaultSweep.bandwidth); SetPoints(pref.Startup.DefaultSweep.points); } + + // Set ObjectName for toolbars and docks + for(auto d : findChildren()) { + d->setObjectName(d->windowTitle()); + } + for(auto t : findChildren()) { + t->setObjectName(t->windowTitle()); + } + + finalize(central); } void VNA::deactivate() diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index 632846b..1857a3e 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -43,6 +43,7 @@ #include #include "VNA/vna.h" #include "Generator/generator.h" +#include "SpectrumAnalyzer/spectrumanalyzer.h" using namespace std; @@ -71,6 +72,7 @@ AppWindow::AppWindow(QWidget *parent) CreateToolbars(); auto logDock = new QDockWidget("Device Log"); logDock->setWidget(&deviceLog); + logDock->setObjectName("Log Dock"); addDockWidget(Qt::BottomDockWidgetArea, logDock); // fill toolbar/dock menu @@ -88,6 +90,7 @@ AppWindow::AppWindow(QWidget *parent) setCentralWidget(central); auto vna = new VNA(this); new Generator(this); + new SpectrumAnalyzer(this); // auto signalGenWidget = new Signalgenerator; // modeSGen = new GUIMode(this, "Signal Generator", signalGenWidget); @@ -122,14 +125,6 @@ AppWindow::AppWindow(QWidget *parent) restoreGeometry(settings.value("geometry").toByteArray()); } - // Set ObjectName for toolbars and docks - for(auto d : findChildren()) { - d->setObjectName(d->windowTitle()); - } - for(auto t : findChildren()) { - t->setObjectName(t->windowTitle()); - } - // Set default mode vna->activate(); @@ -248,6 +243,7 @@ void AppWindow::CreateToolbars() connect(toolbars.reference.outputEnabled, &QCheckBox::clicked, this, &AppWindow::UpdateReference); addToolBar(tb_reference); + tb_reference->setObjectName("Reference Toolbar"); } Preferences &AppWindow::getPreferenceRef() diff --git a/Software/PC_Application/averaging.cpp b/Software/PC_Application/averaging.cpp index 442e30c..a84180b 100644 --- a/Software/PC_Application/averaging.cpp +++ b/Software/PC_Application/averaging.cpp @@ -68,6 +68,40 @@ Protocol::Datapoint Averaging::process(Protocol::Datapoint d) return d; } +Protocol::SpectrumAnalyzerResult Averaging::process(Protocol::SpectrumAnalyzerResult d) +{ + if (d.pointNum == avg.size()) { + // add moving average entry + deque, 4>> deque; + avg.push_back(deque); + } + + if (d.pointNum < avg.size()) { + // can compute average + // get correct queue + auto deque = &avg[d.pointNum]; + // add newest sample to queue + array, 4> sample = {d.port1, d.port2, 0, 0}; + deque->push_back(sample); + if(deque->size() > averages) { + deque->pop_front(); + } + + // calculate average + complex sum[4]; + for(auto s : *deque) { + sum[0] += s[0]; + sum[1] += s[1]; + sum[2] += s[2]; + sum[3] += s[3]; + } + d.port1 = abs(sum[0] / (double) (deque->size())); + d.port2 = abs(sum[1] / (double) (deque->size())); + } + + return d; +} + unsigned int Averaging::getLevel() { if(avg.size() > 0) { diff --git a/Software/PC_Application/averaging.h b/Software/PC_Application/averaging.h index d416d6f..488c00e 100644 --- a/Software/PC_Application/averaging.h +++ b/Software/PC_Application/averaging.h @@ -13,6 +13,7 @@ public: void reset(); void setAverages(unsigned int a); Protocol::Datapoint process(Protocol::Datapoint d); + Protocol::SpectrumAnalyzerResult process(Protocol::SpectrumAnalyzerResult d); unsigned int getLevel(); private: std::vector, 4>>> avg; diff --git a/Software/PC_Application/mode.cpp b/Software/PC_Application/mode.cpp index 8e3ae7b..977c981 100644 --- a/Software/PC_Application/mode.cpp +++ b/Software/PC_Application/mode.cpp @@ -68,7 +68,7 @@ void Mode::activate() // restore visibility of toolbars and docks // window->getUi()->menuDocks->clear(); - for(auto d : window->findChildren()) { + for(auto d : docks) { // window->getUi()->menuDocks->addAction(d->toggleViewAction()); bool hidden = settings.value("dock_"+name+"_"+d->windowTitle(), d->isHidden()).toBool(); if(hidden) { @@ -78,7 +78,7 @@ void Mode::activate() } } // window->getUi()->menuToolbars->clear(); - for(auto t : window->findChildren()) { + for(auto t : toolbars) { // window->getUi()->menuToolbars->addAction(t->toggleViewAction()); bool hidden = settings.value("toolbar_"+name+"_"+t->windowTitle(), t->isHidden()).toBool(); if(hidden) { @@ -99,10 +99,10 @@ void Mode::deactivate() { QSettings settings; // save dock/toolbar visibility - for(auto d : window->findChildren()) { + for(auto d : docks) { settings.setValue("dock_"+name+"_"+d->windowTitle(), d->isHidden()); } - for(auto t : window->findChildren()) { + for(auto t : toolbars) { settings.setValue("toolbar_"+name+"_"+t->windowTitle(), t->isHidden()); } // settings.setValue("geometry_"+name, window->saveGeometry()); @@ -133,6 +133,13 @@ void Mode::finalize(QWidget *centralWidget) { central = centralWidget; window->getCentral()->addWidget(central); + // Set ObjectName for toolbars and docks + for(auto d : docks) { + d->setObjectName(d->windowTitle()+name); + } + for(auto t : toolbars) { + t->setObjectName(t->windowTitle()+name); + } // hide all mode specific GUI elements for(auto t : toolbars) { t->hide(); diff --git a/Software/VNA_embedded/Application/App.cpp b/Software/VNA_embedded/Application/App.cpp index a555dde..aeb6eab 100644 --- a/Software/VNA_embedded/Application/App.cpp +++ b/Software/VNA_embedded/Application/App.cpp @@ -16,6 +16,7 @@ #include "Hardware.hpp" #include "Manual.hpp" #include "Generator.hpp" +#include "SpectrumAnalyzer.hpp" #define LOG_LEVEL LOG_LEVEL_INFO #define LOG_MODULE "App" @@ -37,6 +38,7 @@ static Flash flash = Flash(&hspi1, FLASH_CS_GPIO_Port, FLASH_CS_Pin); #define FLAG_USB_PACKET 0x01 #define FLAG_DATAPOINT 0x02 +#define FLAG_WORK_REQUIRED 0x04 static void VNACallback(Protocol::Datapoint res) { result = res; @@ -50,6 +52,11 @@ static void USBPacketReceived(Protocol::PacketInfo p) { xTaskNotifyFromISR(handle, FLAG_USB_PACKET, eSetBits, &woken); portYIELD_FROM_ISR(woken); } +static void HardwareWorkRequired() { + BaseType_t woken = false; + xTaskNotifyFromISR(handle, FLAG_WORK_REQUIRED, eSetBits, &woken); + portYIELD_FROM_ISR(woken); +} void App_Start() { handle = xTaskGetCurrentTaskHandle(); @@ -90,7 +97,7 @@ void App_Start() { EN_6V_GPIO_Port->BSRR = EN_6V_Pin; #endif - if (!HW::Init()) { + if (!HW::Init(HardwareWorkRequired)) { LOG_CRIT("Initialization failed, unable to start"); LED::Error(4); } @@ -108,6 +115,9 @@ void App_Start() { uint32_t notification; if(xTaskNotifyWait(0x00, UINT32_MAX, ¬ification, 100) == pdPASS) { // something happened + if(notification & FLAG_WORK_REQUIRED) { + HW::Work(); + } if(notification & FLAG_DATAPOINT) { Protocol::PacketInfo packet; packet.type = Protocol::PacketType::Datapoint; @@ -143,6 +153,12 @@ void App_Start() { Generator::Setup(packet.generator); Communication::SendWithoutPayload(Protocol::PacketType::Ack); break; + case Protocol::PacketType::SpectrumAnalyzerSettings: + sweepActive = false; + LOG_INFO("Updating spectrum analyzer settings"); + SA::Setup(packet.spectrumSettings); + Communication::SendWithoutPayload(Protocol::PacketType::Ack); + break; #ifdef HAS_FLASH case Protocol::PacketType::ClearFlash: HW::SetMode(HW::Mode::Idle); @@ -191,7 +207,7 @@ void App_Start() { LOG_WARN("Timed out waiting for point, last received point was %d (Status 0x%04x)", result.pointNum, FPGA::GetStatus()); FPGA::AbortSweep(); // restart the current sweep - HW::Init(); + HW::Init(HardwareWorkRequired); HW::Ref::update(); VNA::Setup(settings, VNACallback); sweepActive = true; diff --git a/Software/VNA_embedded/Application/Communication/Protocol.cpp b/Software/VNA_embedded/Application/Communication/Protocol.cpp index 7b53839..cde6a90 100644 --- a/Software/VNA_embedded/Application/Communication/Protocol.cpp +++ b/Software/VNA_embedded/Application/Communication/Protocol.cpp @@ -358,6 +358,50 @@ static int16_t EncodeManualControl(Protocol::ManualControl d, uint8_t *buf, return e.getSize(); } +static Protocol::SpectrumAnalyzerSettings DecodeSpectrumAnalyzerSettings(uint8_t *buf) { + Protocol::SpectrumAnalyzerSettings d; + Decoder e(buf); + e.get(d.f_start); + e.get(d.f_stop); + e.get(d.RBW); + e.get(d.pointNum); + d.WindowType = e.getBits(2); + d.SignalID = e.getBits(1); + d.Detector = e.getBits(3); + return d; +} +static int16_t EncodeSpectrumAnalyzerSettings(Protocol::SpectrumAnalyzerSettings d, uint8_t *buf, + uint16_t bufSize) { + Encoder e(buf, bufSize); + e.add(d.f_start); + e.add(d.f_stop); + e.add(d.RBW); + e.add(d.pointNum); + e.addBits(d.WindowType, 2); + e.addBits(d.SignalID, 1); + e.addBits(d.Detector, 3); + return e.getSize(); +} + +static Protocol::SpectrumAnalyzerResult DecodeSpectrumAnalyzerResult(uint8_t *buf) { + Protocol::SpectrumAnalyzerResult d; + Decoder e(buf); + e.get(d.port1); + e.get(d.port2); + e.get(d.frequency); + e.get(d.pointNum); + return d; +} +static int16_t EncodeSpectrumAnalyzerResult(Protocol::SpectrumAnalyzerResult d, uint8_t *buf, + uint16_t bufSize) { + Encoder e(buf, bufSize); + e.add(d.port1); + e.add(d.port2); + e.add(d.frequency); + e.add(d.pointNum); + return e.getSize(); +} + static Protocol::FirmwarePacket DecodeFirmwarePacket(uint8_t *buf) { Protocol::FirmwarePacket d; // simple packet format, memcpy is faster than using the decoder @@ -446,6 +490,12 @@ uint16_t Protocol::DecodeBuffer(uint8_t *buf, uint16_t len, PacketInfo *info) { case PacketType::Generator: info->generator = DecodeGeneratorSettings(&data[4]); break; + case PacketType::SpectrumAnalyzerSettings: + info->spectrumSettings = DecodeSpectrumAnalyzerSettings(&data[4]); + break; + case PacketType::SpectrumAnalyzerResult: + info->spectrumResult = DecodeSpectrumAnalyzerResult(&data[4]); + break; case PacketType::Ack: case PacketType::PerformFirmwareUpdate: case PacketType::ClearFlash: @@ -486,6 +536,12 @@ uint16_t Protocol::EncodePacket(PacketInfo packet, uint8_t *dest, uint16_t dests case PacketType::Generator: payload_size = EncodeGeneratorSettings(packet.generator, &dest[4], destsize - 8); break; + case PacketType::SpectrumAnalyzerSettings: + payload_size = EncodeSpectrumAnalyzerSettings(packet.spectrumSettings, &dest[4], destsize - 8); + break; + case PacketType::SpectrumAnalyzerResult: + payload_size = EncodeSpectrumAnalyzerResult(packet.spectrumResult, &dest[4], destsize - 8); + break; case PacketType::Ack: case PacketType::PerformFirmwareUpdate: case PacketType::ClearFlash: diff --git a/Software/VNA_embedded/Application/Communication/Protocol.hpp b/Software/VNA_embedded/Application/Communication/Protocol.hpp index d5b5a80..dff2420 100644 --- a/Software/VNA_embedded/Application/Communication/Protocol.hpp +++ b/Software/VNA_embedded/Application/Communication/Protocol.hpp @@ -102,10 +102,18 @@ using SpectrumAnalyzerSettings = struct _spectrumAnalyzerSettings { uint64_t f_start; uint64_t f_stop; uint32_t RBW; + uint16_t pointNum; uint8_t WindowType :2; uint8_t SignalID :1; + uint8_t Detector :3; }; +using SpectrumAnalyzerResult = struct _spectrumAnalyzerResult { + float port1; + float port2; + uint64_t frequency; + uint16_t pointNum; +}; static constexpr uint16_t FirmwareChunkSize = 256; using FirmwarePacket = struct _firmwarePacket { @@ -127,6 +135,8 @@ enum class PacketType : uint8_t { Nack = 10, Reference = 11, Generator = 12, + SpectrumAnalyzerSettings = 13, + SpectrumAnalyzerResult = 14, }; using PacketInfo = struct _packetinfo { @@ -138,8 +148,10 @@ using PacketInfo = struct _packetinfo { GeneratorSettings generator; DeviceInfo info; ManualControl manual; - ManualStatus status; FirmwarePacket firmware; + ManualStatus status; + SpectrumAnalyzerSettings spectrumSettings; + SpectrumAnalyzerResult spectrumResult; }; }; diff --git a/Software/VNA_embedded/Application/Drivers/max2871.cpp b/Software/VNA_embedded/Application/Drivers/max2871.cpp index 8b362c8..fe72405 100644 --- a/Software/VNA_embedded/Application/Drivers/max2871.cpp +++ b/Software/VNA_embedded/Application/Drivers/max2871.cpp @@ -4,7 +4,7 @@ #include "delay.hpp" #include -#define LOG_LEVEL LOG_LEVEL_WARN +#define LOG_LEVEL LOG_LEVEL_ERR #define LOG_MODULE "MAX2871" #include "Log.h" @@ -198,7 +198,7 @@ bool MAX2871::SetFrequency(uint64_t f) { approx.num, approx.denom, abs(rem_f - rem_approx)); } - uint64_t f_set = (uint64_t) N * f_PFD + (f_PFD * approx.num) / approx.denom; + uint64_t f_set = (uint64_t) N * f_PFD + rem_approx; f_set /= (1UL << div); // write values to registers diff --git a/Software/VNA_embedded/Application/Drivers/max2871.hpp b/Software/VNA_embedded/Application/Drivers/max2871.hpp index 1013e56..4648887 100644 --- a/Software/VNA_embedded/Application/Drivers/max2871.hpp +++ b/Software/VNA_embedded/Application/Drivers/max2871.hpp @@ -57,6 +57,9 @@ public: uint32_t* GetRegisters() { return regs; } + uint64_t GetActualFrequency() { + return outputFrequency; + } private: static constexpr uint64_t MaxFreq = 6100000000; // 6GHz according to datasheet, but slight overclocking is possible diff --git a/Software/VNA_embedded/Application/Hardware.cpp b/Software/VNA_embedded/Application/Hardware.cpp index b677eb4..62294c3 100644 --- a/Software/VNA_embedded/Application/Hardware.cpp +++ b/Software/VNA_embedded/Application/Hardware.cpp @@ -6,6 +6,7 @@ #include "Exti.hpp" #include "VNA.hpp" #include "Manual.hpp" +#include "SpectrumAnalyzer.hpp" #define LOG_LEVEL LOG_LEVEL_INFO #define LOG_MODULE "HW" @@ -15,8 +16,6 @@ static uint32_t extOutFreq = 0; static bool extRefInUse = false; HW::Mode activeMode; -static constexpr uint32_t IF1 = 60100000; -static constexpr uint32_t IF2 = 250000; static Protocol::ReferenceSettings ref; using namespace HWHAL; @@ -30,6 +29,9 @@ static void HaltedCallback() { break; } } + +static HW::WorkRequest requestWork; + static void ReadComplete(FPGA::SamplingResult result) { bool needs_work = false; switch(activeMode) { @@ -39,11 +41,14 @@ static void ReadComplete(FPGA::SamplingResult result) { case HW::Mode::Manual: needs_work = Manual::MeasurementDone(result); break; + case HW::Mode::SA: + needs_work = SA::MeasurementDone(result); + break; default: break; } - if(needs_work) { - HAL_NVIC_SetPendingIRQ(COMP4_IRQn); + if(needs_work && requestWork) { + requestWork(); } } @@ -51,9 +56,7 @@ static void FPGA_Interrupt(void*) { FPGA::InitiateSampleRead(ReadComplete); } -/* low priority interrupt to handle additional workload after FPGA interrupt */ -extern "C" { -void COMP4_IRQHandler(void) { +void HW::Work() { switch(activeMode) { case HW::Mode::VNA: VNA::Work(); @@ -61,16 +64,17 @@ void COMP4_IRQHandler(void) { case HW::Mode::Manual: Manual::Work(); break; + case HW::Mode::SA: + SA::Work(); + break; default: break; } } -} -bool HW::Init() { +bool HW::Init(WorkRequest wr) { + requestWork = wr; LOG_DEBUG("Initializing..."); - HAL_NVIC_SetPriority(COMP4_IRQn, 15, 0); - HAL_NVIC_EnableIRQ(COMP4_IRQn); activeMode = Mode::Idle; @@ -179,10 +183,9 @@ void HW::SetMode(Mode mode) { default: break; } - if(activeMode == Mode::Manual && mode != Mode::Idle) { - // do a full initialization when switching from manual mode to anything else than idle - // (making sure that any changes made in manual mode are reverted) - HW::Init(); + if(mode != Mode::Idle && activeMode != Mode::Idle) { + // do a full initialization when switching directly between modes + HW::Init(requestWork); } SetIdle(); activeMode = mode; diff --git a/Software/VNA_embedded/Application/Hardware.hpp b/Software/VNA_embedded/Application/Hardware.hpp index e5cab3a..0b6dde7 100644 --- a/Software/VNA_embedded/Application/Hardware.hpp +++ b/Software/VNA_embedded/Application/Hardware.hpp @@ -5,6 +5,10 @@ namespace HW { +static constexpr uint32_t ADCSamplerate = 914000; +static constexpr uint32_t IF1 = 60100000; +static constexpr uint32_t IF2 = 250000; + enum class Mode { Idle, Manual, @@ -12,9 +16,12 @@ enum class Mode { SA, }; -bool Init(); +using WorkRequest = void (*)(void); + +bool Init(WorkRequest wr); void SetMode(Mode mode); void SetIdle(); +void Work(); bool GetTemps(uint8_t *source, uint8_t *lo); void fillDeviceInfo(Protocol::DeviceInfo *info); diff --git a/Software/VNA_embedded/Application/Manual.cpp b/Software/VNA_embedded/Application/Manual.cpp index 6d0890e..b3ca07b 100644 --- a/Software/VNA_embedded/Application/Manual.cpp +++ b/Software/VNA_embedded/Application/Manual.cpp @@ -97,6 +97,7 @@ void Manual::Work() { } Protocol::PacketInfo p; p.type = Protocol::PacketType::Status; + p.status = status; uint16_t isr_flags = FPGA::GetStatus(); if (!(isr_flags & 0x0002)) { p.status.source_locked = 1; diff --git a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp new file mode 100644 index 0000000..8fec47e --- /dev/null +++ b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp @@ -0,0 +1,207 @@ +#include "SpectrumAnalyzer.hpp" +#include "Hardware.hpp" +#include "HW_HAL.hpp" +#include +#include +#include "Communication.h" + +#define LOG_LEVEL LOG_LEVEL_DEBUG +#define LOG_MODULE "SA" +#include "Log.h" + +static Protocol::SpectrumAnalyzerSettings s; +static uint32_t pointCnt; +static uint32_t points; +static uint32_t binSize; +static uint8_t signalIDstep; +static uint32_t sampleNum; +static Protocol::PacketInfo p; +static bool active = false; + +static float port1Measurement, port2Measurement; + +using namespace HWHAL; + +static void StartNextSample() { + uint64_t freq = s.f_start + (s.f_stop - s.f_start) * pointCnt / (points - 1); + uint64_t LO1freq; + uint32_t LO2freq; + switch(signalIDstep) { + case 0: + default: + // reset minimum amplitudes in first signal ID step + port1Measurement = std::numeric_limits::max(); + port2Measurement = std::numeric_limits::max(); + LO1freq = freq + HW::IF1; + LO2freq = HW::IF1 - HW::IF2; + break; + case 1: + LO1freq = freq - HW::IF1; + LO2freq = HW::IF1 - HW::IF2; + break; + case 2: + LO1freq = freq + HW::IF1; + LO2freq = HW::IF1 + HW::IF2; + break; + case 3: + LO1freq = freq - HW::IF1; + LO2freq = HW::IF1 + HW::IF2; + break; + } + LO1.SetFrequency(LO1freq); + // LO1 is not able to reach all frequencies with the required precision, adjust LO2 to account for deviation + int32_t LO1deviation = (int64_t) LO1.GetActualFrequency() - LO1freq; + LO2freq += LO1deviation; + // Adjust LO2 PLL + // Generate second LO with Si5351 + Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2); + Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2); + // Configure the sampling in the FPGA + FPGA::WriteSweepConfig(0, 0, Source.GetRegisters(), LO1.GetRegisters(), 0, + 0, FPGA::SettlingTime::us20, FPGA::Samples::SPPRegister, 0, + FPGA::LowpassFilter::M947); + + FPGA::StartSweep(); +} + +void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) { + LOG_DEBUG("Setting up..."); + s = settings; + HW::SetMode(HW::Mode::SA); + FPGA::AbortSweep(); + FPGA::SetMode(FPGA::Mode::FPGA); + // in almost all cases a full sweep requires more points than the FPGA can handle at a time + // individually start each point and do the sweep in the uC + FPGA::SetNumberOfPoints(1); + // calculate amount of required points + points = (s.f_stop - s.f_start) / s.RBW; + // adjust to integer multiple of requested result points (in order to have the same amount of measurements in each bin) + 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); + // calculate required samples per measurement for requested RBW + // see https://www.tek.com/blog/window-functions-spectrum-analyzers for window factors + constexpr float window_factors[4] = {0.89f, 2.23f, 1.44f, 3.77f}; + sampleNum = HW::ADCSamplerate * window_factors[s.WindowType] / s.RBW; + // round up to next multiple of 128 + sampleNum += 128 - sampleNum%128; + FPGA::SetSamplesPerPoint(sampleNum); + // set initial state + pointCnt = 0; + // enable the required hardware resources + Si5351.Enable(SiChannel::Port1LO2); + Si5351.Enable(SiChannel::Port2LO2); + FPGA::SetWindow((FPGA::Window) s.WindowType); + FPGA::Enable(FPGA::Periphery::LO1Chip); + FPGA::Enable(FPGA::Periphery::LO1RF); + FPGA::Enable(FPGA::Periphery::ExcitePort1); + FPGA::Enable(FPGA::Periphery::Port1Mixer); + FPGA::Enable(FPGA::Periphery::Port2Mixer); + active = true; + StartNextSample(); +} + +bool SA::MeasurementDone(FPGA::SamplingResult result) { + if(!active) { + return false; + } + float port1 = abs(std::complex(result.P1I, result.P1Q))/sampleNum; + float port2 = abs(std::complex(result.P2I, result.P2Q))/sampleNum; + if(port1 < port1Measurement) { + port1Measurement = port1; + } + if(port2 < port2Measurement) { + port2Measurement = port2; + } + // trigger work function + return true; +} + +void SA::Work() { + if(!active) { + return; + } + if(!s.SignalID || signalIDstep >= 3) { + // this measurement point is done, handle result according to detector + uint16_t binIndex = pointCnt / binSize; + uint32_t pointInBin = pointCnt % binSize; + bool lastPointInBin = pointInBin >= binSize - 1; + auto det = (Detector) s.Detector; + if(det == Detector::Normal) { + det = binIndex & 0x01 ? Detector::PosPeak : Detector::NegPeak; + } + switch(det) { + case Detector::PosPeak: + if(pointInBin == 0) { + p.spectrumResult.port1 = std::numeric_limits::min(); + p.spectrumResult.port2 = std::numeric_limits::min(); + } + if(port1Measurement > p.spectrumResult.port1) { + p.spectrumResult.port1 = port1Measurement; + } + if(port2Measurement > p.spectrumResult.port2) { + p.spectrumResult.port2 = port2Measurement; + } + break; + case Detector::NegPeak: + if(pointInBin == 0) { + p.spectrumResult.port1 = std::numeric_limits::max(); + p.spectrumResult.port2 = std::numeric_limits::max(); + } + if(port1Measurement < p.spectrumResult.port1) { + p.spectrumResult.port1 = port1Measurement; + } + if(port2Measurement < p.spectrumResult.port2) { + p.spectrumResult.port2 = port2Measurement; + } + break; + case Detector::Sample: + if(pointInBin <= binSize / 2) { + // still in first half of bin, simply overwrite + p.spectrumResult.port1 = port1Measurement; + p.spectrumResult.port2 = port2Measurement; + } + break; + case Detector::Average: + if(pointInBin == 0) { + p.spectrumResult.port1 = 0; + p.spectrumResult.port2 = 0; + } + p.spectrumResult.port1 += port1Measurement; + p.spectrumResult.port2 += port2Measurement; + if(lastPointInBin) { + // calculate average + p.spectrumResult.port1 /= binSize; + p.spectrumResult.port2 /= binSize; + } + break; + case Detector::Normal: + // nothing to do, normal detector handled by PosPeak or NegPeak in each sample + break; + } + if(lastPointInBin) { + // Send result to application + p.type = Protocol::PacketType::SpectrumAnalyzerResult; + // measurements are already up to date, fill remaining fields + p.spectrumResult.pointNum = binIndex; + p.spectrumResult.frequency = s.f_start + (s.f_stop - s.f_start) * binIndex / (s.pointNum - 1); + Communication::Send(p); + } + // setup for next step + signalIDstep = 0; + if(pointCnt < points - 1) { + pointCnt++; + } else { + pointCnt = 0; + } + } else { + // more measurements required for signal ID + signalIDstep++; + } + StartNextSample(); +} + +void SA::Stop() { + active = false; + FPGA::AbortSweep(); +} diff --git a/Software/VNA_embedded/Application/SpectrumAnalyzer.hpp b/Software/VNA_embedded/Application/SpectrumAnalyzer.hpp new file mode 100644 index 0000000..9219133 --- /dev/null +++ b/Software/VNA_embedded/Application/SpectrumAnalyzer.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "Protocol.hpp" +#include "FPGA/FPGA.hpp" + +namespace SA { + +enum class Detector { + PosPeak = 0x00, + NegPeak = 0x01, + Sample = 0x02, + Normal = 0x03, + Average = 0x04, +}; + +void Setup(Protocol::SpectrumAnalyzerSettings settings); +bool MeasurementDone(FPGA::SamplingResult result); +void Work(); +void Stop(); + +} diff --git a/Software/VNA_embedded/Application/VNA.cpp b/Software/VNA_embedded/Application/VNA.cpp index 6690e6a..f9323d2 100644 --- a/Software/VNA_embedded/Application/VNA.cpp +++ b/Software/VNA_embedded/Application/VNA.cpp @@ -55,7 +55,7 @@ bool VNA::Setup(Protocol::SweepSettings s, SweepCallback cb) { uint16_t points = settings.points <= FPGA::MaxPoints ? settings.points : FPGA::MaxPoints; // Configure sweep FPGA::SetNumberOfPoints(points); - uint32_t samplesPerPoint = (1000000 / s.if_bandwidth); + uint32_t samplesPerPoint = (HW::ADCSamplerate / s.if_bandwidth); // round up to next multiple of 128 (128 samples are spread across 35 IF2 periods) samplesPerPoint = ((uint32_t) ((samplesPerPoint + 127) / 128)) * 128; // has to be one less than actual number of samples