From e68a9abffe398b8e5f5314450809a7fb798562e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Tue, 27 Oct 2020 22:07:14 +0100 Subject: [PATCH] Proof-of-concept TDR measurements --- Software/PC_Application/Application.pro | 12 +- .../CustomWidgets/tilewidget.cpp | 4 +- .../CustomWidgets/tilewidget.ui | 2 +- .../SpectrumAnalyzer/spectrumanalyzer.cpp | 8 +- .../Traces/bodeplotaxisdialog.cpp | 118 ---- .../Traces/bodeplotaxisdialog.h | 27 - .../Traces/bodeplotaxisdialog.ui | 462 --------------- Software/PC_Application/Traces/fftcomplex.cpp | 152 +++++ Software/PC_Application/Traces/fftcomplex.h | 62 ++ Software/PC_Application/Traces/trace.cpp | 89 ++- Software/PC_Application/Traces/trace.h | 19 + .../{tracebodeplot.cpp => tracexyplot.cpp} | 227 +++++--- .../Traces/{tracebodeplot.h => tracexyplot.h} | 48 +- .../Traces/xyplotaxisdialog.cpp | 177 ++++++ .../PC_Application/Traces/xyplotaxisdialog.h | 30 + .../PC_Application/Traces/xyplotaxisdialog.ui | 547 ++++++++++++++++++ Software/PC_Application/VNA/vna.cpp | 6 +- Software/PC_Application/appwindow.cpp | 4 +- 18 files changed, 1276 insertions(+), 718 deletions(-) delete mode 100644 Software/PC_Application/Traces/bodeplotaxisdialog.cpp delete mode 100644 Software/PC_Application/Traces/bodeplotaxisdialog.h delete mode 100644 Software/PC_Application/Traces/bodeplotaxisdialog.ui create mode 100644 Software/PC_Application/Traces/fftcomplex.cpp create mode 100644 Software/PC_Application/Traces/fftcomplex.h rename Software/PC_Application/Traces/{tracebodeplot.cpp => tracexyplot.cpp} (62%) rename Software/PC_Application/Traces/{tracebodeplot.h => tracexyplot.h} (67%) create mode 100644 Software/PC_Application/Traces/xyplotaxisdialog.cpp create mode 100644 Software/PC_Application/Traces/xyplotaxisdialog.h create mode 100644 Software/PC_Application/Traces/xyplotaxisdialog.ui diff --git a/Software/PC_Application/Application.pro b/Software/PC_Application/Application.pro index b0337a0..c1a8681 100644 --- a/Software/PC_Application/Application.pro +++ b/Software/PC_Application/Application.pro @@ -19,10 +19,9 @@ HEADERS += \ SpectrumAnalyzer/spectrumanalyzer.h \ Tools/eseries.h \ Tools/impedancematchdialog.h \ - Traces/bodeplotaxisdialog.h \ + Traces/fftcomplex.h \ Traces/markerwidget.h \ Traces/trace.h \ - Traces/tracebodeplot.h \ Traces/traceeditdialog.h \ Traces/traceexportdialog.h \ Traces/traceimportdialog.h \ @@ -32,6 +31,8 @@ HEADERS += \ Traces/traceplot.h \ Traces/tracesmithchart.h \ Traces/tracewidget.h \ + Traces/tracexyplot.h \ + Traces/xyplotaxisdialog.h \ VNA/vna.h \ appwindow.h \ averaging.h \ @@ -63,10 +64,9 @@ SOURCES += \ SpectrumAnalyzer/spectrumanalyzer.cpp \ Tools/eseries.cpp \ Tools/impedancematchdialog.cpp \ - Traces/bodeplotaxisdialog.cpp \ + Traces/fftcomplex.cpp \ Traces/markerwidget.cpp \ Traces/trace.cpp \ - Traces/tracebodeplot.cpp \ Traces/traceeditdialog.cpp \ Traces/traceexportdialog.cpp \ Traces/traceimportdialog.cpp \ @@ -76,6 +76,8 @@ SOURCES += \ Traces/traceplot.cpp \ Traces/tracesmithchart.cpp \ Traces/tracewidget.cpp \ + Traces/tracexyplot.cpp \ + Traces/xyplotaxisdialog.cpp \ VNA/vna.cpp \ appwindow.cpp \ averaging.cpp \ @@ -103,12 +105,12 @@ FORMS += \ Device/manualcontroldialog.ui \ Generator/signalgenwidget.ui \ Tools/impedancematchdialog.ui \ - Traces/bodeplotaxisdialog.ui \ Traces/markerwidget.ui \ Traces/traceeditdialog.ui \ Traces/traceexportdialog.ui \ Traces/traceimportdialog.ui \ Traces/tracewidget.ui \ + Traces/xyplotaxisdialog.ui \ main.ui \ preferencesdialog.ui diff --git a/Software/PC_Application/CustomWidgets/tilewidget.cpp b/Software/PC_Application/CustomWidgets/tilewidget.cpp index 51e3ba4..78fa753 100644 --- a/Software/PC_Application/CustomWidgets/tilewidget.cpp +++ b/Software/PC_Application/CustomWidgets/tilewidget.cpp @@ -1,7 +1,7 @@ #include "tilewidget.h" #include "ui_tilewidget.h" #include -#include "Traces/tracebodeplot.h" +#include "Traces/tracexyplot.h" #include "Traces/tracesmithchart.h" TileWidget::TileWidget(TraceModel &model, QWidget *parent) : @@ -130,7 +130,7 @@ void TileWidget::on_bSmithchart_clicked() void TileWidget::on_bBodeplot_clicked() { - setContent(new TraceBodePlot(model)); + setContent(new TraceXYPlot(model)); } void TileWidget::traceDeleted(TracePlot *) diff --git a/Software/PC_Application/CustomWidgets/tilewidget.ui b/Software/PC_Application/CustomWidgets/tilewidget.ui index ce2f51c..f8a12fd 100644 --- a/Software/PC_Application/CustomWidgets/tilewidget.ui +++ b/Software/PC_Application/CustomWidgets/tilewidget.ui @@ -81,7 +81,7 @@ - Bodeplot + XY-plot diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index 8f04118..d5c0c90 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -25,7 +25,7 @@ #include "Traces/tracemodel.h" #include "Traces/tracewidget.h" #include "Traces/tracesmithchart.h" -#include "Traces/tracebodeplot.h" +#include "Traces/tracexyplot.h" #include "Traces/traceimportdialog.h" #include "CustomWidgets/tilewidget.h" #include "CustomWidgets/siunitedit.h" @@ -55,11 +55,11 @@ SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window) tPort2->fromLivedata(Trace::LivedataType::Overwrite, Trace::LiveParameter::Port2); traceModel.addTrace(tPort2); - auto tracebode = new TraceBodePlot(traceModel); + auto tracebode = new TraceXYPlot(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); + tracebode->setYAxis(0, TraceXYPlot::YAxisType::Magnitude, false, false, -120,0,10); + tracebode->setYAxis(1, TraceXYPlot::YAxisType::Disabled, false, true, 0,0,1); central->setPlot(tracebode); diff --git a/Software/PC_Application/Traces/bodeplotaxisdialog.cpp b/Software/PC_Application/Traces/bodeplotaxisdialog.cpp deleted file mode 100644 index 9e8f218..0000000 --- a/Software/PC_Application/Traces/bodeplotaxisdialog.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "bodeplotaxisdialog.h" -#include "ui_bodeplotaxisdialog.h" - -BodeplotAxisDialog::BodeplotAxisDialog(TraceBodePlot *plot) : - QDialog(nullptr), - ui(new Ui::BodeplotAxisDialog), - plot(plot) -{ - ui->setupUi(this); - - // Setup GUI connections - connect(ui->Y1type, qOverload(&QComboBox::currentIndexChanged), [this](int index) { - ui->Y1log->setEnabled(index != 0); - ui->Y1linear->setEnabled(index != 0); - ui->Y1auto->setEnabled(index != 0); - bool autoRange = ui->Y1auto->isChecked(); - ui->Y1min->setEnabled(index != 0 && !autoRange); - ui->Y1max->setEnabled(index != 0 && !autoRange); - ui->Y1divs->setEnabled(index != 0 && !autoRange); - auto type = (TraceBodePlot::YAxisType) index; - QString unit; - switch(type) { - case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break; - case TraceBodePlot::YAxisType::Phase: unit = "°"; break; - case TraceBodePlot::YAxisType::VSWR: unit = ""; break; - default: unit = ""; break; - } - ui->Y1min->setUnit(unit); - ui->Y1max->setUnit(unit); - ui->Y1divs->setUnit(unit); - }); - connect(ui->Y1auto, &QCheckBox::toggled, [this](bool checked) { - ui->Y1min->setEnabled(!checked); - ui->Y1max->setEnabled(!checked); - ui->Y1divs->setEnabled(!checked); - }); - - connect(ui->Y2type, qOverload(&QComboBox::currentIndexChanged), [this](int index) { - ui->Y2log->setEnabled(index != 0); - ui->Y2linear->setEnabled(index != 0); - ui->Y2auto->setEnabled(index != 0); - bool autoRange = ui->Y2auto->isChecked(); - ui->Y2min->setEnabled(index != 0 && !autoRange); - ui->Y2max->setEnabled(index != 0 && !autoRange); - ui->Y2divs->setEnabled(index != 0 && !autoRange); - auto type = (TraceBodePlot::YAxisType) index; - QString unit; - switch(type) { - case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break; - case TraceBodePlot::YAxisType::Phase: unit = "°"; break; - case TraceBodePlot::YAxisType::VSWR: unit = ""; break; - default: unit = ""; break; - } - ui->Y2min->setUnit(unit); - ui->Y2max->setUnit(unit); - ui->Y2divs->setUnit(unit); - }); - connect(ui->Y2auto, &QCheckBox::toggled, [this](bool checked) { - ui->Y2min->setEnabled(!checked); - ui->Y2max->setEnabled(!checked); - ui->Y2divs->setEnabled(!checked); - }); - - connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) { - ui->Xmin->setEnabled(!checked); - ui->Xmax->setEnabled(!checked); - ui->Xdivs->setEnabled(!checked); - }); - - ui->Xmin->setUnit("Hz"); - ui->Xmax->setUnit("Hz"); - ui->Xdivs->setUnit("Hz"); - ui->Xmin->setPrefixes(" kMG"); - ui->Xmax->setPrefixes(" kMG"); - ui->Xdivs->setPrefixes(" kMG"); - - // Fill initial values - // assume same order in YAxisType enum as in ComboBox items - ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type); - if(plot->YAxis[0].log) { - ui->Y1log->setChecked(true); - } else { - ui->Y1linear->setChecked(true); - } - ui->Y1auto->setChecked(plot->YAxis[0].autorange); - ui->Y1min->setValueQuiet(plot->YAxis[0].rangeMin); - ui->Y1max->setValueQuiet(plot->YAxis[0].rangeMax); - ui->Y1divs->setValueQuiet(plot->YAxis[0].rangeDiv); - - ui->Y2type->setCurrentIndex((int) plot->YAxis[1].type); - if(plot->YAxis[1].log) { - ui->Y2log->setChecked(true); - } else { - ui->Y2linear->setChecked(true); - } - ui->Y2auto->setChecked(plot->YAxis[1].autorange); - ui->Y2min->setValueQuiet(plot->YAxis[1].rangeMin); - ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax); - ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv); - - ui->Xauto->setChecked(plot->XAxis.autorange); - ui->Xmin->setValueQuiet(plot->XAxis.rangeMin); - ui->Xmax->setValueQuiet(plot->XAxis.rangeMax); - ui->Xdivs->setValueQuiet(plot->XAxis.rangeDiv); -} - -BodeplotAxisDialog::~BodeplotAxisDialog() -{ - delete ui; -} - -void BodeplotAxisDialog::on_buttonBox_accepted() -{ - // set plot values to the ones selected in the dialog - plot->setYAxis(0, (TraceBodePlot::YAxisType) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1divs->value()); - plot->setYAxis(1, (TraceBodePlot::YAxisType) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2divs->value()); - plot->setXAxis(ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value()); -} diff --git a/Software/PC_Application/Traces/bodeplotaxisdialog.h b/Software/PC_Application/Traces/bodeplotaxisdialog.h deleted file mode 100644 index c0ad49d..0000000 --- a/Software/PC_Application/Traces/bodeplotaxisdialog.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef BODEPLOTAXISDIALOG_H -#define BODEPLOTAXISDIALOG_H - -#include -#include "tracebodeplot.h" - -namespace Ui { -class BodeplotAxisDialog; -} - -class BodeplotAxisDialog : public QDialog -{ - Q_OBJECT - -public: - explicit BodeplotAxisDialog(TraceBodePlot *plot); - ~BodeplotAxisDialog(); - -private slots: - void on_buttonBox_accepted(); - -private: - Ui::BodeplotAxisDialog *ui; - TraceBodePlot *plot; -}; - -#endif // BODEPLOTAXISDIALOG_H diff --git a/Software/PC_Application/Traces/bodeplotaxisdialog.ui b/Software/PC_Application/Traces/bodeplotaxisdialog.ui deleted file mode 100644 index 122aa82..0000000 --- a/Software/PC_Application/Traces/bodeplotaxisdialog.ui +++ /dev/null @@ -1,462 +0,0 @@ - - - BodeplotAxisDialog - - - - 0 - 0 - 715 - 282 - - - - Axis Setup - - - true - - - - - - - - - - - 15 - - - - Primary Y axis - - - Qt::AlignCenter - - - - - - - - - Type: - - - - - - - - Disabled - - - - - Magnitude - - - - - Phase - - - - - VSWR - - - - - - - - - - Qt::Horizontal - - - - - - - - - Linear - - - Y1group - - - - - - - Log - - - Y1group - - - - - - - - - Qt::Horizontal - - - - - - - - - Range: - - - - - - - Auto - - - - - - - Maximum: - - - - - - - - - - Minimum: - - - - - - - - - - Divisions: - - - - - - - - - - - - - - Qt::Vertical - - - - - - - - - - 15 - - - - Secondary Y axis - - - Qt::AlignCenter - - - - - - - - - Type: - - - - - - - - Disabled - - - - - Magnitude - - - - - Phase - - - - - VSWR - - - - - - - - - - Qt::Horizontal - - - - - - - - - Linear - - - Y2group - - - - - - - Log - - - Y2group - - - - - - - - - Qt::Horizontal - - - - - - - - - Range: - - - - - - - Auto - - - - - - - Maximum: - - - - - - - - - - Minimum: - - - - - - - - - - Divisions: - - - - - - - - - - - - - - Qt::Vertical - - - - - - - - - - 0 - 0 - - - - - 15 - - - - X axis - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Range: - - - - - - - Auto - - - - - - - Maximum: - - - - - - - - - - Minimum: - - - - - - - - - - Divisions: - - - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - SIUnitEdit - QLineEdit -
CustomWidgets/siunitedit.h
-
-
- - - - buttonBox - accepted() - BodeplotAxisDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - BodeplotAxisDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - - - - -
diff --git a/Software/PC_Application/Traces/fftcomplex.cpp b/Software/PC_Application/Traces/fftcomplex.cpp new file mode 100644 index 0000000..86cad8f --- /dev/null +++ b/Software/PC_Application/Traces/fftcomplex.cpp @@ -0,0 +1,152 @@ +/* + * Free FFT and convolution (C++) + * + * Copyright (c) 2020 Project Nayuki. (MIT License) + * https://www.nayuki.io/page/free-small-fft-in-multiple-languages + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include +#include +#include +#include +#include "fftcomplex.h" + +using std::complex; +using std::size_t; +using std::uintmax_t; +using std::vector; + + +// Private function prototypes +static size_t reverseBits(size_t val, int width); + + +void Fft::transform(vector > &vec, bool inverse) { + size_t n = vec.size(); + if (n == 0) + return; + else if ((n & (n - 1)) == 0) // Is power of 2 + transformRadix2(vec, inverse); + else // More complicated algorithm for arbitrary sizes + transformBluestein(vec, inverse); +} + + +void Fft::transformRadix2(vector > &vec, bool inverse) { + // Length variables + size_t n = vec.size(); + int levels = 0; // Compute levels = floor(log2(n)) + for (size_t temp = n; temp > 1U; temp >>= 1) + levels++; + if (static_cast(1U) << levels != n) + throw std::domain_error("Length is not a power of 2"); + + // Trigonometric table + vector > expTable(n / 2); + for (size_t i = 0; i < n / 2; i++) + expTable[i] = std::polar(1.0, (inverse ? 2 : -2) * M_PI * i / n); + + // Bit-reversed addressing permutation + for (size_t i = 0; i < n; i++) { + size_t j = reverseBits(i, levels); + if (j > i) + std::swap(vec[i], vec[j]); + } + + // Cooley-Tukey decimation-in-time radix-2 FFT + for (size_t size = 2; size <= n; size *= 2) { + size_t halfsize = size / 2; + size_t tablestep = n / size; + for (size_t i = 0; i < n; i += size) { + for (size_t j = i, k = 0; j < i + halfsize; j++, k += tablestep) { + complex temp = vec[j + halfsize] * expTable[k]; + vec[j + halfsize] = vec[j] - temp; + vec[j] += temp; + } + } + if (size == n) // Prevent overflow in 'size *= 2' + break; + } +} + + +void Fft::transformBluestein(vector > &vec, bool inverse) { + // Find a power-of-2 convolution length m such that m >= n * 2 + 1 + size_t n = vec.size(); + size_t m = 1; + while (m / 2 <= n) { + if (m > SIZE_MAX / 2) + throw std::length_error("Vector too large"); + m *= 2; + } + + // Trigonometric table + vector > expTable(n); + for (size_t i = 0; i < n; i++) { + uintmax_t temp = static_cast(i) * i; + temp %= static_cast(n) * 2; + double angle = (inverse ? M_PI : -M_PI) * temp / n; + expTable[i] = std::polar(1.0, angle); + } + + // Temporary vectors and preprocessing + vector > avec(m); + for (size_t i = 0; i < n; i++) + avec[i] = vec[i] * expTable[i]; + vector > bvec(m); + bvec[0] = expTable[0]; + for (size_t i = 1; i < n; i++) + bvec[i] = bvec[m - i] = std::conj(expTable[i]); + + // Convolution + vector > cvec(m); + convolve(avec, bvec, cvec); + + // Postprocessing + for (size_t i = 0; i < n; i++) + vec[i] = cvec[i] * expTable[i]; +} + + +void Fft::convolve( + const vector > &xvec, + const vector > &yvec, + vector > &outvec) { + + size_t n = xvec.size(); + if (n != yvec.size() || n != outvec.size()) + throw std::domain_error("Mismatched lengths"); + vector > xv = xvec; + vector > yv = yvec; + transform(xv, false); + transform(yv, false); + for (size_t i = 0; i < n; i++) + xv[i] *= yv[i]; + transform(xv, true); + for (size_t i = 0; i < n; i++) // Scaling (because this FFT implementation omits it) + outvec[i] = xv[i] / static_cast(n); +} + + +static size_t reverseBits(size_t val, int width) { + size_t result = 0; + for (int i = 0; i < width; i++, val >>= 1) + result = (result << 1) | (val & 1U); + return result; +} diff --git a/Software/PC_Application/Traces/fftcomplex.h b/Software/PC_Application/Traces/fftcomplex.h new file mode 100644 index 0000000..ae5f0f5 --- /dev/null +++ b/Software/PC_Application/Traces/fftcomplex.h @@ -0,0 +1,62 @@ +/* + * Free FFT and convolution (C++) + * + * Copyright (c) 2020 Project Nayuki. (MIT License) + * https://www.nayuki.io/page/free-small-fft-in-multiple-languages + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include +#include + + +namespace Fft { + + /* + * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. + * The vector can have any length. This is a wrapper function. The inverse transform does not perform scaling, so it is not a true inverse. + */ + void transform(std::vector > &vec, bool inverse); + + + /* + * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. + * The vector's length must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm. + */ + void transformRadix2(std::vector > &vec, bool inverse); + + + /* + * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. + * The vector can have any length. This requires the convolution function, which in turn requires the radix-2 FFT function. + * Uses Bluestein's chirp z-transform algorithm. + */ + void transformBluestein(std::vector > &vec, bool inverse); + + + /* + * Computes the circular convolution of the given complex vectors. Each vector's length must be the same. + */ + void convolve( + const std::vector > &xvec, + const std::vector > &yvec, + std::vector > &outvec); + +} diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index b1efd3b..0eccc99 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -1,9 +1,12 @@ #include "trace.h" +#include +#include "fftcomplex.h" using namespace std; Trace::Trace(QString name, QColor color, LiveParameter live) - : _name(name), + : tdr_users(0), + _name(name), _color(color), _liveType(LivedataType::Overwrite), _liveParam(live), @@ -64,6 +67,12 @@ void Trace::addData(Trace::Data d) { } emit dataAdded(this, d); emit dataChanged(); + if(lower == _data.begin()) { + // received the first point, which means the last sweep just finished + if(tdr_users) { + updateTimeDomainData(); + } + } } void Trace::setName(QString name) { @@ -137,12 +146,79 @@ void Trace::removeMarker(TraceMarker *m) markers.erase(m); emit markerRemoved(m); } +//#include +//#include + +void Trace::updateTimeDomainData() +{ +// using namespace std::chrono; +// auto starttime = duration_cast< milliseconds >( +// system_clock::now().time_since_epoch() +// ).count(); + auto steps = size(); + if(minFreq() * size() != maxFreq()) { + // data is not available with correct frequency spacing, calculate required steps + steps = maxFreq() / minFreq(); + } + const double PI = 3.141592653589793238463; + // reserve vector for negative frequenies and DC as well + vector> frequencyDomain(2*steps + 1); + // copy frequencies, use the flipped conjugate for negative part + for(unsigned int i = 1;i<=steps;i++) { + auto S = getData(minFreq() * i); + constexpr double alpha0 = 0.54; + auto hamming = alpha0 - (1.0 - alpha0) * -cos(PI * i / steps); + S *= hamming; + frequencyDomain[2 * steps - i + 1] = conj(S); + frequencyDomain[i] = S; + } + // use simple extrapolation from lowest two points to extract DC value + auto abs_DC = 2.0 * abs(frequencyDomain[1]) - abs(frequencyDomain[2]); + auto phase_DC = 2.0 * arg(frequencyDomain[1]) - arg(frequencyDomain[2]); + frequencyDomain[0] = polar(abs_DC, phase_DC); + + auto fft_bins = frequencyDomain.size(); + timeDomain.clear(); + timeDomain.resize(fft_bins); + const double fs = 1.0 / (minFreq() * fft_bins); + double last_step = 0.0; + + Fft::transform(frequencyDomain, true); + for(unsigned int i = 0;i( +// system_clock::now().time_since_epoch() +// ).count() - starttime; +// cout << "TDR: " << this << " (took " << duration << "ms)" < 0) { + tdr_users--; + } +} + void Trace::setCalibration(bool value) { calibration = value; @@ -288,7 +364,16 @@ std::complex Trace::getData(double frequency) return std::numeric_limits>::quiet_NaN(); } - return sample(index(frequency)).S; + auto i = index(frequency); + if(_data.at(i).frequency == frequency) { + return _data[i].S; + } else { + // no exact frequency match, needs to interpolate + auto high = _data[i]; + auto low = _data[i-1]; + double alpha = (frequency - low.frequency) / (high.frequency - low.frequency); + return low.S * (1 - alpha) + high.S * alpha; + } } int Trace::index(double frequency) diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index e0d3d0a..bbaa9f1 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -21,6 +21,13 @@ public: std::complex S; }; + class TimedomainData { + public: + double time; + double impulseResponse; + double stepResponse; + }; + enum class LiveParameter { S11, S12, @@ -76,6 +83,14 @@ public: void setCalibration(bool value); void setReflection(bool value); + // TDR calculation can be ressource intensive, only perform when some other module is interested. + // Each interested module should call addTDRinterest(), read the data with getTDR() and finally + // call removeTDRinterest() once TDR updates are no longer required. + // The data is only updated at the end of a sweep and upon the first addTDRinterest() call. + void addTDRinterest(); + void removeTDRinterest(); + const std::vector& getTDR() { return timeDomain;} + public slots: void setTouchstoneParameter(int value); void setTouchstoneFilename(const QString &value); @@ -98,7 +113,11 @@ signals: void markerRemoved(TraceMarker *m); private: + void updateTimeDomainData(); + void printTimeDomain(); std::vector _data; + std::vector timeDomain; + unsigned int tdr_users; QString _name; QColor _color; LivedataType _liveType; diff --git a/Software/PC_Application/Traces/tracebodeplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp similarity index 62% rename from Software/PC_Application/Traces/tracebodeplot.cpp rename to Software/PC_Application/Traces/tracexyplot.cpp index 063e9c2..ed26502 100644 --- a/Software/PC_Application/Traces/tracebodeplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -1,4 +1,4 @@ -#include "tracebodeplot.h" +#include "tracexyplot.h" #include #include "qwtplotpiecewisecurve.h" #include "qwt_series_data.h" @@ -11,18 +11,26 @@ #include "tracemarker.h" #include #include -#include "bodeplotaxisdialog.h" +#include "xyplotaxisdialog.h" #include using namespace std; -set TraceBodePlot::allPlots; +set TraceXYPlot::allPlots; -static double AxisTransformation(TraceBodePlot::YAxisType type, complex data) { +const set TraceXYPlot::YAxisTypes = {TraceXYPlot::YAxisType::Disabled, + TraceXYPlot::YAxisType::Magnitude, + TraceXYPlot::YAxisType::Phase, + TraceXYPlot::YAxisType::VSWR, + TraceXYPlot::YAxisType::Impulse, + TraceXYPlot::YAxisType::Step, + TraceXYPlot::YAxisType::Impedance}; + +static double FrequencyAxisTransformation(TraceXYPlot::YAxisType type, complex data) { switch(type) { - case TraceBodePlot::YAxisType::Magnitude: return 20*log10(abs(data)); break; - case TraceBodePlot::YAxisType::Phase: return arg(data) * 180.0 / M_PI; break; - case TraceBodePlot::YAxisType::VSWR: + case TraceXYPlot::YAxisType::Magnitude: return 20*log10(abs(data)); break; + case TraceXYPlot::YAxisType::Phase: return arg(data) * 180.0 / M_PI; break; + case TraceXYPlot::YAxisType::VSWR: if(abs(data) < 1.0) { return (1+abs(data)) / (1-abs(data)); } @@ -31,31 +39,79 @@ static double AxisTransformation(TraceBodePlot::YAxisType type, complex } return numeric_limits::quiet_NaN(); } +static double TimeAxisTransformation(TraceXYPlot::YAxisType type, Trace *t, int index) { + auto timeData = t->getTDR()[index]; + switch(type) { + case TraceXYPlot::YAxisType::Impulse: return timeData.impulseResponse; break; + case TraceXYPlot::YAxisType::Step: return timeData.stepResponse; break; + case TraceXYPlot::YAxisType::Impedance: + if(abs(timeData.stepResponse) < 1.0) { + return 50 * (1+timeData.stepResponse) / (1-timeData.stepResponse); + } + break; + default: break; + } + return numeric_limits::quiet_NaN(); +} -template class QwtTraceSeries : public QwtSeriesData { +class QwtTraceSeries : public QwtSeriesData { public: - QwtTraceSeries(Trace &t) + QwtTraceSeries(Trace &t, TraceXYPlot::YAxisType Ytype, TraceXYPlot::XAxisType Xtype) : QwtSeriesData(), - t(t){}; + Ytype(Ytype), + Xtype(Xtype), + t(t){} size_t size() const override { - return t.size(); + switch(Ytype) { + case TraceXYPlot::YAxisType::Magnitude: + case TraceXYPlot::YAxisType::Phase: + case TraceXYPlot::YAxisType::VSWR: + return t.size(); + case TraceXYPlot::YAxisType::Impulse: + case TraceXYPlot::YAxisType::Step: + case TraceXYPlot::YAxisType::Impedance: + return t.getTDR().size(); + default: + return 0; + } } QPointF sample(size_t i) const override { - Trace::Data d = t.sample(i); - QPointF p; - p.setX(d.frequency); - p.setY(AxisTransformation(E, d.S)); - return p; + switch(Ytype) { + case TraceXYPlot::YAxisType::Magnitude: + case TraceXYPlot::YAxisType::Phase: + case TraceXYPlot::YAxisType::VSWR: { + Trace::Data d = t.sample(i); + QPointF p; + p.setX(d.frequency); + p.setY(FrequencyAxisTransformation(Ytype, d.S)); + return p; + } + case TraceXYPlot::YAxisType::Impulse: + case TraceXYPlot::YAxisType::Step: + case TraceXYPlot::YAxisType::Impedance: { + auto sample = t.getTDR()[i]; + QPointF p; + // TODO set distance + p.setX(sample.time); + p.setY(TimeAxisTransformation(Ytype, &t, i)); + return p; + } + default: + return QPointF(); + } + } QRectF boundingRect() const override { return qwtBoundingRect(*this); } private: + TraceXYPlot::YAxisType Ytype; + TraceXYPlot::XAxisType Xtype; Trace &t; }; -TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent) +TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent) : TracePlot(parent), selectedMarker(nullptr) { @@ -69,10 +125,10 @@ TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent) grid->attach(plot); setColorFromPreferences(); - auto selectPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas()); + auto selectPicker = new XYplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas()); selectPicker->setStateMachine(new QwtPickerClickPointMachine); - drawPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas()); + drawPicker = new XYplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas()); drawPicker->setStateMachine(new QwtPickerDragPointMachine); drawPicker->setTrackerPen(QPen(Qt::white)); @@ -94,14 +150,14 @@ TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent) setYAxis(1, YAxisType::Phase, false, false, -180, 180, 30); // enable autoscaling and set for full span (no information about actual span available yet) setXAxis(0, 6000000000); - setXAxis(true, 0, 6000000000, 600000000); + setXAxis(XAxisType::Frequency, true, 0, 6000000000, 600000000); // get notified when the span changes - connect(&model, &TraceModel::SpanChanged, this, qOverload(&TraceBodePlot::setXAxis)); + connect(&model, &TraceModel::SpanChanged, this, qOverload(&TraceXYPlot::setXAxis)); allPlots.insert(this); } -TraceBodePlot::~TraceBodePlot() +TraceXYPlot::~TraceXYPlot() { for(int axis = 0;axis < 2;axis++) { for(auto pd : curves[axis]) { @@ -112,17 +168,16 @@ TraceBodePlot::~TraceBodePlot() allPlots.erase(this); } -void TraceBodePlot::setXAxis(double min, double max) +void TraceXYPlot::setXAxis(double min, double max) { sweep_fmin = min; sweep_fmax = max; updateXAxis(); } -void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log, bool autorange, double min, double max, double div) +void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool autorange, double min, double max, double div) { - if(YAxis[axis].type != type) { - YAxis[axis].type = type; + if(YAxis[axis].Ytype != type) { // remove traces that are active but not supported with the new axis type bool erased = false; do { @@ -136,6 +191,13 @@ void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log, } } while(erased); + if(isTDRtype(YAxis[axis].Ytype)) { + for(auto t : tracesAxis[axis]) { + t->removeTDRinterest(); + } + } + YAxis[axis].Ytype = type; + for(auto t : tracesAxis[axis]) { // supported but needs an adjusted QwtSeriesData auto td = curves[axis][t]; @@ -149,6 +211,9 @@ void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log, markerDataChanged(m); } } + if(isTDRtype(type)) { + t->addTDRinterest(); + } } } YAxis[axis].log = log; @@ -168,8 +233,9 @@ void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log, replot(); } -void TraceBodePlot::setXAxis(bool autorange, double min, double max, double div) +void TraceXYPlot::setXAxis(XAxisType type, bool autorange, double min, double max, double div) { + XAxis.Xtype = type; XAxis.autorange = autorange; XAxis.rangeMin = min; XAxis.rangeMax = max; @@ -177,33 +243,45 @@ void TraceBodePlot::setXAxis(bool autorange, double min, double max, double div) updateXAxis(); } -void TraceBodePlot::enableTrace(Trace *t, bool enabled) +void TraceXYPlot::enableTrace(Trace *t, bool enabled) { for(int axis = 0;axis < 2;axis++) { - if(supported(t, YAxis[axis].type)) { + if(supported(t, YAxis[axis].Ytype)) { enableTraceAxis(t, axis, enabled); } } } -void TraceBodePlot::updateGraphColors() +void TraceXYPlot::updateGraphColors() { for(auto p : allPlots) { p->setColorFromPreferences(); } } -void TraceBodePlot::updateContextMenu() +bool TraceXYPlot::isTDRtype(TraceXYPlot::YAxisType type) +{ + switch(type) { + case YAxisType::Impulse: + case YAxisType::Step: + case YAxisType::Impedance: + return true; + default: + return false; + } +} + +void TraceXYPlot::updateContextMenu() { contextmenu->clear(); auto setup = new QAction("Axis setup..."); connect(setup, &QAction::triggered, [this]() { - auto setup = new BodeplotAxisDialog(this); + auto setup = new XYplotAxisDialog(this); setup->show(); }); contextmenu->addAction(setup); for(int axis = 0;axis < 2;axis++) { - if(YAxis[axis].type == YAxisType::Disabled) { + if(YAxis[axis].Ytype == YAxisType::Disabled) { continue; } if(axis == 0) { @@ -213,7 +291,7 @@ void TraceBodePlot::updateContextMenu() } for(auto t : traces) { // Skip traces that are not applicable for the selected axis type - if(!supported(t.first, YAxis[axis].type)) { + if(!supported(t.first, YAxis[axis].Ytype)) { continue; } @@ -236,18 +314,18 @@ void TraceBodePlot::updateContextMenu() }); } -bool TraceBodePlot::supported(Trace *) +bool TraceXYPlot::supported(Trace *) { // potentially possible to add every kind of trace (depends on axis) return true; } -void TraceBodePlot::replot() +void TraceXYPlot::replot() { plot->replot(); } -QString TraceBodePlot::AxisTypeToName(TraceBodePlot::YAxisType type) +QString TraceXYPlot::AxisTypeToName(TraceXYPlot::YAxisType type) { switch(type) { case YAxisType::Disabled: return "Disabled"; break; @@ -258,7 +336,7 @@ QString TraceBodePlot::AxisTypeToName(TraceBodePlot::YAxisType type) } } -void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled) +void TraceXYPlot::enableTraceAxis(Trace *t, int axis, bool enabled) { bool alreadyEnabled = tracesAxis[axis].find(t) != tracesAxis[axis].end(); if(alreadyEnabled != enabled) { @@ -272,20 +350,26 @@ void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled) cd.curve->setSamples(cd.data); curves[axis][t] = cd; // connect signals - connect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot); - connect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged); - connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged); - connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot); + connect(t, &Trace::dataChanged, this, &TraceXYPlot::triggerReplot); + connect(t, &Trace::colorChanged, this, &TraceXYPlot::traceColorChanged); + connect(t, &Trace::visibilityChanged, this, &TraceXYPlot::traceColorChanged); + connect(t, &Trace::visibilityChanged, this, &TraceXYPlot::triggerReplot); if(axis == 0) { - connect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded); - connect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved); + connect(t, &Trace::markerAdded, this, &TraceXYPlot::markerAdded); + connect(t, &Trace::markerRemoved, this, &TraceXYPlot::markerRemoved); auto tracemarkers = t->getMarkers(); for(auto m : tracemarkers) { markerAdded(m); } } + if(isTDRtype(YAxis[axis].Ytype)) { + t->addTDRinterest(); + } traceColorChanged(t); } else { + if(isTDRtype(YAxis[axis].Ytype)) { + t->removeTDRinterest(); + } tracesAxis[axis].erase(t); // clean up and delete if(curves[axis].find(t) != curves[axis].end()) { @@ -295,14 +379,14 @@ void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled) int otherAxis = axis == 0 ? 1 : 0; if(curves[otherAxis].find(t) == curves[otherAxis].end()) { // this trace is not used anymore, disconnect from notifications - disconnect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot); - disconnect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged); - disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged); - disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot); + disconnect(t, &Trace::dataChanged, this, &TraceXYPlot::triggerReplot); + disconnect(t, &Trace::colorChanged, this, &TraceXYPlot::traceColorChanged); + disconnect(t, &Trace::visibilityChanged, this, &TraceXYPlot::traceColorChanged); + disconnect(t, &Trace::visibilityChanged, this, &TraceXYPlot::triggerReplot); } if(axis == 0) { - disconnect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded); - disconnect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved); + disconnect(t, &Trace::markerAdded, this, &TraceXYPlot::markerAdded); + disconnect(t, &Trace::markerRemoved, this, &TraceXYPlot::markerRemoved); auto tracemarkers = t->getMarkers(); for(auto m : tracemarkers) { markerRemoved(m); @@ -315,7 +399,7 @@ void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled) } } -bool TraceBodePlot::supported(Trace *t, TraceBodePlot::YAxisType type) +bool TraceXYPlot::supported(Trace *t, TraceXYPlot::YAxisType type) { switch(type) { case YAxisType::Disabled: @@ -331,7 +415,7 @@ bool TraceBodePlot::supported(Trace *t, TraceBodePlot::YAxisType type) return true; } -void TraceBodePlot::updateXAxis() +void TraceXYPlot::updateXAxis() { if(XAxis.autorange && sweep_fmax-sweep_fmin > 0) { QList tickList; @@ -346,21 +430,12 @@ void TraceBodePlot::updateXAxis() triggerReplot(); } -QwtSeriesData *TraceBodePlot::createQwtSeriesData(Trace &t, int axis) +QwtSeriesData *TraceXYPlot::createQwtSeriesData(Trace &t, int axis) { - switch(YAxis[axis].type) { - case YAxisType::Magnitude: - return new QwtTraceSeries(t); - case YAxisType::Phase: - return new QwtTraceSeries(t); - case YAxisType::VSWR: - return new QwtTraceSeries(t); - default: - return nullptr; - } + return new QwtTraceSeries(t, YAxis[axis].Ytype, XAxis.Xtype); } -void TraceBodePlot::traceColorChanged(Trace *t) +void TraceXYPlot::traceColorChanged(Trace *t) { for(int axis = 0;axis < 2;axis++) { if(curves[axis].find(t) != curves[axis].end()) { @@ -388,7 +463,7 @@ void TraceBodePlot::traceColorChanged(Trace *t) } } -void TraceBodePlot::markerAdded(TraceMarker *m) +void TraceXYPlot::markerAdded(TraceMarker *m) { if(markers.count(m)) { return; @@ -396,17 +471,17 @@ void TraceBodePlot::markerAdded(TraceMarker *m) auto qwtMarker = new QwtPlotMarker; markers[m] = qwtMarker; markerSymbolChanged(m); - connect(m, &TraceMarker::symbolChanged, this, &TraceBodePlot::markerSymbolChanged); - connect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged); + connect(m, &TraceMarker::symbolChanged, this, &TraceXYPlot::markerSymbolChanged); + connect(m, &TraceMarker::dataChanged, this, &TraceXYPlot::markerDataChanged); markerDataChanged(m); qwtMarker->attach(plot); triggerReplot(); } -void TraceBodePlot::markerRemoved(TraceMarker *m) +void TraceXYPlot::markerRemoved(TraceMarker *m) { - disconnect(m, &TraceMarker::symbolChanged, this, &TraceBodePlot::markerSymbolChanged); - disconnect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged); + disconnect(m, &TraceMarker::symbolChanged, this, &TraceXYPlot::markerSymbolChanged); + disconnect(m, &TraceMarker::dataChanged, this, &TraceXYPlot::markerDataChanged); if(markers.count(m)) { markers[m]->detach(); delete markers[m]; @@ -415,15 +490,15 @@ void TraceBodePlot::markerRemoved(TraceMarker *m) triggerReplot(); } -void TraceBodePlot::markerDataChanged(TraceMarker *m) +void TraceXYPlot::markerDataChanged(TraceMarker *m) { auto qwtMarker = markers[m]; qwtMarker->setXValue(m->getFrequency()); - qwtMarker->setYValue(AxisTransformation(YAxis[0].type, m->getData())); + qwtMarker->setYValue(FrequencyAxisTransformation(YAxis[0].Ytype, m->getData())); triggerReplot(); } -void TraceBodePlot::markerSymbolChanged(TraceMarker *m) +void TraceXYPlot::markerSymbolChanged(TraceMarker *m) { auto qwtMarker = markers[m]; auto old_sym = qwtMarker->symbol(); @@ -437,7 +512,7 @@ void TraceBodePlot::markerSymbolChanged(TraceMarker *m) triggerReplot(); } -void TraceBodePlot::clicked(const QPointF pos) +void TraceXYPlot::clicked(const QPointF pos) { auto clickPoint = drawPicker->plotToPixel(pos); unsigned int closestDistance = numeric_limits::max(); @@ -461,7 +536,7 @@ void TraceBodePlot::clicked(const QPointF pos) } } -void TraceBodePlot::moved(const QPointF pos) +void TraceXYPlot::moved(const QPointF pos) { if(!selectedMarker || !selectedCurve) { return; @@ -469,7 +544,7 @@ void TraceBodePlot::moved(const QPointF pos) selectedMarker->setFrequency(pos.x()); } -void TraceBodePlot::setColorFromPreferences() +void TraceXYPlot::setColorFromPreferences() { auto pref = Preferences::getInstance(); plot->setCanvasBackground(pref.General.graphColors.background); diff --git a/Software/PC_Application/Traces/tracebodeplot.h b/Software/PC_Application/Traces/tracexyplot.h similarity index 67% rename from Software/PC_Application/Traces/tracebodeplot.h rename to Software/PC_Application/Traces/tracexyplot.h index 0564427..afb0ba4 100644 --- a/Software/PC_Application/Traces/tracebodeplot.h +++ b/Software/PC_Application/Traces/tracexyplot.h @@ -1,5 +1,5 @@ -#ifndef TRACEBODEPLOT_H -#define TRACEBODEPLOT_H +#ifndef TRACEXYPLOT_H +#define TRACEXYPLOT_H #include "traceplot.h" #include @@ -11,10 +11,10 @@ #include // Derived plotpicker, exposing transformation functions -class BodeplotPicker : public QwtPlotPicker { +class XYplotPicker : public QwtPlotPicker { Q_OBJECT public: - BodeplotPicker(int xAxis, int yAxis, RubberBand rubberBand, DisplayMode trackerMode, QWidget *w) + XYplotPicker(int xAxis, int yAxis, RubberBand rubberBand, DisplayMode trackerMode, QWidget *w) : QwtPlotPicker(xAxis, yAxis, rubberBand, trackerMode, w) {}; QPoint plotToPixel(const QPointF &pos) { return transform(pos); @@ -24,33 +24,46 @@ public: } }; -class TraceBodePlot : public TracePlot +class TraceXYPlot : public TracePlot { - friend class BodeplotAxisDialog; + friend class XYplotAxisDialog; Q_OBJECT public: - TraceBodePlot(TraceModel &model, QWidget *parent = nullptr); - ~TraceBodePlot(); + TraceXYPlot(TraceModel &model, QWidget *parent = nullptr); + ~TraceXYPlot(); enum class YAxisType { Disabled = 0, + // S parameter options Magnitude = 1, Phase = 2, VSWR = 3, + // TDR options + Impulse = 4, + Step = 5, + Impedance = 6, Last, }; + static const std::set YAxisTypes; + enum class XAxisType { + Frequency, + Time, + Distance, + }; virtual void setXAxis(double min, double max) override; void setYAxis(int axis, YAxisType type, bool log, bool autorange, double min, double max, double div); - void setXAxis(bool autorange, double min, double max, double div); + void setXAxis(XAxisType type, bool autorange, double min, double max, double div); void enableTrace(Trace *t, bool enabled) override; - // Applies potentially changed colors to all bodeplots + // Applies potentially changed colors to all XY-plots static void updateGraphColors(); + bool isTDRtype(YAxisType type); + protected: - virtual void updateContextMenu(); - virtual bool supported(Trace *t); + virtual void updateContextMenu() override; + virtual bool supported(Trace *t) override; void replot() override; private slots: @@ -74,7 +87,10 @@ private: class Axis { public: - YAxisType type; + union { + YAxisType Ytype; + XAxisType Xtype; + }; bool log; bool autorange; double rangeMin; @@ -97,10 +113,10 @@ private: TraceMarker *selectedMarker; QwtPlotCurve *selectedCurve; - BodeplotPicker *drawPicker; + XYplotPicker *drawPicker; // keep track of all created plots for changing colors - static std::set allPlots; + static std::set allPlots; }; -#endif // TRACEBODEPLOT_H +#endif // TRACEXYPLOT_H diff --git a/Software/PC_Application/Traces/xyplotaxisdialog.cpp b/Software/PC_Application/Traces/xyplotaxisdialog.cpp new file mode 100644 index 0000000..f984ffb --- /dev/null +++ b/Software/PC_Application/Traces/xyplotaxisdialog.cpp @@ -0,0 +1,177 @@ +#include "xyplotaxisdialog.h" +#include "ui_bodeplotaxisdialog.h" +#include + +using namespace std; + +XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : + QDialog(nullptr), + ui(new Ui::XYplotAxisDialog), + plot(plot) +{ + ui->setupUi(this); + + // Setup GUI connections + connect(ui->Y1type, qOverload(&QComboBox::currentIndexChanged), [this](int index) { + //ui->Y1log->setEnabled(index != 0); + ui->Y1linear->setEnabled(index != 0); + ui->Y1auto->setEnabled(index != 0); + bool autoRange = ui->Y1auto->isChecked(); + ui->Y1min->setEnabled(index != 0 && !autoRange); + ui->Y1max->setEnabled(index != 0 && !autoRange); + ui->Y1divs->setEnabled(index != 0 && !autoRange); + auto type = (TraceXYPlot::YAxisType) index; + QString unit = YAxisUnit(type); + ui->Y1min->setUnit(unit); + ui->Y1max->setUnit(unit); + ui->Y1divs->setUnit(unit); + }); + connect(ui->Y1auto, &QCheckBox::toggled, [this](bool checked) { + ui->Y1min->setEnabled(!checked); + ui->Y1max->setEnabled(!checked); + ui->Y1divs->setEnabled(!checked); + }); + + connect(ui->Y2type, qOverload(&QComboBox::currentIndexChanged), [this](int index) { + //ui->Y2log->setEnabled(index != 0); + ui->Y2linear->setEnabled(index != 0); + ui->Y2auto->setEnabled(index != 0); + bool autoRange = ui->Y2auto->isChecked(); + ui->Y2min->setEnabled(index != 0 && !autoRange); + ui->Y2max->setEnabled(index != 0 && !autoRange); + ui->Y2divs->setEnabled(index != 0 && !autoRange); + auto type = (TraceXYPlot::YAxisType) index; + QString unit = YAxisUnit(type); + ui->Y2min->setUnit(unit); + ui->Y2max->setUnit(unit); + ui->Y2divs->setUnit(unit); + }); + + connect(ui->XType, qOverload(&QComboBox::currentIndexChanged), this, &XYplotAxisDialog::XAxisTypeChanged); + + connect(ui->Y2auto, &QCheckBox::toggled, [this](bool checked) { + ui->Y2min->setEnabled(!checked); + ui->Y2max->setEnabled(!checked); + ui->Y2divs->setEnabled(!checked); + }); + + connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) { + ui->Xmin->setEnabled(!checked); + ui->Xmax->setEnabled(!checked); + ui->Xdivs->setEnabled(!checked); + }); + + ui->XType->setCurrentIndex((int) plot->XAxis.Xtype); + ui->Xmin->setPrefixes("pnum kMG"); + ui->Xmax->setPrefixes("pnum kMG"); + ui->Xdivs->setPrefixes("pnum kMG"); + + // Fill initial values + // assume same order in YAxisType enum as in ComboBox items + ui->Y1type->setCurrentIndex((int) plot->YAxis[0].Ytype); + if(plot->YAxis[0].log) { + ui->Y1log->setChecked(true); + } else { + ui->Y1linear->setChecked(true); + } + ui->Y1auto->setChecked(plot->YAxis[0].autorange); + ui->Y1min->setValueQuiet(plot->YAxis[0].rangeMin); + ui->Y1max->setValueQuiet(plot->YAxis[0].rangeMax); + ui->Y1divs->setValueQuiet(plot->YAxis[0].rangeDiv); + + ui->Y2type->setCurrentIndex((int) plot->YAxis[1].Ytype); + if(plot->YAxis[1].log) { + ui->Y2log->setChecked(true); + } else { + ui->Y2linear->setChecked(true); + } + ui->Y2auto->setChecked(plot->YAxis[1].autorange); + ui->Y2min->setValueQuiet(plot->YAxis[1].rangeMin); + ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax); + ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv); + + ui->Xauto->setChecked(plot->XAxis.autorange); + ui->Xmin->setValueQuiet(plot->XAxis.rangeMin); + ui->Xmax->setValueQuiet(plot->XAxis.rangeMax); + ui->Xdivs->setValueQuiet(plot->XAxis.rangeDiv); +} + +XYplotAxisDialog::~XYplotAxisDialog() +{ + delete ui; +} + +void XYplotAxisDialog::on_buttonBox_accepted() +{ + // set plot values to the ones selected in the dialog + plot->setYAxis(0, (TraceXYPlot::YAxisType) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1divs->value()); + plot->setYAxis(1, (TraceXYPlot::YAxisType) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2divs->value()); + plot->setXAxis((TraceXYPlot::XAxisType) ui->XType->currentIndex(), ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value()); +} + +void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex) +{ + auto type = (TraceXYPlot::XAxisType) XAxisIndex; + auto supported = supportedYAxis(type); + for(auto t : TraceXYPlot::YAxisTypes) { + auto enable = supported.count(t) > 0; + auto index = (int) t; + auto *model = qobject_cast(ui->Y1type->model()); + auto item = model->item(index); + item->setFlags(enable ? item->flags() | Qt::ItemIsEnabled + : item->flags() & ~Qt::ItemIsEnabled); + model = qobject_cast(ui->Y2type->model()); + item = model->item(index); + item->setFlags(enable ? item->flags() | Qt::ItemIsEnabled + : item->flags() & ~Qt::ItemIsEnabled); + } + // Disable Yaxis if previously selected type is not supported + if(!supported.count((TraceXYPlot::YAxisType)ui->Y1type->currentIndex())) { + ui->Y1type->setCurrentIndex(0); + } + if(!supported.count((TraceXYPlot::YAxisType)ui->Y2type->currentIndex())) { + ui->Y2type->setCurrentIndex(0); + } + + QString unit; + switch(type) { + case TraceXYPlot::XAxisType::Frequency: unit = "Hz"; break; + case TraceXYPlot::XAxisType::Time: unit = "s"; break; + case TraceXYPlot::XAxisType::Distance: unit = "m"; break; + } + ui->Xmin->setUnit(unit); + ui->Xmax->setUnit(unit); + ui->Xdivs->setUnit(unit); +} + +QString XYplotAxisDialog::YAxisUnit(TraceXYPlot::YAxisType type) +{ + switch(type) { + case TraceXYPlot::YAxisType::Magnitude: return "db"; break; + case TraceXYPlot::YAxisType::Phase: return "°"; break; + case TraceXYPlot::YAxisType::VSWR: return ""; break; + case TraceXYPlot::YAxisType::Impulse: return ""; break; + case TraceXYPlot::YAxisType::Step: return ""; break; + case TraceXYPlot::YAxisType::Impedance: return "Ohm"; break; + default: return ""; break; + } +} + +std::set XYplotAxisDialog::supportedYAxis(TraceXYPlot::XAxisType type) +{ + set ret = {TraceXYPlot::YAxisType::Disabled}; + switch(type) { + case TraceXYPlot::XAxisType::Frequency: + ret.insert(TraceXYPlot::YAxisType::Magnitude); + ret.insert(TraceXYPlot::YAxisType::Phase); + ret.insert(TraceXYPlot::YAxisType::VSWR); + break; + case TraceXYPlot::XAxisType::Time: + case TraceXYPlot::XAxisType::Distance: + ret.insert(TraceXYPlot::YAxisType::Impulse); + ret.insert(TraceXYPlot::YAxisType::Step); + ret.insert(TraceXYPlot::YAxisType::Impedance); + break; + } + return ret; +} diff --git a/Software/PC_Application/Traces/xyplotaxisdialog.h b/Software/PC_Application/Traces/xyplotaxisdialog.h new file mode 100644 index 0000000..56e8af5 --- /dev/null +++ b/Software/PC_Application/Traces/xyplotaxisdialog.h @@ -0,0 +1,30 @@ +#ifndef XYPLOTAXISDIALOG_H +#define XYPLOTAXISDIALOG_H + +#include +#include "tracexyplot.h" + +namespace Ui { +class XYplotAxisDialog; +} + +class XYplotAxisDialog : public QDialog +{ + Q_OBJECT + +public: + explicit XYplotAxisDialog(TraceXYPlot *plot); + ~XYplotAxisDialog(); + +private slots: + void on_buttonBox_accepted(); + void XAxisTypeChanged(int XAxisIndex); + +private: + QString YAxisUnit(TraceXYPlot::YAxisType type); + std::set supportedYAxis(TraceXYPlot::XAxisType type); + Ui::XYplotAxisDialog *ui; + TraceXYPlot *plot; +}; + +#endif // XYPLOTAXISDIALOG_H diff --git a/Software/PC_Application/Traces/xyplotaxisdialog.ui b/Software/PC_Application/Traces/xyplotaxisdialog.ui new file mode 100644 index 0000000..6c4c5ef --- /dev/null +++ b/Software/PC_Application/Traces/xyplotaxisdialog.ui @@ -0,0 +1,547 @@ + + + BodeplotAxisDialog + + + + 0 + 0 + 715 + 282 + + + + Axis Setup + + + true + + + + + 9 + 248 + 166 + 25 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 10 + 701 + 233 + + + + + + + + + + 15 + + + + Primary Y axis + + + Qt::AlignCenter + + + + + + + + + Type: + + + + + + + + Disabled + + + + + Magnitude + + + + + Phase + + + + + VSWR + + + + + Impulse Response + + + + + Step Response + + + + + Impedance + + + + + + + + + + Qt::Horizontal + + + + + + + + + Linear + + + Y1group + + + + + + + false + + + Log + + + Y1group + + + + + + + + + Qt::Horizontal + + + + + + + + + Range: + + + + + + + Auto + + + + + + + Maximum: + + + + + + + + + + Minimum: + + + + + + + + + + Divisions: + + + + + + + + + + + + + + Qt::Vertical + + + + + + + + + + 15 + + + + Secondary Y axis + + + Qt::AlignCenter + + + + + + + + + Type: + + + + + + + + Disabled + + + + + Magnitude + + + + + Phase + + + + + VSWR + + + + + Impulse Response + + + + + Step Response + + + + + Impedance + + + + + + + + + + Qt::Horizontal + + + + + + + + + Linear + + + Y2group + + + + + + + false + + + Log + + + Y2group + + + + + + + + + Qt::Horizontal + + + + + + + + + Range: + + + + + + + Auto + + + + + + + Maximum: + + + + + + + + + + Minimum: + + + + + + + + + + Divisions: + + + + + + + + + + + + + + Qt::Vertical + + + + + + + + + + 0 + 0 + + + + + 15 + + + + X axis + + + Qt::AlignCenter + + + + + + + + + Type: + + + + + + + + Frequency + + + + + Time + + + + + Distance + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Range: + + + + + + + Auto + + + + + + + Maximum: + + + + + + + + + + Minimum: + + + + + + + + + + Divisions: + + + + + + + + + + + + + + + + SIUnitEdit + QLineEdit +
CustomWidgets/siunitedit.h
+
+
+ + + + buttonBox + accepted() + BodeplotAxisDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + BodeplotAxisDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + + + + +
diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index 97c4919..65b54d2 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -26,7 +26,7 @@ #include "Traces/tracemodel.h" #include "Traces/tracewidget.h" #include "Traces/tracesmithchart.h" -#include "Traces/tracebodeplot.h" +#include "Traces/tracexyplot.h" #include "Traces/traceimportdialog.h" #include "CustomWidgets/tilewidget.h" #include "CustomWidgets/siunitedit.h" @@ -70,9 +70,9 @@ VNA::VNA(AppWindow *window) auto tracesmith2 = new TraceSmithChart(traceModel); tracesmith2->enableTrace(tS22, true); - auto tracebode1 = new TraceBodePlot(traceModel); + auto tracebode1 = new TraceXYPlot(traceModel); tracebode1->enableTrace(tS12, true); - auto tracebode2 = new TraceBodePlot(traceModel); + auto tracebode2 = new TraceXYPlot(traceModel); tracebode2->enableTrace(tS21, true); connect(&traceModel, &TraceModel::requiredExcitation, this, &VNA::ExcitationRequired); diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index 1b7f3a1..9c8b79e 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -25,7 +25,7 @@ #include "Traces/tracemodel.h" #include "Traces/tracewidget.h" #include "Traces/tracesmithchart.h" -#include "Traces/tracebodeplot.h" +#include "Traces/tracexyplot.h" #include "Traces/traceimportdialog.h" #include "CustomWidgets/tilewidget.h" #include "CustomWidgets/siunitedit.h" @@ -110,7 +110,7 @@ AppWindow::AppWindow(QWidget *parent) connect(ui->actionPreferences, &QAction::triggered, [=](){ Preferences::getInstance().edit(); // settings might have changed, update necessary stuff - TraceBodePlot::updateGraphColors(); + TraceXYPlot::updateGraphColors(); }); setWindowTitle("VNA");