diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index f211c15..8eba3d8 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -88,22 +88,22 @@ jobs: path: Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI PC_Application_Windows: - runs-on: windows-2019 + runs-on: windows-2022 steps: - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: - version: '6.2.0' - arch: 'win64_mingw81' + version: '6.2.4' + arch: 'win64_mingw' - name: Download libusb run: | curl -o libusb.7z -L https://github.com/libusb/libusb/releases/download/v1.0.25/libusb-1.0.25.7z 7z x libusb.7z -r -olibusb - Xcopy /E /I /Y libusb\include ..\Qt\6.2.0\mingw81_64\include + Xcopy /E /I /Y libusb\include %QT_ROOT_DIR%\include Xcopy /E /I /Y libusb\MinGW64\static\libusb-1.0.a Software\PC_Application\LibreVNA-GUI shell: cmd @@ -134,10 +134,9 @@ jobs: cd Software/PC_Application/LibreVNA-GUI/release del *.o *.cpp windeployqt.exe . - copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libwinpthread-1.dll . - copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libgcc_s_seh-1.dll . - copy "..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libstdc++-6.dll" . - copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\Qt6OpenGL.dll . + copy %QT_ROOT_DIR%\bin\libwinpthread-1.dll . + copy %QT_ROOT_DIR%\bin\libgcc_s_seh-1.dll . + copy %QT_ROOT_DIR%\bin\Qt6OpenGL.dll . shell: cmd - name: Upload diff --git a/.github/workflows/Release_tag_stable.yml b/.github/workflows/Release_tag_stable.yml index e111c07..f92179c 100644 --- a/.github/workflows/Release_tag_stable.yml +++ b/.github/workflows/Release_tag_stable.yml @@ -104,22 +104,22 @@ jobs: PC_Application_Windows: needs: PC_Application_Ubuntu - runs-on: windows-2019 + runs-on: windows-2022 steps: - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: - version: '6.2.0' - arch: 'win64_mingw81' + version: '6.2.4' + arch: 'win64_mingw' - name: Download libusb run: | curl -o libusb.7z -L https://github.com/libusb/libusb/releases/download/v1.0.25/libusb-1.0.25.7z 7z x libusb.7z -r -olibusb - Xcopy /E /I /Y libusb\include ..\Qt\6.2.0\mingw81_64\include + Xcopy /E /I /Y libusb\include %QT_ROOT_DIR%\include Xcopy /E /I /Y libusb\MinGW64\static\libusb-1.0.a Software\PC_Application\LibreVNA-GUI shell: cmd @@ -145,10 +145,9 @@ jobs: cd Software/PC_Application/LibreVNA-GUI/release del *.o *.cpp windeployqt.exe . - copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libwinpthread-1.dll . - copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libgcc_s_seh-1.dll . - copy "..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libstdc++-6.dll" . - copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\Qt6OpenGL.dll . + copy %QT_ROOT_DIR%\bin\libwinpthread-1.dll . + copy %QT_ROOT_DIR%\bin\libgcc_s_seh-1.dll . + copy %QT_ROOT_DIR%\bin\Qt6OpenGL.dll . shell: cmd - name: Zip app diff --git a/CHANGELOG.md b/CHANGELOG.md index 594c791..bf6f1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Changelog +## v1.6.4 + +Critical bugfix for the embedded firmware: + +- Fix SPI flash timing, see #315 + +Minor improvement for the GUI: + +- Option to add titles to graphs +- Show trace names even when only enabled on secondary Y axis +- Add x axis variable to the available variables for formulas in "from math" traces + + + ## v1.6.3 + Bugfixes and quality of life improvements - Windows and macOS: add icon to GUI application diff --git a/Hardware/Kicad/PowerSupply.kicad_sch b/Hardware/Kicad/PowerSupply.kicad_sch index 54e932e..8a272ea 100644 --- a/Hardware/Kicad/PowerSupply.kicad_sch +++ b/Hardware/Kicad/PowerSupply.kicad_sch @@ -13075,7 +13075,7 @@ (hide yes) ) ) - (property "MPN" "LM3370SD-4221" + (property "MPN" "LM3370SD-3021" (at 101.6 190.5 0) (effects (font @@ -13970,7 +13970,7 @@ (hide yes) ) ) - (property "MPN" "LM3370SD-4221" + (property "MPN" "LM3370SD-3021" (at 101.6 231.14 0) (effects (font @@ -16667,7 +16667,7 @@ (hide yes) ) ) - (property "MPN" "LM3370SD-4221" + (property "MPN" "LM3370SD-3021" (at 208.28 180.34 0) (effects (font diff --git a/README.md b/README.md index b81ede4..8792869 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# LibreVNA + ![Build workflow status](https://github.com/jankae/LibreVNA/actions/workflows/Build.yml/badge.svg) ![Build workflow status](https://github.com/jankae/LibreVNA/actions/workflows/Test.yml/badge.svg) ![Build workflow status](https://github.com/jankae/LibreVNA/actions/workflows/HIL_Tests.yml/badge.svg) ![LibreVNA](Software/PC_Application/LibreVNA-GUI/resources/banner.png) diff --git a/Software/Integrationtests/tests/TestUpdate.py b/Software/Integrationtests/tests/TestUpdate.py index c53c8e3..8e13f40 100644 --- a/Software/Integrationtests/tests/TestUpdate.py +++ b/Software/Integrationtests/tests/TestUpdate.py @@ -4,8 +4,11 @@ import subprocess class TestUpdate(TestBase): def test_Update(self): + # first update: actually update to the firmware version we want to test self.vna.cmd("DEV:UPDATE ../../combined.vnafw", timeout=60) - + # second update: check that we still have a working firmware update with this version + self.vna.cmd("DEV:UPDATE ../../combined.vnafw", timeout=60) + reported = self.vna.query("DEV:INF:FWREVISION?") major = subprocess.check_output("grep -oP '(?<=FW_MAJOR=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip() minor = subprocess.check_output("grep -oP '(?<=FW_MINOR=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip() diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.cpp index 3af0126..3dde1c1 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.cpp @@ -4,6 +4,7 @@ #include "caldevice.h" #include "usbdevice.h" #include "CustomWidgets/informationbox.h" +#include "preferences.h" #include @@ -234,6 +235,14 @@ void LibreCALDialog::updateCalibrationStartStatus() canStart = validatePortSelection(true); } + if(canStart) { + if(!Preferences::getInstance().Acquisition.allowUseOfUnstableLibreCALTemp && !device->stabilized()) { + canStart = false; + ui->lCalibrationStatus->setText("LibreCAL temperature unstable"); + ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }"); + } + } + ui->start->setEnabled(canStart); if(canStart) { ui->lCalibrationStatus->setText("Ready to start"); @@ -259,6 +268,7 @@ void LibreCALDialog::updateDeviceStatus() ui->lDeviceStatus->setText("Heating up, please wait with calibration"); ui->lDeviceStatus->setStyleSheet("QLabel { color : orange; }"); } + updateCalibrationStartStatus(); } void LibreCALDialog::determineAutoPorts() @@ -420,6 +430,7 @@ void LibreCALDialog::stopSweep() void LibreCALDialog::startCalibration() { disableUI(); + busy = true; ui->progressCal->setValue(0); ui->lCalibrationStatus->setText("Creating calibration kit from coefficients..."); @@ -592,6 +603,7 @@ void LibreCALDialog::startCalibration() disconnect(cal, &Calibration::measurementsUpdated, this, nullptr); setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); enableUI(); + busy = false; break; } setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); @@ -609,6 +621,7 @@ void LibreCALDialog::startCalibration() connect(cal, &Calibration::measurementsAborted, this, [=](){ setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); enableUI(); + busy = false; ui->lCalibrationStatus->setText("Ready to start"); }); diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calibration.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/calibration.cpp index b922c16..18d06c1 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/calibration.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calibration.cpp @@ -301,6 +301,7 @@ QString Calibration::TypeToString(Calibration::Type type) { switch(type) { case Type::None: return "None"; + case Type::OSL: return "OSL"; case Type::SOLT: return "SOLT"; case Type::ThroughNormalization: return "ThroughNormalization"; case Type::TRL: return "TRL"; @@ -708,19 +709,19 @@ Calibration::Point Calibration::createInitializedPoint(double f) { point.frequency = f; // resize vectors point.D.resize(caltype.usedPorts.size(), 0.0); - point.R.resize(caltype.usedPorts.size(), 0.0); + point.R.resize(caltype.usedPorts.size(), 1.0); point.S.resize(caltype.usedPorts.size(), 0.0); point.L.resize(caltype.usedPorts.size()); point.T.resize(caltype.usedPorts.size()); point.I.resize(caltype.usedPorts.size()); fill(point.L.begin(), point.L.end(), vector>(caltype.usedPorts.size(), 0.0)); - fill(point.T.begin(), point.T.end(), vector>(caltype.usedPorts.size(), 0.0)); + fill(point.T.begin(), point.T.end(), vector>(caltype.usedPorts.size(), 1.0)); fill(point.I.begin(), point.I.end(), vector>(caltype.usedPorts.size(), 0.0)); return point; } -Calibration::Point Calibration::computeSOLT(double f) +Calibration::Point Calibration::computeOSL(double f) { Point point = createInitializedPoint(f); @@ -762,6 +763,13 @@ Calibration::Point Calibration::computeSOLT(double f) auto delta = (l_c * l_m * (o_m - s_m) + o_c * o_m * (s_m - l_m) + s_c * s_m * (l_m - o_m)) / denom; point.R[i] = point.D[i] * point.S[i] - delta; } + return point; +} + +Calibration::Point Calibration::computeSOLT(double f) +{ + Point point = computeOSL(f); + // calculate forward match and transmission for(unsigned int i=0;i= caltype.usedPorts.size()) { + // no calibration or it does not contain this port + return false; + } + auto def = std::complex(0.0, 0.0); + for(const auto &p : points) { + if(p.D[index] != def) { + // at least one point does not match the default value -> we have a valid calibration for this + return true; + } + } + // all points still at default value + return false; +} + +bool Calibration::hasReflectionTracking(unsigned int port) +{ + unsigned int index = std::find(caltype.usedPorts.begin(), caltype.usedPorts.end(), port) - caltype.usedPorts.begin(); + if(points.size() == 0 || index >= caltype.usedPorts.size()) { + // no calibration or it does not contain this port + return false; + } + auto def = std::complex(1.0, 0.0); + for(const auto &p : points) { + if(p.R[index] != def) { + // at least one point does not match the default value -> we have a valid calibration for this + return true; + } + } + // all points still at default value + return false; +} + +bool Calibration::hasSourceMatch(unsigned int port) +{ + unsigned int index = std::find(caltype.usedPorts.begin(), caltype.usedPorts.end(), port) - caltype.usedPorts.begin(); + if(points.size() == 0 || index >= caltype.usedPorts.size()) { + // no calibration or it does not contain this port + return false; + } + auto def = std::complex(0.0, 0.0); + for(const auto &p : points) { + if(p.S[index] != def) { + // at least one point does not match the default value -> we have a valid calibration for this + return true; + } + } + // all points still at default value + return false; +} + +bool Calibration::hasReceiverMatch(unsigned int sourcePort, unsigned int receivePort) +{ + unsigned int indexSrc = std::find(caltype.usedPorts.begin(), caltype.usedPorts.end(), sourcePort) - caltype.usedPorts.begin(); + unsigned int indexRcv = std::find(caltype.usedPorts.begin(), caltype.usedPorts.end(), receivePort) - caltype.usedPorts.begin(); + if(points.size() == 0 || indexSrc >= caltype.usedPorts.size() || indexRcv >= caltype.usedPorts.size()) { + // no calibration or it does not contain this port + return false; + } + auto def = std::complex(0.0, 0.0); + for(const auto &p : points) { + if(p.L[indexSrc][indexRcv] != def) { + // at least one point does not match the default value -> we have a valid calibration for this + return true; + } + } + // all points still at default value + return false; +} + +bool Calibration::hasTransmissionTracking(unsigned int sourcePort, unsigned int receivePort) +{ + unsigned int indexSrc = std::find(caltype.usedPorts.begin(), caltype.usedPorts.end(), sourcePort) - caltype.usedPorts.begin(); + unsigned int indexRcv = std::find(caltype.usedPorts.begin(), caltype.usedPorts.end(), receivePort) - caltype.usedPorts.begin(); + if(points.size() == 0 || indexSrc >= caltype.usedPorts.size() || indexRcv >= caltype.usedPorts.size()) { + // no calibration or it does not contain this port + return false; + } + auto def = std::complex(1.0, 0.0); + for(const auto &p : points) { + if(p.T[indexSrc][indexRcv] != def) { + // at least one point does not match the default value -> we have a valid calibration for this + return true; + } + } + // all points still at default value + return false; +} + +bool Calibration::hasIsolation(unsigned int sourcePort, unsigned int receivePort) +{ + unsigned int indexSrc = std::find(caltype.usedPorts.begin(), caltype.usedPorts.end(), sourcePort) - caltype.usedPorts.begin(); + unsigned int indexRcv = std::find(caltype.usedPorts.begin(), caltype.usedPorts.end(), receivePort) - caltype.usedPorts.begin(); + if(points.size() == 0 || indexSrc >= caltype.usedPorts.size() || indexRcv >= caltype.usedPorts.size()) { + // no calibration or it does not contain this port + return false; + } + auto def = std::complex(0.0, 0.0); + for(const auto &p : points) { + if(p.I[indexSrc][indexRcv] != def) { + // at least one point does not match the default value -> we have a valid calibration for this + return true; + } + } + // all points still at default value + return false; +} + bool Calibration::hasUnsavedChanges() const { return unsavedChanges; @@ -1487,11 +1606,12 @@ bool Calibration::toFile(QString filename) { if(filename.isEmpty()) { QString fn = descriptiveCalName(); - filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", fn, "Calibration files (*.cal)", nullptr, Preferences::QFileDialogOptions()); + filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.cal + "/" + fn, "Calibration files (*.cal)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return false; } + Preferences::getInstance().UISettings.Paths.cal = QFileInfo(filename).path(); } if(filename.toLower().endsWith(".cal")) { @@ -1511,11 +1631,12 @@ bool Calibration::toFile(QString filename) bool Calibration::fromFile(QString filename) { if(filename.isEmpty()) { - filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", "", "Calibration files (*.cal)", nullptr, Preferences::QFileDialogOptions()); + filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", Preferences::getInstance().UISettings.Paths.cal, "Calibration files (*.cal)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return false; } + Preferences::getInstance().UISettings.Paths.cal = QFileInfo(filename).path(); } // force correct file ending @@ -1604,7 +1725,7 @@ std::vector Calibration::getTypes() return types; } -bool Calibration::canCompute(Calibration::CalType type, double *startFreq, double *stopFreq, int *points) +bool Calibration::canCompute(Calibration::CalType type, double *startFreq, double *stopFreq, int *points, bool *isLog) { using RequiredMeasurements = struct { CalibrationMeasurement::Base::Type type; @@ -1615,6 +1736,14 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl case Type::None: return true; // Always possible to reset the calibration case Type::SOLT: + // through measurements between all ports + for(unsigned int i=1;i<=type.usedPorts.size();i++) { + for(unsigned int j=i+1;j<=type.usedPorts.size();j++) { + required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j}); + } + } + [[fallthrough]]; + case Type::OSL: // SOL measurements for every port for(auto p : type.usedPorts) { required.push_back({.type = CalibrationMeasurement::Base::Type::Short, .port1 = p, .port2 = 0}); @@ -1627,12 +1756,6 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl required.push_back({.type = CalibrationMeasurement::Base::Type::Load, .port1 = p, .port2 = 0}); } } - // through measurements between all ports - for(unsigned int i=1;i<=type.usedPorts.size();i++) { - for(unsigned int j=i+1;j<=type.usedPorts.size();j++) { - required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j}); - } - } break; case Type::ThroughNormalization: // through measurements between all ports @@ -1674,7 +1797,7 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl foundMeasurements.push_back(meas); } } - return hasFrequencyOverlap(foundMeasurements, startFreq, stopFreq, points); + return hasFrequencyOverlap(foundMeasurements, startFreq, stopFreq, points, isLog); } return false; } @@ -1688,16 +1811,23 @@ bool Calibration::compute(Calibration::CalType type) } double start, stop; int numPoints; - if(!canCompute(type, &start, &stop, &numPoints)) { + bool isLog; + if(!canCompute(type, &start, &stop, &numPoints, &isLog)) { return false; } caltype = type; try { points.clear(); for(int i=0;i m, double *startFreq, double *stopFreq, int *points) +bool Calibration::hasFrequencyOverlap(std::vector m, double *startFreq, double *stopFreq, int *points, bool *isLog) { double minResolution = std::numeric_limits::max(); double minFreq = 0; double maxFreq = std::numeric_limits::max(); + unsigned int logCount = 0; + unsigned int linCount = 0; for(auto meas : m) { if(meas->numPoints() < 2) { return false; @@ -1869,6 +2002,38 @@ bool Calibration::hasFrequencyOverlap(std::vector::max(); + double maxDiff = 0; + double minRatio = std::numeric_limits::max(); + double maxRatio = 0; + for(unsigned int i=1;inumPoints();i++) { + double f_prev = meas->getPointFreq(i-1); + double f_next = meas->getPointFreq(i); + double diff = f_next - f_prev; + double ratio = f_next / f_prev; + if(diff < minDiff) { + minDiff = diff; + } + if(diff > maxDiff) { + maxDiff = diff; + } + if(ratio < minRatio) { + minRatio = ratio; + } + if(ratio > maxRatio) { + maxRatio = ratio; + } + } + double diffVariationNormalized = (maxDiff - minDiff) / maxDiff; + double ratioVariationNormalized = (maxRatio - minRatio) / maxRatio; + if(abs(diffVariationNormalized) < abs(ratioVariationNormalized)) { + // more linear + linCount++; + } else { + // more logarithmic + logCount++; + } } if(startFreq) { *startFreq = minFreq; @@ -1879,6 +2044,9 @@ bool Calibration::hasFrequencyOverlap(std::vector linCount; + } if(maxFreq > minFreq) { return true; } else { diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calibration.h b/Software/PC_Application/LibreVNA-GUI/Calibration/calibration.h index 733f723..9efe3c1 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/calibration.h +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calibration.h @@ -14,11 +14,13 @@ class Calibration : public QObject, public Savable, public SCPINode Q_OBJECT friend class LibreCALDialog; + friend class CalibrationTests; public: Calibration(); enum class Type { None, + OSL, SOLT, ThroughNormalization, TRL, @@ -62,7 +64,7 @@ public: static std::vector getTypes(); // Checks whether all measurements for a specific calibration are available. // If pointer to the frequency/points variables are given, the start/stop frequency and number of points the calibration will have after the calculation is stored there - bool canCompute(CalType type, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr); + bool canCompute(CalType type, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr, bool *isLog = nullptr); // Resets the calibration (deletes all measurements and calculated coefficients) void reset(); // Returns the minimum number of ports for a given calibration type. @@ -98,6 +100,14 @@ public: QString getValidDevice() const; bool validForDevice(QString serial) const; + // query whether error terms coefficients are available. Port count starts at 1 + bool hasDirectivity(unsigned int port); + bool hasReflectionTracking(unsigned int port); + bool hasSourceMatch(unsigned int port); + bool hasReceiverMatch(unsigned int sourcePort, unsigned int receivePort); + bool hasTransmissionTracking(unsigned int sourcePort, unsigned int receivePort); + bool hasIsolation(unsigned int sourcePort, unsigned int receivePort); + public slots: // Call once all datapoints of the current span have been added void measurementsComplete(); @@ -130,7 +140,7 @@ private: void createDefaultMeasurements(DefaultMeasurements dm); void deleteMeasurements(); - bool hasFrequencyOverlap(std::vector m, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr); + static bool hasFrequencyOverlap(std::vector m, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr, bool *isLog = nullptr); // returns all measurements that match the paramaters std::vector findMeasurements(CalibrationMeasurement::Base::Type type, int port1 = 0, int port2 = 0); // returns the first measurement in the list that matches the parameters @@ -151,6 +161,7 @@ private: std::vector points; Point createInitializedPoint(double f); + Point computeOSL(double f); Point computeSOLT(double f); Point computeThroughNormalization(double f); Point computeTRL(double f); diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.cpp index bd4d15a..321d955 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.cpp @@ -627,6 +627,10 @@ void CalibrationMeasurement::Isolation::addPoint(const DeviceDriver::VNAMeasurem QString name = meas.first; unsigned int rcv = name.mid(1, 1).toInt() - 1; unsigned int src = name.mid(2, 1).toInt() - 1; + if(rcv > 8 || src > 8) { + // skip + continue; + } if(rcv >= p.S.size()) { p.S.resize(rcv + 1); } diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.h b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.h index ed1602f..8161e8b 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.h +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.h @@ -41,6 +41,7 @@ public: virtual double maxUsableFreq() = 0; virtual double minFreq() = 0; virtual double maxFreq() = 0; + virtual double getPointFreq(unsigned int p) = 0; virtual unsigned int numPoints() = 0; virtual bool readyForMeasurement() {return false;} virtual bool readyForCalculation() {return false;} @@ -88,6 +89,7 @@ public: virtual double maxUsableFreq() override; virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits::max();} virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;} + virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;} virtual unsigned int numPoints() override {return points.size();} virtual bool readyForMeasurement() override {return standard != nullptr;} virtual bool readyForCalculation() override {return standard && points.size() > 0;} @@ -207,6 +209,7 @@ public: virtual double maxUsableFreq() override; virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits::max();} virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;} + virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;} virtual unsigned int numPoints() override {return points.size();} virtual bool readyForMeasurement() override {return standard != nullptr;} virtual bool readyForCalculation() override {return standard && points.size() > 0;} @@ -281,6 +284,7 @@ public: virtual double maxUsableFreq() override {return maxFreq();} virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits::max();} virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;} + virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;} virtual unsigned int numPoints() override; virtual bool readyForMeasurement() override {return true;} virtual bool readyForCalculation() override {return points.size() > 0;} diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.cpp new file mode 100644 index 0000000..b79f58f --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.cpp @@ -0,0 +1,314 @@ +#include "calibrationviewdialog.h" +#include "ui_calibrationviewdialog.h" + +#include +#include + +const QColor CalibrationViewDialog::colorNoCal = Qt::darkRed; +const QColor CalibrationViewDialog::colorHasCal = Qt::darkGreen; + +CalibrationViewDialog::CalibrationViewDialog(Calibration *cal, unsigned int ports, QWidget *parent) + : QDialog(parent) + , ui(new Ui::CalibrationViewDialog) + , cal(cal) + , ports(ports) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + ui->port->setMaximum(ports); + scene = new QGraphicsScene(); + populateScene(); + ui->view->setScene(scene); + + connect(ui->port, &QSpinBox::valueChanged, this, &CalibrationViewDialog::populateScene); +} + +CalibrationViewDialog::~CalibrationViewDialog() +{ + delete ui; +} + +void CalibrationViewDialog::populateScene() +{ + scene->clear(); + auto colorF = QApplication::palette().text().color(); + auto colorB = QApplication::palette().base().color(); + auto pen = QPen(colorF); + + auto drawDot = [this](float x, float y, float size, QPen pen = QPen(), QBrush brush = QBrush()) { + scene->addEllipse(x - size/2, y - size/2, size, size, pen, brush); + }; + + auto drawText = [this](float x, float y, QString s, QColor color, auto alignH = Qt::AlignCenter, auto alignV = Qt::AlignCenter, float rotation = 0.0f) { + auto text = scene->addText(s); + text->setRotation(rotation); + switch(alignH) { + default: + case Qt::AlignLeft: break; + case Qt::AlignCenter: + case Qt::AlignHCenter: x -= text->boundingRect().bottomRight().x()/2; break; + case Qt::AlignRight: x -= text->boundingRect().bottomRight().x(); break; + } + switch(alignV) { + default: + case Qt::AlignTop: break; + case Qt::AlignCenter: + case Qt::AlignVCenter: y -= text->boundingRect().bottomRight().y()/2; break; + case Qt::AlignBottom: y -= text->boundingRect().bottomRight().y(); break; + } + text->setPos(QPointF(x, y)); + text->setDefaultTextColor(color); + }; + + auto drawPath = [this, drawText](QList vertices, QColor color, QString label = QString(), bool arrow = false) { + // draw lines + for(unsigned int i=1;iaddLine(QLineF(vertices[i-1], vertices[i]), color); + if(i == vertices.size() / 2) { + // this is the middle segment, add label and arrow if required + auto midPoint = (vertices[i-1]+vertices[i])/2; + QVector2D direction = QVector2D(vertices[i] - vertices[i-1]); + direction.normalize(); + auto ortho = QVector2D(-direction.y(), direction.x()); + if(arrow) { + auto poly = QPolygonF({ + QPointF(midPoint + direction.toPointF()*arrowLength/2), + QPointF(midPoint - direction.toPointF()*arrowLength/2 + ortho.toPointF()*arrowWidth/2), + QPointF(midPoint - direction.toPointF()*arrowLength/2 - ortho.toPointF()*arrowWidth/2) + }); + scene->addPolygon(poly, color, color); + } + if(!label.isEmpty()) { + auto pos = midPoint; + if(label.startsWith("_")) { + label.remove(0, 1); + pos -= ortho.toPointF() * labelDistance; + } else { + pos += ortho.toPointF() * labelDistance; + } + auto alignH = abs(direction.x()) > abs(direction.y()) ? Qt::AlignCenter : Qt::AlignLeft; + auto alignV = abs(direction.y()) > abs(direction.x()) ? Qt::AlignCenter : Qt::AlignTop; + drawText(pos.x(), pos.y(), label, color, alignH, alignV); + } + } + } + }; + + auto DUTwidth = 2 * ports * pathSpacing; + auto DUTstartX = ports * pathSpacing + 2*boxSpacing; + + // set the overall scene size + scene->setSceneRect(0, 0, marginLeft + DUTstartX + DUTwidth + marginRight, marginTop + ports * portHeight + marginBottom); + + // create the DUT + // rectangle + scene->addRect(marginLeft + DUTstartX, marginTop, DUTwidth, ports * portHeight, pen); + drawText(marginLeft + DUTstartX + DUTwidth/2, marginTop, "DUT", colorF, Qt::AlignCenter, Qt::AlignTop); + // ports + for(unsigned int i=1;i<=ports;i++) { + // input marker + drawDot(marginLeft + DUTstartX, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, portSize, colorF, colorB); + // output marker + drawDot(marginLeft + DUTstartX, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, portSize, colorF, colorB); + } + // the reflection path + drawPath({ + QPointF(marginLeft + DUTstartX + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + QPointF(marginLeft + DUTstartX + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + }, colorF, "_S"+QString::number(ui->port->value())+QString::number(ui->port->value()), true); + // the forward transmission paths + float xstart = marginLeft + DUTstartX + pathSpacing; + for(unsigned int i=1;i<=ports;i++) { + if((int) i == ui->port->value()) { + // skip, this is the reflection path + continue; + } + drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF); + drawPath({ + QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + QPointF(marginLeft + DUTstartX + portSize/2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + }, colorF, QString((int) i > ui->port->value() ? "_" : "") + "S"+QString::number(i)+QString::number(ui->port->value()), true); + xstart += pathSpacing; + } + // the reverse transmission paths + bool first = true; + for(unsigned int i=1;i<=ports;i++) { + if((int) i == ui->port->value()) { + // skip, this is the reflection path + continue; + } + if(first) { + first = false; + drawDot(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF); + drawPath({ + QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + }, colorF, "", false); + } else { + drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF); + } + drawPath({ + QPointF(marginLeft + DUTstartX + portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + }, colorF, QString((int) i > ui->port->value() ? "" : "_") + "S"+QString::number(ui->port->value())+QString::number(i), true); + xstart += pathSpacing; + } + + // isolation, transmission and receiver match paths + xstart = marginLeft; + for(unsigned int i=1;i<=ports;i++) { + if((int) i == ui->port->value()) { + // skip, this is the reflection path + continue; + } + // isolation + drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF); + drawPath({ + QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + }, colorF); + drawPath({ + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + }, cal->hasIsolation(ui->port->value(), i) ? colorHasCal : colorNoCal, QString((int) i > ui->port->value() ? "_" : "") + "I"+QString::number(i)+QString::number(ui->port->value()), true); + drawPath({ + QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + QPointF(marginLeft, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + }, colorF); + xstart += pathSpacing; + // transmission + drawDot(xstart, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF); + drawPath({ + QPointF(marginLeft + DUTstartX - portSize / 2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + }, colorF); + drawPath({ + QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + QPointF(xstart, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + }, cal->hasTransmissionTracking(ui->port->value(), i) ? colorHasCal : colorNoCal, "_T"+QString::number(i)+QString::number(ui->port->value()), true); + // Reveicer match + drawDot(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF); + drawPath({ + QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset), + QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portForwardYOffset), + QPointF(marginLeft + DUTstartX - portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset), + }, cal->hasReceiverMatch(ui->port->value(), i) ? colorHasCal : colorNoCal, "L"+QString::number(i)+QString::number(ui->port->value()), true); + } + + // reflection error box + drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF); + drawDot(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF); + drawDot(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF); + drawDot(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF); + drawDot(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF); + // unity paths + drawPath({ + QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(marginLeft + DUTstartX - portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + }, colorF, "", true); + drawPath({ + QPointF(marginLeft + DUTstartX - portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + }, colorF); + drawPath({ + QPointF(marginLeft + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + }, colorF); + + // directivity + drawPath({ + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + }, cal->hasDirectivity(ui->port->value()) ? colorHasCal : colorNoCal, "_D"+QString::number(ui->port->value()), true); + + // reflection tracking + drawPath({ + QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + }, cal->hasReflectionTracking(ui->port->value()) ? colorHasCal : colorNoCal, "_R"+QString::number(ui->port->value()), true); + + // source match + drawPath({ + QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset), + QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset), + }, cal->hasSourceMatch(ui->port->value()) ? colorHasCal : colorNoCal, "S"+QString::number(ui->port->value()), true); + + // create the VNA ports + for(unsigned int i=1;i<=ports;i++) { + // stimulus port + if(i == (unsigned int) ui->port->value()) { + drawDot(marginLeft, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, portSize, colorF, colorB); + drawText(marginLeft - portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, "a"+QString::number(i), colorF, Qt::AlignRight, Qt::AlignCenter); + } + // output marker + drawDot(marginLeft, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, portSize, colorF, colorB); + drawText(marginLeft - portSize/2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, "b"+QString::number(i), colorF, Qt::AlignRight, Qt::AlignCenter); + } + + // Fill the measurement correction table + ui->table->clear(); + ui->table->setRowCount(ports*ports); + ui->table->setColumnCount(2); + ui->table->setHorizontalHeaderLabels({"Parameter", "Calibration Status"}); + for(unsigned int i=1;i<=ports;i++) { + for(unsigned int j=1;j<=ports;j++) { + auto row = (i-1)*ports+j-1; + // add parameter + ui->table->setItem(row, 0, new QTableWidgetItem("S"+QString::number(j)+QString::number(i))); + // check the calibration status + QString status = "Uncalibrated"; + if(i == j) { + // check reflection parameters + if(cal->hasSourceMatch(i) && cal->hasDirectivity(i) && cal->hasReflectionTracking(i)) { + // we are calibrated + status = "Calibrated"; + // check if we have enhanced responses + QList enhanced; + for(unsigned int k=1;k<=ports;k++) { + if(k==i) { + continue; + } + if(cal->hasReceiverMatch(i, k)) { + enhanced.append(k); + } + } + if(enhanced.size() == 1) { + status += " with enhanced response from port "+QString::number(enhanced[0]); + } else if(enhanced.size() > 1) { + status += " with enhanced response from ports "; + for(unsigned int k=0;k 0) { + status += ", "; + } + status += QString::number(enhanced[k]); + } + } + } + } else { + // check transmission calibration + if(cal->hasTransmissionTracking(i, j)) { + // we are calibrated + status = "Calibrated"; + // check if we have isolation terms + if(cal->hasIsolation(i, j)) { + status += " with isolation measurement"; + } + } + } + // add calibration status + ui->table->setItem(row, 1, new QTableWidgetItem(status)); + } + } + ui->table->resizeColumnsToContents(); +} diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.h b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.h new file mode 100644 index 0000000..1e4b734 --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.h @@ -0,0 +1,51 @@ +#ifndef CALIBRATIONVIEWDIALOG_H +#define CALIBRATIONVIEWDIALOG_H + +#include + +#include "calibration.h" +#include + +namespace Ui { +class CalibrationViewDialog; +} + +class CalibrationViewDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CalibrationViewDialog(Calibration *cal, unsigned int ports, QWidget *parent = nullptr); + ~CalibrationViewDialog(); + +private slots: + void populateScene(); +private: + static constexpr int marginTop = 10; + static constexpr int marginBottom = 10; + static constexpr int marginLeft = 30; + static constexpr int marginRight = 10; + + static constexpr int portHeight = 170; + static constexpr int portForwardYOffset = -50; + static constexpr int portReverseYOffset = 50; + static constexpr int boxSpacing = portReverseYOffset - portForwardYOffset; + + static constexpr int portSize = 10; + static constexpr int arrowLength = 15; + static constexpr int arrowWidth = 10; + static constexpr int junctionSize = 6; + static constexpr int labelDistance = 6; + + static constexpr int pathSpacing = 40; + + static const QColor colorNoCal; + static const QColor colorHasCal; + + Ui::CalibrationViewDialog *ui; + Calibration *cal; + unsigned int ports; + QGraphicsScene *scene; +}; + +#endif // CALIBRATIONVIEWDIALOG_H diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.ui b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.ui new file mode 100644 index 0000000..89a2e78 --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationviewdialog.ui @@ -0,0 +1,144 @@ + + + CalibrationViewDialog + + + + 0 + 0 + 1106 + 665 + + + + Calibration Error Term Model + + + + + + Qt::Orientation::Horizontal + + + + + + + + + View error term model when stimulus is at port + + + + + + + 1 + + + 8 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + <html><head/><body><p><span style='color: darkgreen;'>Green</span> error terms have been calculated from calibration measurements.</p><p><span style='color: darkred;'>Red</span> error terms are at their default values (either 1 or 0).</p></body></html> + + + + + + + QGraphicsView::DragMode::ScrollHandDrag + + + + + + + <html><head/><body><p>D: Directivity, R: Reflection tracking, S: Source match, L: Receiver match, T: Transmission tracking, I: Isolation</p></body></html> + + + true + + + + + + + + + + + Which measurements are being corrected? + + + true + + + + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + QAbstractItemView::SelectionMode::NoSelection + + + true + + + false + + + + + + + + + + + QDialogButtonBox::StandardButton::Ok + + + + + + + + + buttonBox + accepted() + CalibrationViewDialog + accept() + + + 374 + 509 + + + 374 + 265 + + + + + diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calkitdialog.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/calkitdialog.cpp index e556305..f3d057d 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/calkitdialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calkitdialog.cpp @@ -89,8 +89,9 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) : accept(); }); connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){ - auto filename = QFileDialog::getOpenFileName(this, "Open calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(this, "Open calibration kit coefficients", Preferences::getInstance().UISettings.Paths.calkit, "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions()); if(filename.length() > 0) { + Preferences::getInstance().UISettings.Paths.calkit = QFileInfo(filename).path(); try { kit = Calkit::fromFile(filename); } catch (runtime_error &e) { @@ -103,8 +104,9 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) : }); connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){ - auto filename = QFileDialog::getSaveFileName(this, "Save calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(this, "Save calibration kit coefficients", Preferences::getInstance().UISettings.Paths.calkit, "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions()); if(filename.length() > 0) { + Preferences::getInstance().UISettings.Paths.calkit = QFileInfo(filename).path(); parseEntries(); kit.toFile(filename); } diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/manualcalibrationdialog.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/manualcalibrationdialog.cpp index d554f96..fbdf887 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/manualcalibrationdialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/manualcalibrationdialog.cpp @@ -7,7 +7,7 @@ ManualCalibrationDialog::ManualCalibrationDialog(const TraceModel &model, Calibr ui(new Ui::ManualCalibrationDialog) { ui->setupUi(this); - auto traceSelector = new SparamTraceSelector(model, cal->getCaltype().usedPorts); + auto traceSelector = new SparamTraceSelector(model, cal->getCaltype().usedPorts, true); ui->verticalLayout->insertWidget(1, traceSelector, 1.0); ui->buttonBox->setEnabled(false); connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled); diff --git a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/csvimport.cpp b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/csvimport.cpp index 39a5e56..686d301 100644 --- a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/csvimport.cpp +++ b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/csvimport.cpp @@ -64,8 +64,9 @@ void CSVImport::selectTrace(unsigned int index) void CSVImport::on_browse_clicked() { - auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions()); if (filename.length() > 0) { + Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path(); ui->file->setText(filename); evaluateFile(); } diff --git a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/touchstoneimport.cpp b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/touchstoneimport.cpp index f06d702..179a786 100644 --- a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/touchstoneimport.cpp +++ b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/touchstoneimport.cpp @@ -112,8 +112,9 @@ void TouchstoneImport::setFile(QString filename) void TouchstoneImport::on_browse_clicked() { - auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, Preferences::QFileDialogOptions()); if (filename.length() > 0) { + Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path(); ui->file->setText(filename); } } diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/amplitudecaldialog.cpp b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/amplitudecaldialog.cpp index ae435e5..dba20aa 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/amplitudecaldialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/amplitudecaldialog.cpp @@ -44,11 +44,12 @@ AmplitudeCalDialog::AmplitudeCalDialog(LibreVNADriver *dev, QWidget *parent) : connect(ui->saveFile, &QPushButton::clicked, [=](){ auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal"; auto fileFilter = QString("Amplitude calibration files (*")+fileEnding+")"; - auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", "", fileFilter, nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.vnacaldata, fileFilter, nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.vnacaldata = QFileInfo(filename).path(); if(!filename.endsWith(fileEnding)) { filename.append(fileEnding); } @@ -70,11 +71,12 @@ AmplitudeCalDialog::AmplitudeCalDialog(LibreVNADriver *dev, QWidget *parent) : connect(ui->loadFile, &QPushButton::clicked, [=](){ auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal"; auto fileFilter = QString("Amplitude calibration files (*")+fileEnding+")"; - auto filename = QFileDialog::getOpenFileName(nullptr, "Save calibration data", "", fileFilter, nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.vnacaldata, fileFilter, nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.vnacaldata = QFileInfo(filename).path(); ifstream file; file.open(filename.toStdString()); if(!file.is_open()) { diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/devicepacketlogview.cpp b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/devicepacketlogview.cpp index 8ff3798..6b4bae8 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/devicepacketlogview.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/devicepacketlogview.cpp @@ -29,11 +29,12 @@ DevicePacketLogView::DevicePacketLogView(QWidget *parent) : updateTree(); }); connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){ - QString filename = QFileDialog::getSaveFileName(nullptr, "Load LibreVNA log data", "", "LibreVNA log files (*.vnalog)", nullptr, Preferences::QFileDialogOptions()); + QString filename = QFileDialog::getSaveFileName(nullptr, "Save LibreVNA log data", Preferences::getInstance().UISettings.Paths.packetlog, "LibreVNA log files (*.vnalog)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path(); if(!filename.endsWith(".vnalog")) { filename.append(".vnalog"); } @@ -43,11 +44,12 @@ DevicePacketLogView::DevicePacketLogView(QWidget *parent) : file.close(); }); connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){ - QString filename = QFileDialog::getOpenFileName(nullptr, "Load LibreVNA log data", "", "LibreVNA log files (*.vnalog)", nullptr, Preferences::QFileDialogOptions()); + QString filename = QFileDialog::getOpenFileName(nullptr, "Load LibreVNA log data", Preferences::getInstance().UISettings.Paths.packetlog, "LibreVNA log files (*.vnalog)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path(); ifstream file; file.open(filename.toStdString()); if(!file.is_open()) { diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/firmwareupdatedialog.cpp b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/firmwareupdatedialog.cpp index 85c6c46..654c723 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/firmwareupdatedialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/firmwareupdatedialog.cpp @@ -52,8 +52,9 @@ bool FirmwareUpdateDialog::FirmwareUpdate(LibreVNADriver *dev, QString file) void FirmwareUpdateDialog::on_bFile_clicked() { ui->bStart->setEnabled(false); - auto filename = QFileDialog::getOpenFileName(nullptr, "Open firmware file", "", "Firmware file (*.vnafw)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(nullptr, "Open firmware file", Preferences::getInstance().UISettings.Paths.firmware, "Firmware file (*.vnafw)", nullptr, Preferences::QFileDialogOptions()); if (filename.length() > 0) { + Preferences::getInstance().UISettings.Paths.firmware = QFileInfo(filename).path(); ui->lFile->setText(filename); reloadFile(); } diff --git a/Software/PC_Application/LibreVNA-GUI/Device/devicelog.cpp b/Software/PC_Application/LibreVNA-GUI/Device/devicelog.cpp index 0f83f92..edc94d3 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/devicelog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Device/devicelog.cpp @@ -68,8 +68,9 @@ void DeviceLog::clear() void DeviceLog::on_bToFile_clicked() { - auto filename = QFileDialog::getSaveFileName(this, "Select file for device log", "", "", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(this, "Select file for device log", Preferences::getInstance().UISettings.Paths.packetlog, "", nullptr, Preferences::QFileDialogOptions()); if(filename.length() > 0) { + Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path(); // create file ofstream file; file.open(filename.toStdString()); diff --git a/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro index 8f52757..151281b 100644 --- a/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro @@ -6,6 +6,7 @@ HEADERS += \ Calibration/LibreCAL/usbdevice.h \ Calibration/calibration.h \ Calibration/calibrationmeasurement.h \ + Calibration/calibrationviewdialog.h \ Calibration/calkit.h \ Calibration/calkitdialog.h \ Calibration/calstandard.h \ @@ -172,6 +173,7 @@ SOURCES += \ Calibration/LibreCAL/usbdevice.cpp \ Calibration/calibration.cpp \ Calibration/calibrationmeasurement.cpp \ + Calibration/calibrationviewdialog.cpp \ Calibration/calkit.cpp \ Calibration/calkitdialog.cpp \ Calibration/calstandard.cpp \ @@ -342,6 +344,7 @@ FORMS += \ Calibration/LibreCAL/factoryUpdateDialog.ui \ Calibration/LibreCAL/librecaldialog.ui \ Calibration/calibrationdialogui.ui \ + Calibration/calibrationviewdialog.ui \ Calibration/calkitdialog.ui \ Calibration/manualcalibrationdialog.ui \ CustomWidgets/csvimport.ui \ @@ -419,5 +422,5 @@ QMAKE_CXXFLAGS += -Wno-deprecated -Wno-deprecated-declarations -Wno-deprecated-c CONFIG += c++17 REVISION = $$system(git rev-parse HEAD) DEFINES += GITHASH=\\"\"$$REVISION\\"\" -DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=3 FW_SUFFIX="" +DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=4 FW_SUFFIX="" DEFINES -= _UNICODE UNICODE diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.cpp index e3b0e7b..53b180f 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.cpp @@ -130,6 +130,7 @@ QString Marker::formatToString(Marker::Format f) case Format::Inductance: return "Inductance"; case Format::QualityFactor: return "Quality Factor"; case Format::GroupDelay: return "Group Delay"; + case Format::NumberOfPeaks: return "Number of peaks"; case Format::TOI: return "Third order intercept"; case Format::AvgTone: return "Average Tone Level"; case Format::AvgModulationProduct: return "Average Modulation Product Level"; @@ -194,8 +195,6 @@ std::vector Marker::applicableFormats() case Type::Delta: case Type::Maximum: case Type::Minimum: - case Type::PeakTable: - case Type::NegativePeakTable: if(Trace::isSAParameter(parentTrace->liveParameter())) { ret.push_back(Format::dBm); ret.push_back(Format::dBuV); @@ -218,6 +217,10 @@ std::vector Marker::applicableFormats() } } break; + case Type::PeakTable: + case Type::NegativePeakTable: + ret.push_back(Format::NumberOfPeaks); + break; default: break; } @@ -228,8 +231,6 @@ std::vector Marker::applicableFormats() case Type::Delta: case Type::Maximum: case Type::Minimum: - case Type::PeakTable: - case Type::NegativePeakTable: if(Trace::isSAParameter(parentTrace->liveParameter())) { ret.push_back(Format::dBm); ret.push_back(Format::dBuV); @@ -253,6 +254,10 @@ std::vector Marker::applicableFormats() } } break; + case Type::PeakTable: + case Type::NegativePeakTable: + ret.push_back(Format::NumberOfPeaks); + break; case Type::Bandpass: ret.push_back(Format::CenterBandwidth); ret.push_back(Format::InsertionLoss); @@ -451,7 +456,11 @@ QString Marker::readableData(Format f) switch(type) { case Type::PeakTable: case Type::NegativePeakTable: - return "Found " + QString::number(helperMarkers.size()) + " peaks"; + switch(f) { + case Format::NumberOfPeaks: return "Found " + QString::number(helperMarkers.size()) + " peak" + (helperMarkers.size() == 1 ? "" : "s"); + default: return "Invalid"; + } + break; case Type::Delta: { if(!delta) { return "Invalid delta marker"; @@ -588,6 +597,7 @@ QString Marker::readableData(Format f) case Format::maxDeltaPos: return "max. Δ+:"+Unit::ToString(maxDeltaPos, "dB", " ", 4); break; + case Format::NumberOfPeaks: case Format::Last: return "Invalid"; } @@ -892,10 +902,6 @@ void Marker::deltaDeleted() void Marker::updateContextmenu() { - if(parent) { - // do nothing, using contextmenu from parent anyway - return; - } // check if the contextmenu or one of its submenus is currently open auto *activeWidget = QApplication::activePopupWidget(); while (activeWidget) { @@ -910,19 +916,22 @@ void Marker::updateContextmenu() contextmenu.clear(); contextmenu.addSection("Marker"); - auto typemenu = contextmenu.addMenu("Type"); - auto typegroup = new QActionGroup(&contextmenu); - for(auto t : getSupportedTypes()) { - auto setTypeAction = new QAction(typeToString(t), typemenu); - setTypeAction->setCheckable(true); - if(t == type) { - setTypeAction->setChecked(true); + if(!parent) { + // type can only be changed for top level markers + auto typemenu = contextmenu.addMenu("Type"); + auto typegroup = new QActionGroup(&contextmenu); + for(auto t : getSupportedTypes()) { + auto setTypeAction = new QAction(typeToString(t), typemenu); + setTypeAction->setCheckable(true); + if(t == type) { + setTypeAction->setChecked(true); + } + connect(setTypeAction, &QAction::triggered, [=](){ + setType(t); + }); + typegroup->addAction(setTypeAction); + typemenu->addAction(setTypeAction); } - connect(setTypeAction, &QAction::triggered, [=](){ - setType(t); - }); - typegroup->addAction(setTypeAction); - typemenu->addAction(setTypeAction); } auto table = contextmenu.addMenu("Data Format in Table"); @@ -965,48 +974,51 @@ void Marker::updateContextmenu() } } - contextmenu.addSeparator(); - - bool needsSeparator = false; - if((applicableGroups.size() > 0 && group == nullptr) || applicableGroups.size() > 1) { - // there are other groups available than the one the marker might already be assigned to - auto addGroupMenu = new QMenu("Add to linked group"); - auto groupGroup = new QActionGroup(addGroupMenu); - for(auto g : model->getGroups()) { - auto addGroupAction = new QAction(QString::number(g->getNumber())); - groupGroup->addAction(addGroupAction); - addGroupAction->setCheckable(true); - if(g == group) { - // already assigned to this group - addGroupAction->setChecked(true); - } - connect(addGroupAction, &QAction::triggered, [=](bool checked){ - if(checked) { - g->add(this); - } - }); - addGroupMenu->addAction(addGroupAction); - } - contextmenu.addMenu(addGroupMenu); - needsSeparator = true; - } - if(group != nullptr) { - // "remove from group" available - auto removeGroup = new QAction("Remove from linked group", &contextmenu); - connect(removeGroup, &QAction::triggered, [=](){ - group->remove(this); - }); - contextmenu.addAction(removeGroup); - needsSeparator = true; - } - if(needsSeparator) { + if(!parent) { + // grouping and deleting is only possible for top level markers contextmenu.addSeparator(); + + bool needsSeparator = false; + if((applicableGroups.size() > 0 && group == nullptr) || applicableGroups.size() > 1) { + // there are other groups available than the one the marker might already be assigned to + auto addGroupMenu = new QMenu("Add to linked group"); + auto groupGroup = new QActionGroup(addGroupMenu); + for(auto g : model->getGroups()) { + auto addGroupAction = new QAction(QString::number(g->getNumber())); + groupGroup->addAction(addGroupAction); + addGroupAction->setCheckable(true); + if(g == group) { + // already assigned to this group + addGroupAction->setChecked(true); + } + connect(addGroupAction, &QAction::triggered, [=](bool checked){ + if(checked) { + g->add(this); + } + }); + addGroupMenu->addAction(addGroupAction); + } + contextmenu.addMenu(addGroupMenu); + needsSeparator = true; + } + if(group != nullptr) { + // "remove from group" available + auto removeGroup = new QAction("Remove from linked group", &contextmenu); + connect(removeGroup, &QAction::triggered, [=](){ + group->remove(this); + }); + contextmenu.addAction(removeGroup); + needsSeparator = true; + } + if(needsSeparator) { + contextmenu.addSeparator(); + } + + + auto deleteAction = new QAction("Delete", &contextmenu); + connect(deleteAction, &QAction::triggered, this, &Marker::deleteLater); + contextmenu.addAction(deleteAction); } - - - auto deleteAction = new QAction("Delete", &contextmenu); - connect(deleteAction, &QAction::triggered, this, &Marker::deleteLater); - contextmenu.addAction(deleteAction); } void Marker::traceTypeChanged() @@ -1231,6 +1243,7 @@ void Marker::setType(Marker::Type t) helper->suffix = h.suffix; helper->assignTrace(parentTrace); helper->setType(h.type); + helper->setVisible(visible); helperMarkers.push_back(helper); } if(type == Type::Flatness) { @@ -1467,6 +1480,35 @@ void Marker::setNumber(int value) } } +QWidget *Marker::getTraceEditor(QAbstractItemDelegate *delegate) +{ + auto c = new QComboBox; + for(auto t : model->getModel().getTraces()) { + c->addItem(t->name()); + if(parentTrace == t) { + // select this item + c->setCurrentIndex(c->count() - 1); + } + } + connect(c, qOverload(&QComboBox::currentIndexChanged), [=](int) { + emit delegate->commitData(c); + }); + return c; +} + +void Marker::updateTraceFromEditor(QWidget *w) +{ + QComboBox *c = (QComboBox*) w; + for(auto t : model->getModel().getTraces()) { + if(c->currentText() == t->name()) { + if(parentTrace != t) { + assignTrace(t); + } + } + } + update(); +} + QWidget *Marker::getTypeEditor(QAbstractItemDelegate *delegate) { auto c = new QComboBox; @@ -1771,11 +1813,7 @@ void Marker::setVisible(bool visible) } QMenu *Marker::getContextMenu() { - if(parent) { - return parent->getContextMenu(); - } else { - return &contextmenu; - } + return &contextmenu; } void Marker::update() @@ -1800,19 +1838,28 @@ void Marker::update() break; case Type::PeakTable: case Type::NegativePeakTable: { - deleteHelperMarkers(); auto peaks = parentTrace->findPeakFrequencies(100, peakThreshold, 3.0, xmin, xmax, type == Type::NegativePeakTable); - char suffix = 'a'; - for(auto p : peaks) { - auto helper = new Marker(model, number, this); - helper->suffix = suffix; - helper->assignTrace(parentTrace); - helper->setPosition(p); - helper->formatTable = formatTable; - helper->formatGraph = formatGraph; - helper->updateContextmenu(); - suffix++; - helperMarkers.push_back(helper); + for(unsigned int i=0;isuffix = QChar('a' + i); + helper->assignTrace(parentTrace); + helper->updateContextmenu(); + helper->setVisible(visible); + helperMarkers.push_back(helper); + } + // update the position of the helper marker + helperMarkers[i]->setPosition(peaks[i]); + } + if(helperMarkers.size() > peaks.size()) { + // need to remove some helper markers + emit beginRemoveHelperMarkers(this); + for(unsigned int i = peaks.size(); i< helperMarkers.size();i++) { + delete helperMarkers[i]; + } + helperMarkers.resize(peaks.size()); + emit endRemoveHelperMarkers(this); } } break; diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.h b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.h index f2fde28..c2d586c 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.h @@ -35,6 +35,8 @@ public: Inductance, QualityFactor, GroupDelay, + // Peak table + NumberOfPeaks, // Noise marker parameters Noise, PhaseNoise, @@ -109,8 +111,10 @@ public: Last, }; Type getType() const; + QWidget *getTraceEditor(QAbstractItemDelegate *delegate = nullptr); + void updateTraceFromEditor(QWidget *w); QWidget *getTypeEditor(QAbstractItemDelegate *delegate = nullptr); - void updateTypeFromEditor(QWidget *c); + void updateTypeFromEditor(QWidget *w); SIUnitEdit* getSettingsEditor(); QWidget *getRestrictEditor(); void adjustSettings(double value); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/markermodel.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/markermodel.cpp index 0cb4c2e..efc43d7 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/markermodel.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/markermodel.cpp @@ -132,11 +132,11 @@ void MarkerModel::markerDataChanged(Marker *m) // only update the other columns, do not override editor data emit dataChanged(index(row, ColIndexData), index(row, ColIndexData)); } else { - emit dataChanged(index(row, ColIndexNumber), index(row, ColIndexData)); + emit dataChanged(index(row, ColIndexSettings), index(row, ColIndexData)); // also update any potential helper markers for(unsigned int i=0;igetHelperMarkers().size();i++) { auto modelIndex = createIndex(i, 0, m); - emit dataChanged(index(i, ColIndexNumber, modelIndex), index(i, ColIndexData, modelIndex)); + emit dataChanged(index(i, ColIndexSettings, modelIndex), index(i, ColIndexData, modelIndex)); } } } @@ -436,41 +436,17 @@ QSize MarkerTraceDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIn QWidget *MarkerTraceDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const { - auto model = (MarkerModel*) index.model(); - auto c = new QComboBox(parent); - c->setMaximumHeight(rowHeight); - connect(c, qOverload(&QComboBox::currentIndexChanged), [c](int) { - c->clearFocus(); - }); - auto traces = model->getModel().getTraces(); - for(auto t : traces) { - MarkerWidgetTraceInfo info; - info.trace = t; - c->addItem(t->name(), QVariant::fromValue(info)); - } - return c; + auto marker = static_cast(index.model())->markerFromIndex(index); + auto editor = marker->getTraceEditor(const_cast(this)); + editor->setMaximumHeight(rowHeight); + editor->setParent(parent); + return editor; } -void MarkerTraceDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +void MarkerTraceDelegate::setModelData(QWidget *editor, QAbstractItemModel *, const QModelIndex &index) const { auto marker = static_cast(index.model())->markerFromIndex(index); - auto c = (QComboBox*) editor; - MarkerWidgetTraceInfo markerInfo; - markerInfo.trace = marker->trace(); - for(int i=0;icount();i++) { - auto info = qvariant_cast(c->itemData(i)); - if(info == markerInfo) { - c->setCurrentIndex(i); - return; - } - } -} - -void MarkerTraceDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const -{ - auto markerModel = (MarkerModel*) model; - auto c = (QComboBox*) editor; - markerModel->setData(index, c->itemData(c->currentIndex())); + marker->updateTraceFromEditor(editor); } QSize MarkerSettingsDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/markermodel.h b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/markermodel.h index 3f2d683..aa7ec00 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/markermodel.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/markermodel.h @@ -14,7 +14,6 @@ class MarkerTraceDelegate : public QStyledItemDelegate Q_OBJECT QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override; QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; - void setEditorData(QWidget * editor, const QModelIndex & index) const override; void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; }; diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Math/expression.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/Math/expression.cpp index 806a674..50f5e2b 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Math/expression.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Math/expression.cpp @@ -15,6 +15,7 @@ Math::Expression::Expression() { parser = new ParserX(pckCOMMON | pckUNIT | pckCOMPLEX); parser->DefineVar("x", Variable(&x)); + dataType = DataType::Invalid; expressionChanged(); } @@ -37,10 +38,8 @@ void Math::Expression::edit() { auto d = new QDialog(); auto ui = new Ui::ExpressionDialog; + d->setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(d); - connect(d, &QDialog::finished, [=](){ - delete ui; - }); ui->expEdit->setText(exp); connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){ exp = ui->expEdit->text(); @@ -93,7 +92,7 @@ void Math::Expression::inputSamplesChanged(unsigned int begin, unsigned int end) data.resize(in.size()); // sanity check input values if(end > 0 && end > in.size()) { - end = in.size() - 1; + end = in.size(); } if(end <= begin) { dataMutex.unlock(); @@ -119,6 +118,14 @@ void Math::Expression::inputSamplesChanged(unsigned int begin, unsigned int end) emit outputSamplesChanged(begin, end); } +void Math::Expression::inputTypeChanged(DataType type) +{ + // call base class slot + TraceMath::inputTypeChanged(type); + // we need to evaluate the expression again to create the correct variables + expressionChanged(); +} + void Math::Expression::expressionChanged() { if(exp.isEmpty()) { diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Math/expression.h b/Software/PC_Application/LibreVNA-GUI/Traces/Math/expression.h index 39e8b2e..2c17698 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Math/expression.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Math/expression.h @@ -24,6 +24,7 @@ public: public slots: void inputSamplesChanged(unsigned int begin, unsigned int end) override; + virtual void inputTypeChanged(DataType type) override; private slots: void expressionChanged(); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.h b/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.h index e99b4b1..e8a238e 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.h @@ -130,7 +130,7 @@ public slots: // some values of the input data have changed, begin/end determine which sample(s) has changed virtual void inputSamplesChanged(unsigned int begin, unsigned int end){Q_UNUSED(begin) Q_UNUSED(end)} - void inputTypeChanged(DataType type); + virtual void inputTypeChanged(DataType type); signals: // emit this whenever a sample changed (alternatively, if all samples are about to change, emit outputDataChanged after they have changed) diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp index fd6a954..d686387 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp @@ -403,11 +403,12 @@ void EyeDiagramPlot::updateContextMenu() auto image = new QAction("Save image...", contextmenu); contextmenu->addAction(image); connect(image, &QAction::triggered, this, [=]() { - auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path(); if(filename.endsWith(".png")) { filename.chop(4); } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp index 74c4235..8cba09c 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp @@ -318,6 +318,10 @@ void Trace::fillFromDatapoints(std::map traceSet, const std::v { // remove all previous points for(auto m : traceSet) { + if(!m.second) { + // no trace, skip + continue; + } if(!deembedded) { m.second->clear(); } else { @@ -332,6 +336,10 @@ void Trace::fillFromDatapoints(std::map traceSet, const std::v td.y = m.second; QString measurement = m.first; if(traceSet.count(measurement)) { + if(!traceSet[measurement]) { + // no trace, skip + continue; + } if(!deembedded) { traceSet[measurement]->addData(td, DataType::Frequency); } else { @@ -1065,16 +1073,24 @@ std::vector Trace::assembleDatapoints(std::map ret; // Sanity check traces - unsigned int samples = traceSet.begin()->second->size(); - auto impedance = traceSet.begin()->second->getReferenceImpedance(); + unsigned int samples = 0; + auto impedance = 0; vector freqs; for(auto m : traceSet) { const Trace *t = m.second; - if(t->size() != samples) { + if(!t) { + // trace not valid, skip + continue; + } + if(samples == 0) { + samples = t->size(); + } else if(t->size() != samples) { qWarning() << "Selected traces do not have the same size"; return ret; } - if(t->getReferenceImpedance() != impedance) { + if(impedance == 0) { + impedance = t->getReferenceImpedance(); + } else if(t->getReferenceImpedance() != impedance) { qWarning() << "Selected traces do not have the same reference impedance"; return ret; } @@ -1098,13 +1114,22 @@ std::vector Trace::assembleDatapoints(std::mapsample(i).y; + if(t) { + d.measurements[measurement] = t->sample(i).y; + } else { + d.measurements[measurement] = 0.0; + } } d.pointNum = i; d.frequency = freqs[i]; diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp index dc59c48..af0e2a4 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp @@ -72,11 +72,12 @@ void TraceCSVExport::on_buttonBox_accepted() return; } - auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", "", "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.data, "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path(); if(!filename.endsWith(".csv")) { filename.append(".csv"); } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.cpp index 6c3e8d0..931032d 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.cpp @@ -551,6 +551,10 @@ Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable) } auto markers = t.first->getMarkers(); for(Marker* m : markers) { + if(!m->isVisible()) { + // can not interact with invisible markers, pretend that there is nothing here + continue; + } if(!m->isMovable() && onlyMovable) { continue; } @@ -563,14 +567,7 @@ Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable) unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); if(distance < closestDistance) { closestDistance = distance; - if(m->getParent()) { - closestMarker = m->getParent(); - if(closestMarker->getType() == Marker::Type::Flatness) { - closestMarker = m; - } - } else { - closestMarker = m; - } + closestMarker = m; } } } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracepolar.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracepolar.cpp index 8de5b17..15b87c7 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracepolar.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracepolar.cpp @@ -253,11 +253,12 @@ void TracePolar::updateContextMenu() auto image = new QAction("Save image...", contextmenu); contextmenu->addAction(image); connect(image, &QAction::triggered, [=]() { - auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path(); if(filename.endsWith(".png")) { filename.chop(4); } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.cpp index f091c22..8c773a2 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.cpp @@ -18,22 +18,42 @@ TraceTouchstoneExport::TraceTouchstoneExport(TraceModel &model, QWidget *parent) ui->selector->setPartialSelectionAllowed(true); connect(ui->selector, qOverload<>(&TraceSetSelector::selectionChanged), this, &TraceTouchstoneExport::selectionChanged); connect(ui->sbPorts, &QSpinBox::valueChanged, this, &TraceTouchstoneExport::setPortNum); - // figure out how many ports the user most likely needs - unsigned int p; - for(p=4;p>=1;p--) { - // do we have a trace name which could indicate such a number of ports? - for(unsigned int i=1;i<=p;i++) { - auto n1 = "S"+QString::number(p)+QString::number(i); - auto n2 = "S"+QString::number(i)+QString::number(p); + // restore the last used settings + auto& pref = Preferences::getInstance(); + auto ports = pref.UISettings.TouchstoneExport.ports; + setPortNum(ports); + ui->cFormat->setCurrentIndex(pref.UISettings.TouchstoneExport.formatIndex); + ui->cUnit->setCurrentIndex(pref.UISettings.TouchstoneExport.unitIndex); + + // attempt to set the traces that were exported last + QStringList traces = pref.UISettings.TouchstoneExport.exportedTraceNames.split(","); + if(traces.size() == ports * ports) { + // got the correct number of traces + for(unsigned int i=0;iname().contains(n1) || t->name().contains(n2)) { - goto traceFound; + if(t->name() == traces[i]) { + setTrace(i / ports + 1, i % ports + 1, t); + break; } } } } -traceFound: - setPortNum(p); + +// unsigned int p; +// for(p=4;p>=1;p--) { +// // do we have a trace name which could indicate such a number of ports? +// for(unsigned int i=1;i<=p;i++) { +// auto n1 = "S"+QString::number(p)+QString::number(i); +// auto n2 = "S"+QString::number(i)+QString::number(p); +// for(auto t : model.getTraces()) { +// if(t->name().contains(n1) || t->name().contains(n2)) { +// goto traceFound; +// } +// } +// } +// } +// traceFound: +// setPortNum(p); } TraceTouchstoneExport::~TraceTouchstoneExport() @@ -41,7 +61,7 @@ TraceTouchstoneExport::~TraceTouchstoneExport() delete ui; } -bool TraceTouchstoneExport::setTrace(int portFrom, int portTo, Trace *t) +bool TraceTouchstoneExport::setTrace(int portTo, int portFrom, Trace *t) { return ui->selector->setTrace(portTo, portFrom, t); } @@ -66,7 +86,7 @@ bool TraceTouchstoneExport::setPortNum(unsigned int ports) for(auto t : traces) { if(t->name().contains(name)) { // this could be the correct trace - setTrace(j, i, t); + setTrace(i, j, t); break; } } @@ -77,17 +97,18 @@ bool TraceTouchstoneExport::setPortNum(unsigned int ports) void TraceTouchstoneExport::on_buttonBox_accepted() { - auto ports = ui->sbPorts->value(); + unsigned int ports = ui->sbPorts->value(); QString extension = ".s"+QString::number(ports)+"p"; - auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", "", "Touchstone files (*"+extension+")", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", Preferences::getInstance().UISettings.Paths.data, "Touchstone files (*"+extension+")", nullptr, Preferences::QFileDialogOptions()); if(filename.length() > 0) { + Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path(); auto t = Touchstone(ports); t.setReferenceImpedance(ui->selector->getReferenceImpedance()); // add trace points to touchstone for(unsigned int s=0;sselector->getPoints();s++) { Touchstone::Datapoint tData; - for(int i=1;i<=ports;i++) { - for(int j=1;j<=ports;j++) { + for(unsigned int i=1;i<=ports;i++) { + for(unsigned int j=1;j<=ports;j++) { auto t = ui->selector->getTrace(i, j); if(!t) { // missing trace, set to 0 @@ -117,6 +138,25 @@ void TraceTouchstoneExport::on_buttonBox_accepted() } t.toFile(filename, unit, format); + + // update preferences for next call + auto& pref = Preferences::getInstance(); + pref.UISettings.TouchstoneExport.ports = ui->sbPorts->value(); + pref.UISettings.TouchstoneExport.formatIndex = ui->cFormat->currentIndex(); + pref.UISettings.TouchstoneExport.unitIndex = ui->cUnit->currentIndex(); + QString traceNames = ""; + for(unsigned int i=0;iselector->getTrace(i / ports + 1, i % ports + 1); + if(t) { + traceNames += t->name(); + } + if(i != (ports*ports-1)) { + // add separator for all but the last trace name + traceNames += ","; + } + } + pref.UISettings.TouchstoneExport.exportedTraceNames = traceNames; + delete this; } } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.h b/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.h index c984a39..c520b85 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.h @@ -18,7 +18,7 @@ class TraceTouchstoneExport : public QDialog public: explicit TraceTouchstoneExport(TraceModel &model, QWidget *parent = nullptr); ~TraceTouchstoneExport(); - bool setTrace(int portFrom, int portTo, Trace *t); + bool setTrace(int portTo, int portFrom, Trace *t); bool setPortNum(unsigned int ports); private slots: diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracewaterfall.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracewaterfall.cpp index 2177cdc..043a6e3 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracewaterfall.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracewaterfall.cpp @@ -213,11 +213,12 @@ void TraceWaterfall::updateContextMenu() auto image = new QAction("Save image...", contextmenu); contextmenu->addAction(image); connect(image, &QAction::triggered, [=]() { - auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path(); if(filename.endsWith(".png")) { filename.chop(4); } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp index 3640178..5ddf491 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp @@ -222,8 +222,9 @@ void TraceWidget::importDialog() } supported.chop(1); supported += ")"; - auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", supported, nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, supported, nullptr, Preferences::QFileDialogOptions()); if (!filename.isEmpty()) { + Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path(); importFile(filename); } } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp index 4b8f2c3..3fcd8ea 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp @@ -350,11 +350,12 @@ void TraceXYPlot::updateContextMenu() auto image = new QAction("Save image...", contextmenu); contextmenu->addAction(image); connect(image, &QAction::triggered, [=]() { - auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path(); if(filename.endsWith(".png")) { filename.chop(4); } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.cpp index 1a59b97..ccde4f2 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.cpp @@ -242,11 +242,12 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : removeLine(index); }); connect(ui->exportLines, &QPushButton::clicked, this, [=](){ - QString filename = QFileDialog::getSaveFileName(nullptr, "Save limit lines", "", "Limit files (*.limits)", nullptr, Preferences::QFileDialogOptions()); + QString filename = QFileDialog::getSaveFileName(nullptr, "Save limit lines", Preferences::getInstance().UISettings.Paths.limitLines, "Limit files (*.limits)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.limitLines = QFileInfo(filename).path(); if(!filename.endsWith(".limits")) { filename.append(".limits"); } @@ -265,7 +266,7 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : }); connect(ui->importLines, &QPushButton::clicked, [=](){ - QString filename = QFileDialog::getOpenFileName(nullptr, "Load limit lines", "", "Limit files (*.limits)", nullptr, Preferences::QFileDialogOptions()); + QString filename = QFileDialog::getOpenFileName(nullptr, "Load limit lines", Preferences::getInstance().UISettings.Paths.limitLines, "Limit files (*.limits)", nullptr, Preferences::QFileDialogOptions()); ifstream file; file.open(filename.toStdString()); if(!file.is_open()) { diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/matchingnetwork.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/matchingnetwork.cpp index 52823fc..039e1f6 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/matchingnetwork.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/matchingnetwork.cpp @@ -767,8 +767,9 @@ void MatchingComponent::mouseDoubleClickEvent(QMouseEvent *e) Q_UNUSED(e); if(type == Type::DefinedThrough || type == Type::DefinedShunt) { // select new touchstone file - auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s2p)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, "Touchstone files (*.s2p)", nullptr, Preferences::QFileDialogOptions()); if (!filename.isEmpty()) { + Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path(); try { *touchstone = Touchstone::fromFile(filename.toStdString()); } catch(const std::exception& e) { diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp index 1ed594e..75fb076 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp @@ -1,4 +1,4 @@ -#include "vna.h" +#include "vna.h" #include "unit.h" #include "CustomWidgets/toggleswitch.h" @@ -18,6 +18,7 @@ #include "CustomWidgets/informationbox.h" #include "Deembedding/manualdeembeddingdialog.h" #include "Calibration/manualcalibrationdialog.h" +#include "Calibration/calibrationviewdialog.h" #include "Calibration/LibreCAL/librecaldialog.h" #include "Util/util.h" #include "Tools/parameters.h" @@ -129,6 +130,16 @@ VNA::VNA(AppWindow *window, QString name) calMenu->addSeparator(); + auto calViewTerms = calMenu->addAction("View error term model"); + connect(calViewTerms, &QAction::triggered, [=](){ + auto dialog = new CalibrationViewDialog(&cal, DeviceDriver::getInfo(window->getDevice()).Limits.VNA.ports); + if(AppWindow::showGUI()) { + dialog->show(); + } + }); + + calMenu->addSeparator(); + auto calImportTerms = calMenu->addAction("Import error terms as traces"); calImportTerms->setEnabled(false); connect(calImportTerms, &QAction::triggered, [=](){ diff --git a/Software/PC_Application/LibreVNA-GUI/appwindow.cpp b/Software/PC_Application/LibreVNA-GUI/appwindow.cpp index 57e6afa..b11ae40 100644 --- a/Software/PC_Application/LibreVNA-GUI/appwindow.cpp +++ b/Software/PC_Application/LibreVNA-GUI/appwindow.cpp @@ -229,19 +229,21 @@ void AppWindow::SetupMenu() connect(ui->actionDisconnect, &QAction::triggered, this, &AppWindow::DisconnectDevice); connect(ui->actionQuit, &QAction::triggered, this, &AppWindow::close); connect(ui->actionSave_setup, &QAction::triggered, [=](){ - auto filename = QFileDialog::getSaveFileName(nullptr, "Save setup data", "", "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(nullptr, "Save setup data", Preferences::getInstance().UISettings.Paths.setup, "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.setup = QFileInfo(filename).path(); SaveSetup(filename); }); connect(ui->actionLoad_setup, &QAction::triggered, [=](){ - auto filename = QFileDialog::getOpenFileName(nullptr, "Load setup data", "", "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(nullptr, "Load setup data", Preferences::getInstance().UISettings.Paths.setup, "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.setup = QFileInfo(filename).path(); LoadSetup(filename); }); connect(ui->actionSave_image, &QAction::triggered, [=](){ diff --git a/Software/PC_Application/LibreVNA-GUI/mode.cpp b/Software/PC_Application/LibreVNA-GUI/mode.cpp index f008092..3a9618b 100644 --- a/Software/PC_Application/LibreVNA-GUI/mode.cpp +++ b/Software/PC_Application/LibreVNA-GUI/mode.cpp @@ -141,11 +141,12 @@ Mode::Type Mode::TypeFromName(QString s) void Mode::saveSreenshot() { - auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions()); if(filename.isEmpty()) { // aborted selection return; } + Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path(); if(filename.endsWith(".png")) { filename.chop(4); } diff --git a/Software/PC_Application/LibreVNA-GUI/preferences.cpp b/Software/PC_Application/LibreVNA-GUI/preferences.cpp index c009d8b..e248e73 100644 --- a/Software/PC_Application/LibreVNA-GUI/preferences.cpp +++ b/Software/PC_Application/LibreVNA-GUI/preferences.cpp @@ -68,7 +68,7 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) : ui->StartupStack->setCurrentWidget(ui->StartupPageSetupFile); }); connect(ui->StartupBrowse, &QPushButton::clicked, [=](){ - ui->StartupSetupFile->setText(QFileDialog::getOpenFileName(nullptr, "Select startup setup file", "", "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions())); + ui->StartupSetupFile->setText(QFileDialog::getOpenFileName(nullptr, "Select startup setup file", Preferences::getInstance().UISettings.Paths.setup, "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions())); }); ui->StartupSweepStart->setUnit("Hz"); ui->StartupSweepStart->setPrefixes(" kMG"); @@ -203,29 +203,31 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) : emit p->updated(); }); connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){ - auto filename = QFileDialog::getSaveFileName(this, "Save preferences", "", "LibreVNA preferences files (*.vnapref)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getSaveFileName(this, "Save preferences", Preferences::getInstance().UISettings.Paths.pref, "LibreVNA preferences files (*.vnapref)", nullptr, Preferences::QFileDialogOptions()); if(filename.length() > 0) { - if(!filename.toLower().endsWith(".vnapref")) { + Preferences::getInstance().UISettings.Paths.pref = QFileInfo(filename).path(); + if(!filename.toLower().endsWith(".vnapref")) { filename.append(".vnapref"); - } - ofstream file; - file.open(filename.toStdString()); - updateFromGUI(); - file << setw(1) << p->toJSON(); - file.close(); + } + ofstream file; + file.open(filename.toStdString()); + updateFromGUI(); + file << setw(1) << p->toJSON(); + file.close(); } }); connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){ - auto filename = QFileDialog::getOpenFileName(this, "Load preferences", "", "LibreVNA preferences files (*.vnapref)", nullptr, Preferences::QFileDialogOptions()); + auto filename = QFileDialog::getOpenFileName(this, "Load preferences", Preferences::getInstance().UISettings.Paths.pref, "LibreVNA preferences files (*.vnapref)", nullptr, Preferences::QFileDialogOptions()); if(filename.length() > 0) { - ifstream file; - file.open(filename.toStdString()); - nlohmann::json j; - file >> j; - file.close(); - p->fromJSON(j); - setInitialGUIState(); - emit p->updated(); + Preferences::getInstance().UISettings.Paths.pref = QFileInfo(filename).path(); + ifstream file; + file.open(filename.toStdString()); + nlohmann::json j; + file >> j; + file.close(); + p->fromJSON(j); + setInitialGUIState(); + emit p->updated(); } }); connect(ui->AcquisitionLimitTDRCheckbox, &QCheckBox::toggled, [=](bool enabled){ @@ -274,6 +276,7 @@ void PreferencesDialog::setInitialGUIState() ui->AcquisitionAlwaysExciteBoth->setChecked(p->Acquisition.alwaysExciteAllPorts); ui->AcquisitionAllowSegmentedSweep->setChecked(p->Acquisition.allowSegmentedSweep); + ui->AcquisitionAllowCalStartWithUnstableLibreCALTemperature->setChecked(p->Acquisition.allowUseOfUnstableLibreCALTemp); ui->AcquisitionAveragingMode->setCurrentIndex(p->Acquisition.useMedianAveraging ? 1 : 0); ui->AcquisitionFullSpanBehavior->setCurrentIndex(p->Acquisition.fullSpanManual ? 1 : 0); ui->AcquisitionFullSpanStart->setValue(p->Acquisition.fullSpanStart); @@ -396,6 +399,7 @@ void PreferencesDialog::updateFromGUI() p->Acquisition.alwaysExciteAllPorts = ui->AcquisitionAlwaysExciteBoth->isChecked(); p->Acquisition.allowSegmentedSweep = ui->AcquisitionAllowSegmentedSweep->isChecked(); + p->Acquisition.allowUseOfUnstableLibreCALTemp = ui->AcquisitionAllowCalStartWithUnstableLibreCALTemperature->isChecked(); p->Acquisition.useMedianAveraging = ui->AcquisitionAveragingMode->currentIndex() == 1; p->Acquisition.fullSpanManual = ui->AcquisitionFullSpanBehavior->currentIndex() == 1; p->Acquisition.fullSpanStart = ui->AcquisitionFullSpanStart->value(); diff --git a/Software/PC_Application/LibreVNA-GUI/preferences.h b/Software/PC_Application/LibreVNA-GUI/preferences.h index f3b7dfa..44105e7 100644 --- a/Software/PC_Application/LibreVNA-GUI/preferences.h +++ b/Software/PC_Application/LibreVNA-GUI/preferences.h @@ -103,8 +103,11 @@ public: } SA; } Startup; struct { + // VNA settings bool alwaysExciteAllPorts; bool allowSegmentedSweep; + bool allowUseOfUnstableLibreCALTemp; + bool useMedianAveraging; // Full span settings @@ -207,6 +210,26 @@ public: bool saveTraceData; bool useNativeDialogs; } Debug; + struct { + struct { + unsigned int ports; + unsigned int formatIndex; + unsigned int unitIndex; + QString exportedTraceNames; + } TouchstoneExport; + struct { + QString setup; + QString cal; + QString calkit; + QString data; + QString image; + QString vnacaldata; + QString packetlog; + QString limitLines; + QString pref; + QString firmware; + } Paths; + } UISettings; bool TCPoverride; // in case of manual port specification via command line @@ -257,6 +280,7 @@ private: {&Startup.SA.averaging, "Startup.SA.averaging", 1}, {&Acquisition.alwaysExciteAllPorts, "Acquisition.alwaysExciteBothPorts", true}, {&Acquisition.allowSegmentedSweep, "Acquisition.allowSegmentedSweep", true}, + {&Acquisition.allowUseOfUnstableLibreCALTemp, "Acquisition.allowUseOfUnstableLibreCALTemp", true}, {&Acquisition.useMedianAveraging, "Acquisition.useMedianAveraging", false}, {&Acquisition.fullSpanManual, "Acquisition.fullSpanManual", false}, {&Acquisition.fullSpanStart, "Acquisition.fullSpanStart", 0.0}, @@ -374,6 +398,21 @@ private: {&Debug.USBlogSizeLimit, "Debug.USBlogSizeLimit", 10000000.0}, {&Debug.saveTraceData, "Debug.saveTraceData", false}, {&Debug.useNativeDialogs, "Debug.useNativeDialogs", true}, + + {&UISettings.TouchstoneExport.ports, "UISettings.TouchstoneExport.ports", 2}, + {&UISettings.TouchstoneExport.formatIndex, "UISettings.TouchstoneExport.formatIndex", 2}, + {&UISettings.TouchstoneExport.unitIndex, "UISettings.TouchstoneExport.unitIndex", 3}, + {&UISettings.TouchstoneExport.exportedTraceNames, "UISettings.TouchstoneExport.exportedTraceNames", ""}, + {&UISettings.Paths.setup, "UISettings.Paths.setup", ""}, + {&UISettings.Paths.cal, "UISettings.Paths.cal", ""}, + {&UISettings.Paths.calkit, "UISettings.Paths.calkit", ""}, + {&UISettings.Paths.data, "UISettings.Paths.data", ""}, + {&UISettings.Paths.image, "UISettings.Paths.image", ""}, + {&UISettings.Paths.vnacaldata, "UISettings.Paths.vnacaldata", ""}, + {&UISettings.Paths.packetlog, "UISettings.Paths.packetlog", ""}, + {&UISettings.Paths.limitLines, "UISettings.Paths.limitLines", ""}, + {&UISettings.Paths.pref, "UISettings.Paths.pref", ""}, + {&UISettings.Paths.firmware, "UISettings.Paths.firmware", ""}, }}; }; diff --git a/Software/PC_Application/LibreVNA-GUI/preferencesdialog.ui b/Software/PC_Application/LibreVNA-GUI/preferencesdialog.ui index 9078999..08a9c1c 100644 --- a/Software/PC_Application/LibreVNA-GUI/preferencesdialog.ui +++ b/Software/PC_Application/LibreVNA-GUI/preferencesdialog.ui @@ -98,7 +98,7 @@ - 2 + 1 @@ -112,8 +112,8 @@ 0 0 - 522 - 945 + 683 + 902 @@ -713,8 +713,8 @@ 0 0 - 564 - 477 + 697 + 564 @@ -741,6 +741,22 @@ + + + + Electronic Calibration + + + + + + Allow calibration to start before the LibreCAL temperature has stabilized + + + + + + @@ -955,7 +971,7 @@ 0 0 683 - 1217 + 1182 @@ -1520,8 +1536,8 @@ 0 0 - 602 - 628 + 486 + 608 @@ -1880,8 +1896,8 @@ 0 0 - 168 - 127 + 144 + 124 @@ -2163,8 +2179,8 @@ 0 0 - 258 - 241 + 697 + 564 diff --git a/Software/PC_Application/LibreVNA-Test/LibreVNA-Test.pro b/Software/PC_Application/LibreVNA-Test/LibreVNA-Test.pro index eb5d929..77323e5 100644 --- a/Software/PC_Application/LibreVNA-Test/LibreVNA-Test.pro +++ b/Software/PC_Application/LibreVNA-Test/LibreVNA-Test.pro @@ -12,6 +12,7 @@ SOURCES += \ ../LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp \ ../LibreVNA-GUI/Calibration/calibration.cpp \ ../LibreVNA-GUI/Calibration/calibrationmeasurement.cpp \ + ../LibreVNA-GUI/Calibration/calibrationviewdialog.cpp \ ../LibreVNA-GUI/Calibration/calkit.cpp \ ../LibreVNA-GUI/Calibration/calkitdialog.cpp \ ../LibreVNA-GUI/Calibration/calstandard.cpp \ @@ -155,6 +156,7 @@ SOURCES += \ ../LibreVNA-GUI/streamingserver.cpp \ ../LibreVNA-GUI/touchstone.cpp \ ../LibreVNA-GUI/unit.cpp \ + calibrationtests.cpp \ ffttests.cpp \ impedancerenormalizationtests.cpp \ main.cpp \ @@ -199,6 +201,7 @@ HEADERS += \ ../LibreVNA-GUI/Calibration/LibreCAL/usbdevice.h \ ../LibreVNA-GUI/Calibration/calibration.h \ ../LibreVNA-GUI/Calibration/calibrationmeasurement.h \ + ../LibreVNA-GUI/Calibration/calibrationviewdialog.h \ ../LibreVNA-GUI/Calibration/calkit.h \ ../LibreVNA-GUI/Calibration/calkitdialog.h \ ../LibreVNA-GUI/Calibration/calstandard.h \ @@ -354,6 +357,7 @@ HEADERS += \ ../LibreVNA-GUI/streamingserver.h \ ../LibreVNA-GUI/touchstone.h \ ../LibreVNA-GUI/unit.h \ + calibrationtests.h \ ffttests.h \ impedancerenormalizationtests.h \ parametertests.h \ @@ -376,6 +380,7 @@ FORMS += \ ../LibreVNA-GUI/Calibration/LibreCAL/factoryUpdateDialog.ui \ ../LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.ui \ ../LibreVNA-GUI/Calibration/calibrationdialogui.ui \ + ../LibreVNA-GUI/Calibration/calibrationviewdialog.ui \ ../LibreVNA-GUI/Calibration/calkitdialog.ui \ ../LibreVNA-GUI/Calibration/manualcalibrationdialog.ui \ ../LibreVNA-GUI/CustomWidgets/csvimport.ui \ @@ -446,6 +451,6 @@ unix:LIBS += -L/usr/lib/ REVISION = $$system(git rev-parse HEAD) DEFINES += GITHASH=\\"\"$$REVISION\\"\" -DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=2 FW_SUFFIX=""#\\"\"-alpha.2\\"\" +DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=4 FW_SUFFIX=""#\\"\"-alpha.2\\"\" DEFINES -= _UNICODE UNICODE win32:DEFINES += QMICROZ_LIBRARY diff --git a/Software/PC_Application/LibreVNA-Test/calibrationtests.cpp b/Software/PC_Application/LibreVNA-Test/calibrationtests.cpp new file mode 100644 index 0000000..8fb1358 --- /dev/null +++ b/Software/PC_Application/LibreVNA-Test/calibrationtests.cpp @@ -0,0 +1,136 @@ +#include "calibrationtests.h" + +#include "calibration.h" + +CalibrationTests::CalibrationTests() {} + +void CalibrationTests::LinearDetection() +{ + // create some measurements + std::vector m; + double startFreq = 100000; + double stopFreq = 6000000000; + int points = 1001; + Calibration cal; + cal.getKit().setIdealDefault(); + auto open = new CalibrationMeasurement::Open(&cal); + open->setPort(1); + m.push_back(open); + auto _short = new CalibrationMeasurement::Short(&cal); + _short->setPort(1); + m.push_back(_short); + auto load = new CalibrationMeasurement::Load(&cal); + load->setPort(1); + m.push_back(load); + + for(int i=0;iaddPoint(meas); + m[1]->addPoint(meas); + m[2]->addPoint(meas); + } + + // verify correct detection + double detectedStart; + double detectedStop; + int detectedPoints; + bool detectedLog; + Calibration::hasFrequencyOverlap(m, &detectedStart, &detectedStop, &detectedPoints, &detectedLog); + + QVERIFY(qFuzzyCompare(detectedStart, startFreq)); + QVERIFY(qFuzzyCompare(detectedStop, stopFreq)); + QVERIFY(detectedPoints == points); + QVERIFY(detectedLog == false); +} + +void CalibrationTests::LogDetection() +{ + // create some measurements + std::vector m; + double startFreq = 100000; + double stopFreq = 6000000000; + int points = 1001; + Calibration cal; + cal.getKit().setIdealDefault(); + auto open = new CalibrationMeasurement::Open(&cal); + open->setPort(1); + m.push_back(open); + auto _short = new CalibrationMeasurement::Short(&cal); + _short->setPort(1); + m.push_back(_short); + auto load = new CalibrationMeasurement::Load(&cal); + load->setPort(1); + m.push_back(load); + + for(int i=0;iaddPoint(meas); + m[1]->addPoint(meas); + m[2]->addPoint(meas); + } + + // verify correct detection + double detectedStart; + double detectedStop; + int detectedPoints; + bool detectedLog; + Calibration::hasFrequencyOverlap(m, &detectedStart, &detectedStop, &detectedPoints, &detectedLog); + + QVERIFY(qFuzzyCompare(detectedStart, startFreq)); + QVERIFY(qFuzzyCompare(detectedStop, stopFreq)); + QVERIFY(detectedPoints == points); + QVERIFY(detectedLog == true); +} + +void CalibrationTests::MixedDetection() +{ + // create some measurements + std::vector m; + double startFreq = 100000; + double stopFreq = 6000000000; + int points = 1001; + Calibration cal; + cal.getKit().setIdealDefault(); + auto open = new CalibrationMeasurement::Open(&cal); + open->setPort(1); + m.push_back(open); + auto _short = new CalibrationMeasurement::Short(&cal); + _short->setPort(1); + m.push_back(_short); + auto load = new CalibrationMeasurement::Load(&cal); + load->setPort(1); + m.push_back(load); + + for(int i=0;iaddPoint(measlin); + m[1]->addPoint(measlog); + m[2]->addPoint(measlog); + } + + // verify correct detection + double detectedStart; + double detectedStop; + int detectedPoints; + bool detectedLog; + Calibration::hasFrequencyOverlap(m, &detectedStart, &detectedStop, &detectedPoints, &detectedLog); + + QVERIFY(qFuzzyCompare(detectedStart, startFreq)); + QVERIFY(qFuzzyCompare(detectedStop, stopFreq)); + QVERIFY(detectedPoints == points); + QVERIFY(detectedLog == true); +} diff --git a/Software/PC_Application/LibreVNA-Test/calibrationtests.h b/Software/PC_Application/LibreVNA-Test/calibrationtests.h new file mode 100644 index 0000000..0602a7d --- /dev/null +++ b/Software/PC_Application/LibreVNA-Test/calibrationtests.h @@ -0,0 +1,18 @@ +#ifndef CALIBRATIONTESTS_H +#define CALIBRATIONTESTS_H + +#include + +class CalibrationTests : public QObject +{ + Q_OBJECT +public: + CalibrationTests(); + +private slots: + void LinearDetection(); + void LogDetection(); + void MixedDetection(); +}; + +#endif // CALIBRATIONTESTS_H diff --git a/Software/PC_Application/LibreVNA-Test/main.cpp b/Software/PC_Application/LibreVNA-Test/main.cpp index 7456941..f9f2868 100644 --- a/Software/PC_Application/LibreVNA-Test/main.cpp +++ b/Software/PC_Application/LibreVNA-Test/main.cpp @@ -3,6 +3,7 @@ #include "parametertests.h" #include "ffttests.h" #include "impedancerenormalizationtests.h" +#include "calibrationtests.h" #include @@ -16,6 +17,7 @@ int main(int argc, char *argv[]) status |= QTest::qExec(new ParameterTests, argc, argv); status |= QTest::qExec(new fftTests, argc, argv); status |= QTest::qExec(new ImpedanceRenormalizationTests, argc, argv); + status |= QTest::qExec(new CalibrationTests, argc, argv); return status; } diff --git a/Software/VNA_embedded/.cproject b/Software/VNA_embedded/.cproject index b1393ba..67f1bd9 100644 --- a/Software/VNA_embedded/.cproject +++ b/Software/VNA_embedded/.cproject @@ -44,7 +44,7 @@ - + @@ -91,7 +91,7 @@ - + diff --git a/Software/VNA_embedded/Application/Drivers/Flash.hpp b/Software/VNA_embedded/Application/Drivers/Flash.hpp index d904290..691908d 100644 --- a/Software/VNA_embedded/Application/Drivers/Flash.hpp +++ b/Software/VNA_embedded/Application/Drivers/Flash.hpp @@ -9,6 +9,7 @@ #define DRIVERS_FLASH_HPP_ #include "stm.hpp" +#include "delay.hpp" class Flash { public: @@ -37,6 +38,7 @@ private: void CS(bool high) { if(high) { CS_gpio->BSRR = CS_pin; + Delay::us(1); } else { CS_gpio->BSRR = CS_pin << 16; } diff --git a/Software/VNA_embedded/Application/Drivers/Si5351C.cpp b/Software/VNA_embedded/Application/Drivers/Si5351C.cpp index f47f874..d31c92c 100644 --- a/Software/VNA_embedded/Application/Drivers/Si5351C.cpp +++ b/Software/VNA_embedded/Application/Drivers/Si5351C.cpp @@ -2,6 +2,7 @@ #include #include +#include "algorithm.hpp" #define LOG_LEVEL LOG_LEVEL_INFO #define LOG_MODULE "SI5351" @@ -50,7 +51,7 @@ bool Si5351C::ConfigureCLKIn(uint32_t clkin_freq) { return success; } -bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src) { +bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src, bool exactFrequency) { if (frequency < 600000000 || frequency > 900000000) { LOG_ERR("Requested PLL frequency out of range (600-900MHz): %lu", frequency); return false; @@ -67,14 +68,14 @@ bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src) { LOG_ERR("Calculated divider out of range (15-90)"); return false; } - FindOptimalDivider(frequency, srcFreq, c.P1, c.P2, c.P3); + FindOptimalDivider(frequency, srcFreq, c.P1, c.P2, c.P3, exactFrequency); FreqPLL[(int) pll] = frequency; LOG_DEBUG("Setting PLL %c to %luHz", pll==PLL::A ? 'A' : 'B', frequency); return WritePLLConfig(c, pll); } -bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength, uint32_t PLLFreqOverride) { +bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength, bool exactFrequency) { ClkConfig c; c.DivideBy4 = false; c.IntegerMode = false; @@ -84,7 +85,7 @@ bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStreng c.source = source; c.strength = strength; - uint32_t pllFreq = PLLFreqOverride > 0 ? PLLFreqOverride : FreqPLL[(int) source]; + uint32_t pllFreq = FreqPLL[(int) source]; if (clknum > 5) { // outputs 6 and 7 are integer dividers only uint32_t div = pllFreq / frequency; @@ -113,7 +114,7 @@ bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStreng return false; } } - FindOptimalDivider(pllFreq, frequency * c.RDiv, c.P1, c.P2, c.P3); + FindOptimalDivider(pllFreq, frequency * c.RDiv, c.P1, c.P2, c.P3, exactFrequency); } LOG_DEBUG("Setting CLK%d to %luHz", clknum, frequency); return WriteClkConfig(c, clknum); @@ -346,13 +347,26 @@ bool Si5351C::ResetPLL(PLL pll) { } void Si5351C::FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1, - uint32_t &P2, uint32_t &P3) { + uint32_t &P2, uint32_t &P3, bool exact) { // see https://www.silabs.com/documents/public/application-notes/AN619.pdf (page 3/6) uint32_t a = f_pll / f; int32_t f_rem = f_pll - f * a; - // always using the highest modulus divider results in less than 1Hz deviation for all frequencies, that is good enough - uint32_t best_c = (1UL << 20) - 1; - uint32_t best_b = (uint64_t) f_rem * best_c / f; + uint32_t best_c; + uint32_t best_b; + if(exact) { + // spend the effort to find the best divider possible + Algorithm::RationalApproximation ratio; + ratio.num = f_rem; + ratio.denom = f; + auto approx = Algorithm::BestRationalApproximation(ratio, (1UL << 20) - 1); + best_c = approx.denom; + best_b = approx.num; + } else { + // just use the highest denominator possible, this is good enough if no exact frequency is required + best_c = (1UL << 20) - 1; + best_b = (uint64_t) f_rem * best_c / f; + } + // convert to Si5351C parameters uint32_t floor = 128 * best_b / best_c; P1 = 128 * a + floor - 512; diff --git a/Software/VNA_embedded/Application/Drivers/Si5351C.hpp b/Software/VNA_embedded/Application/Drivers/Si5351C.hpp index 62eca24..3f800b8 100644 --- a/Software/VNA_embedded/Application/Drivers/Si5351C.hpp +++ b/Software/VNA_embedded/Application/Drivers/Si5351C.hpp @@ -31,8 +31,8 @@ public: }; bool Init(uint32_t clkin_freq = 0); bool ConfigureCLKIn(uint32_t clkin_freq); - bool SetPLL(PLL pll, uint32_t frequency, PLLSource src); - bool SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength = DriveStrength::mA2, uint32_t PLLFreqOverride = 0); + bool SetPLL(PLL pll, uint32_t frequency, PLLSource src, bool exactFrequency=true); + bool SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength = DriveStrength::mA2, bool exactFrequency=true); bool SetBypass(uint8_t clknum, PLLSource source, DriveStrength strength = DriveStrength::mA2); bool SetCLKtoXTAL(uint8_t clknum); bool SetCLKToCLKIN(uint8_t clknum); @@ -48,7 +48,7 @@ public: bool WriteRawCLKConfig(uint8_t clknum, const uint8_t *config); bool ReadRawCLKConfig(uint8_t clknum, uint8_t *config); private: - void FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1, uint32_t &P2, uint32_t &P3); + void FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1, uint32_t &P2, uint32_t &P3, bool exact); enum class Reg : uint8_t { DeviceStatus = 0, InterruptStatusSticky = 1, diff --git a/Software/VNA_embedded/Application/Drivers/algorithm.cpp b/Software/VNA_embedded/Application/Drivers/algorithm.cpp index d8ca1f8..2ec2ce0 100644 --- a/Software/VNA_embedded/Application/Drivers/algorithm.cpp +++ b/Software/VNA_embedded/Application/Drivers/algorithm.cpp @@ -2,6 +2,7 @@ #include "stm.hpp" #include +#include Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float ratio, uint32_t max_denom) { RationalApproximation result; @@ -42,3 +43,47 @@ Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float rati } return result; } + +uint32_t gcd(uint32_t u, uint32_t v) { + if(u==0) { + return v; + } else if(v==0) { + return u; + } + + uint8_t i = __builtin_ctz(u); + u >>= i; + uint8_t j = __builtin_ctz(v); + v >>= j; + + uint8_t k = i < j ? i : j; + while(true) { + if(u > v) { + std::swap(u, v); + } + v -= u; + if(v==0) { + return u << k; + } + v >>= __builtin_ctz(v); + } +} + +Algorithm::RationalApproximation Algorithm::BestRationalApproximation( + RationalApproximation ratio, uint32_t max_denom) { + if(ratio.denom <= max_denom) { + // nothing to do, we can just return the ratio as it is + return ratio; + } + // Try to simplify the ratio. + // Find greatest common divider + uint32_t div = gcd(ratio.num, ratio.denom); + ratio.num /= div; + ratio.denom /= div; + if(ratio.denom <= max_denom) { + // small enough now, can use as is + return ratio; + } + // no good, we need to approximate + return Algorithm::BestRationalApproximation((float) ratio.num / ratio.denom, max_denom); +} diff --git a/Software/VNA_embedded/Application/Drivers/algorithm.hpp b/Software/VNA_embedded/Application/Drivers/algorithm.hpp index 446eb86..8564642 100644 --- a/Software/VNA_embedded/Application/Drivers/algorithm.hpp +++ b/Software/VNA_embedded/Application/Drivers/algorithm.hpp @@ -9,6 +9,7 @@ using RationalApproximation = struct _rationalapproximation { uint32_t denom; }; +RationalApproximation BestRationalApproximation(RationalApproximation ratio, uint32_t max_denom); RationalApproximation BestRationalApproximation(float ratio, uint32_t max_denom); } diff --git a/Software/VNA_embedded/Application/Drivers/max2871.cpp b/Software/VNA_embedded/Application/Drivers/max2871.cpp index b939171..0b3f91d 100644 --- a/Software/VNA_embedded/Application/Drivers/max2871.cpp +++ b/Software/VNA_embedded/Application/Drivers/max2871.cpp @@ -189,7 +189,10 @@ bool MAX2871::SetFrequency(uint64_t f) { LOG_DEBUG("Looking for best fractional match"); float fraction = (float) rem_f / f_PFD; - auto approx = Algorithm::BestRationalApproximation(fraction, 4095); + Algorithm::RationalApproximation ratio; + ratio.num = rem_f; + ratio.denom = f_PFD; + auto approx = Algorithm::BestRationalApproximation(ratio, 4095); if (approx.denom == approx.num) { // got an impossible result due to floating point limitations(?) diff --git a/Software/VNA_embedded/Application/Generator.cpp b/Software/VNA_embedded/Application/Generator.cpp index a5c3867..8904200 100644 --- a/Software/VNA_embedded/Application/Generator.cpp +++ b/Software/VNA_embedded/Application/Generator.cpp @@ -34,7 +34,7 @@ void Generator::Setup(Protocol::GeneratorSettings g) { if(g.frequency < HW::BandSwitchFrequency) { bandSelect = true; FPGA::Disable(FPGA::Periphery::SourceChip); - Si5351.SetCLK(SiChannel::LowbandSource, g.frequency, Si5351C::PLL::B, + Si5351.SetCLK(SiChannel::LowbandSource, g.frequency, Si5351C::PLL::A, amplitude.lowBandPower); Si5351.Enable(SiChannel::LowbandSource); } else { diff --git a/Software/VNA_embedded/Application/Manual.cpp b/Software/VNA_embedded/Application/Manual.cpp index a225e57..52a2d17 100644 --- a/Software/VNA_embedded/Application/Manual.cpp +++ b/Software/VNA_embedded/Application/Manual.cpp @@ -16,7 +16,7 @@ void Manual::Setup(Protocol::ManualControl m) { FPGA::AbortSweep(); // Configure lowband source if (m.V1.SourceLowEN) { - Si5351.SetCLK(SiChannel::LowbandSource, m.V1.SourceLowFrequency, Si5351C::PLL::B, + Si5351.SetCLK(SiChannel::LowbandSource, m.V1.SourceLowFrequency, Si5351C::PLL::A, (Si5351C::DriveStrength) m.V1.SourceLowPower); Si5351.Enable(SiChannel::LowbandSource); } else { diff --git a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp index dc9d083..64407ca 100644 --- a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp +++ b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp @@ -94,7 +94,7 @@ static void StartNextSample() { } attenuator = amplitude.attenuator; if(trackingFreq < HW::BandSwitchFrequency) { - Si5351.SetCLK(SiChannel::LowbandSource, trackingFreq, Si5351C::PLL::B, amplitude.lowBandPower); + Si5351.SetCLK(SiChannel::LowbandSource, trackingFreq, Si5351C::PLL::A, amplitude.lowBandPower, false); FPGA::Disable(FPGA::Periphery::SourceChip); FPGA::Disable(FPGA::Periphery::SourceRF); trackingLowband = true; @@ -198,8 +198,8 @@ static void StartNextSample() { // only adjust LO2 PLL if necessary (if the deviation is significantly less than the RBW it does not matter) if((uint32_t) abs(LO2freq - lastLO2) > actualRBW / 100) { // Si5351.SetPLL(Si5351C::PLL::B, LO2freq*HW::LO2Multiplier, HW::Ref::getSource()); - Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2); - Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2); + Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2, false); + Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2, false); lastLO2 = LO2freq; } if (s.UseDFT) { diff --git a/Software/VNA_embedded/Application/VNA.cpp b/Software/VNA_embedded/Application/VNA.cpp index 01a05cc..2f0ea5d 100644 --- a/Software/VNA_embedded/Application/VNA.cpp +++ b/Software/VNA_embedded/Application/VNA.cpp @@ -87,14 +87,14 @@ static bool setPLLFrequencies(uint64_t f) { } LOFreq = f + HW::getIF1(); } - if(sourceFreq > HW::BandSwitchFrequency) { + if(sourceFreq >= HW::BandSwitchFrequency) { Source.SetFrequency(sourceFreq); } LO1.SetFrequency(LOFreq); bool needsRefSwitch = false; if(settings.suppressPeaks) { // Integer spurs can cause a small peak. - if(sourceFreq > HW::BandSwitchFrequency) { + if(sourceFreq >= HW::BandSwitchFrequency) { uint32_t sourceDist = Source.DistanceToIntegerSpur(); if((sourceDist > 0) && (sourceDist < 3 * HW::getIF2())) { LOG_DEBUG("Source spur at %lu: %lu", (uint32_t) f, sourceDist); @@ -210,7 +210,7 @@ bool VNA::Setup(Protocol::SweepSettings s) { Si5351.Enable(SiChannel::Port1LO2); Si5351.Enable(SiChannel::Port2LO2); Si5351.Enable(SiChannel::RefLO2); - Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource()); + Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false); Si5351.ResetPLL(Si5351C::PLL::B); Si5351.WaitForLock(Si5351C::PLL::B, 10); @@ -458,7 +458,7 @@ void VNA::SweepHalted() { } // need the Si5351 as Source - bool freqSuccess = Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::B, driveStrength); + bool freqSuccess = Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::A, driveStrength, false); static bool lowbandDisabled = false; if (pointCnt == 0) { // First point in sweep, switch to correct source @@ -499,7 +499,7 @@ void VNA::SweepHalted() { Si5351.SetCLK(SiChannel::Source, PLLRefFreqs[sourceRefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8); Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8); last_LO2 = HW::getIF1() - HW::getIF2(); - Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource()); + Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false); Si5351.ResetPLL(Si5351C::PLL::B); Si5351.WaitForLock(Si5351C::PLL::B, 10); HAL_Delay(2); @@ -514,7 +514,7 @@ void VNA::SweepHalted() { Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8); } if(needs2LOshift(frequency, last_LO2, actualBandwidth, &last_LO2)) { - Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource()); + Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false); Si5351.ResetPLL(Si5351C::PLL::B); Si5351.WaitForLock(Si5351C::PLL::B, 10); // PLL reset causes the 2.LO to turn off briefly and then ramp on back, needs delay before next point diff --git a/Software/VNA_embedded/Makefile b/Software/VNA_embedded/Makefile index 2ef881b..ae3b667 100644 --- a/Software/VNA_embedded/Makefile +++ b/Software/VNA_embedded/Makefile @@ -101,7 +101,7 @@ MCU = $(CPU) -mthumb $(FLOAT-ABI) $(FPU) C_DEFS = \ -DFW_MAJOR=1 \ -DFW_MINOR=6 \ --DFW_PATCH=3 \ +-DFW_PATCH=4 \ -DDEBUG \ -DUSE_FULL_LL_DRIVER \ -DHW_REVISION="'B'" \