Merge branch 'master' into prototype_testing

This commit is contained in:
Jan Käberich 2025-08-22 18:18:40 +02:00
commit 8a74eedfa0
64 changed files with 1415 additions and 272 deletions

View file

@ -88,22 +88,22 @@ jobs:
path: Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI path: Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI
PC_Application_Windows: PC_Application_Windows:
runs-on: windows-2019 runs-on: windows-2022
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v4
with: with:
version: '6.2.0' version: '6.2.4'
arch: 'win64_mingw81' arch: 'win64_mingw'
- name: Download libusb - name: Download libusb
run: | run: |
curl -o libusb.7z -L https://github.com/libusb/libusb/releases/download/v1.0.25/libusb-1.0.25.7z 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 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 Xcopy /E /I /Y libusb\MinGW64\static\libusb-1.0.a Software\PC_Application\LibreVNA-GUI
shell: cmd shell: cmd
@ -134,10 +134,9 @@ jobs:
cd Software/PC_Application/LibreVNA-GUI/release cd Software/PC_Application/LibreVNA-GUI/release
del *.o *.cpp del *.o *.cpp
windeployqt.exe . windeployqt.exe .
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libwinpthread-1.dll . copy %QT_ROOT_DIR%\bin\libwinpthread-1.dll .
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libgcc_s_seh-1.dll . copy %QT_ROOT_DIR%\bin\libgcc_s_seh-1.dll .
copy "..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libstdc++-6.dll" . copy %QT_ROOT_DIR%\bin\Qt6OpenGL.dll .
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\Qt6OpenGL.dll .
shell: cmd shell: cmd
- name: Upload - name: Upload

View file

@ -104,22 +104,22 @@ jobs:
PC_Application_Windows: PC_Application_Windows:
needs: PC_Application_Ubuntu needs: PC_Application_Ubuntu
runs-on: windows-2019 runs-on: windows-2022
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v4
with: with:
version: '6.2.0' version: '6.2.4'
arch: 'win64_mingw81' arch: 'win64_mingw'
- name: Download libusb - name: Download libusb
run: | run: |
curl -o libusb.7z -L https://github.com/libusb/libusb/releases/download/v1.0.25/libusb-1.0.25.7z 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 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 Xcopy /E /I /Y libusb\MinGW64\static\libusb-1.0.a Software\PC_Application\LibreVNA-GUI
shell: cmd shell: cmd
@ -145,10 +145,9 @@ jobs:
cd Software/PC_Application/LibreVNA-GUI/release cd Software/PC_Application/LibreVNA-GUI/release
del *.o *.cpp del *.o *.cpp
windeployqt.exe . windeployqt.exe .
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libwinpthread-1.dll . copy %QT_ROOT_DIR%\bin\libwinpthread-1.dll .
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libgcc_s_seh-1.dll . copy %QT_ROOT_DIR%\bin\libgcc_s_seh-1.dll .
copy "..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libstdc++-6.dll" . copy %QT_ROOT_DIR%\bin\Qt6OpenGL.dll .
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\Qt6OpenGL.dll .
shell: cmd shell: cmd
- name: Zip app - name: Zip app

View file

@ -1,6 +1,21 @@
# Changelog # 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 ## v1.6.3
Bugfixes and quality of life improvements Bugfixes and quality of life improvements
- Windows and macOS: add icon to GUI application - Windows and macOS: add icon to GUI application

View file

@ -13075,7 +13075,7 @@
(hide yes) (hide yes)
) )
) )
(property "MPN" "LM3370SD-4221" (property "MPN" "LM3370SD-3021"
(at 101.6 190.5 0) (at 101.6 190.5 0)
(effects (effects
(font (font
@ -13970,7 +13970,7 @@
(hide yes) (hide yes)
) )
) )
(property "MPN" "LM3370SD-4221" (property "MPN" "LM3370SD-3021"
(at 101.6 231.14 0) (at 101.6 231.14 0)
(effects (effects
(font (font
@ -16667,7 +16667,7 @@
(hide yes) (hide yes)
) )
) )
(property "MPN" "LM3370SD-4221" (property "MPN" "LM3370SD-3021"
(at 208.28 180.34 0) (at 208.28 180.34 0)
(effects (effects
(font (font

View file

@ -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) ![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) ![LibreVNA](Software/PC_Application/LibreVNA-GUI/resources/banner.png)

View file

@ -4,8 +4,11 @@ import subprocess
class TestUpdate(TestBase): class TestUpdate(TestBase):
def test_Update(self): 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) 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?") reported = self.vna.query("DEV:INF:FWREVISION?")
major = subprocess.check_output("grep -oP '(?<=FW_MAJOR=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip() 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() minor = subprocess.check_output("grep -oP '(?<=FW_MINOR=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip()

View file

@ -4,6 +4,7 @@
#include "caldevice.h" #include "caldevice.h"
#include "usbdevice.h" #include "usbdevice.h"
#include "CustomWidgets/informationbox.h" #include "CustomWidgets/informationbox.h"
#include "preferences.h"
#include <set> #include <set>
@ -234,6 +235,14 @@ void LibreCALDialog::updateCalibrationStartStatus()
canStart = validatePortSelection(true); 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); ui->start->setEnabled(canStart);
if(canStart) { if(canStart) {
ui->lCalibrationStatus->setText("Ready to start"); ui->lCalibrationStatus->setText("Ready to start");
@ -259,6 +268,7 @@ void LibreCALDialog::updateDeviceStatus()
ui->lDeviceStatus->setText("Heating up, please wait with calibration"); ui->lDeviceStatus->setText("Heating up, please wait with calibration");
ui->lDeviceStatus->setStyleSheet("QLabel { color : orange; }"); ui->lDeviceStatus->setStyleSheet("QLabel { color : orange; }");
} }
updateCalibrationStartStatus();
} }
void LibreCALDialog::determineAutoPorts() void LibreCALDialog::determineAutoPorts()
@ -420,6 +430,7 @@ void LibreCALDialog::stopSweep()
void LibreCALDialog::startCalibration() void LibreCALDialog::startCalibration()
{ {
disableUI(); disableUI();
busy = true;
ui->progressCal->setValue(0); ui->progressCal->setValue(0);
ui->lCalibrationStatus->setText("Creating calibration kit from coefficients..."); ui->lCalibrationStatus->setText("Creating calibration kit from coefficients...");
@ -592,6 +603,7 @@ void LibreCALDialog::startCalibration()
disconnect(cal, &Calibration::measurementsUpdated, this, nullptr); disconnect(cal, &Calibration::measurementsUpdated, this, nullptr);
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
enableUI(); enableUI();
busy = false;
break; break;
} }
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
@ -609,6 +621,7 @@ void LibreCALDialog::startCalibration()
connect(cal, &Calibration::measurementsAborted, this, [=](){ connect(cal, &Calibration::measurementsAborted, this, [=](){
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
enableUI(); enableUI();
busy = false;
ui->lCalibrationStatus->setText("Ready to start"); ui->lCalibrationStatus->setText("Ready to start");
}); });

View file

@ -301,6 +301,7 @@ QString Calibration::TypeToString(Calibration::Type type)
{ {
switch(type) { switch(type) {
case Type::None: return "None"; case Type::None: return "None";
case Type::OSL: return "OSL";
case Type::SOLT: return "SOLT"; case Type::SOLT: return "SOLT";
case Type::ThroughNormalization: return "ThroughNormalization"; case Type::ThroughNormalization: return "ThroughNormalization";
case Type::TRL: return "TRL"; case Type::TRL: return "TRL";
@ -708,19 +709,19 @@ Calibration::Point Calibration::createInitializedPoint(double f) {
point.frequency = f; point.frequency = f;
// resize vectors // resize vectors
point.D.resize(caltype.usedPorts.size(), 0.0); 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.S.resize(caltype.usedPorts.size(), 0.0);
point.L.resize(caltype.usedPorts.size()); point.L.resize(caltype.usedPorts.size());
point.T.resize(caltype.usedPorts.size()); point.T.resize(caltype.usedPorts.size());
point.I.resize(caltype.usedPorts.size()); point.I.resize(caltype.usedPorts.size());
fill(point.L.begin(), point.L.end(), vector<complex<double>>(caltype.usedPorts.size(), 0.0)); fill(point.L.begin(), point.L.end(), vector<complex<double>>(caltype.usedPorts.size(), 0.0));
fill(point.T.begin(), point.T.end(), vector<complex<double>>(caltype.usedPorts.size(), 0.0)); fill(point.T.begin(), point.T.end(), vector<complex<double>>(caltype.usedPorts.size(), 1.0));
fill(point.I.begin(), point.I.end(), vector<complex<double>>(caltype.usedPorts.size(), 0.0)); fill(point.I.begin(), point.I.end(), vector<complex<double>>(caltype.usedPorts.size(), 0.0));
return point; return point;
} }
Calibration::Point Calibration::computeSOLT(double f) Calibration::Point Calibration::computeOSL(double f)
{ {
Point point = createInitializedPoint(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; 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; 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 // calculate forward match and transmission
for(unsigned int i=0;i<caltype.usedPorts.size();i++) { for(unsigned int i=0;i<caltype.usedPorts.size();i++) {
for(unsigned int j=0;j<caltype.usedPorts.size();j++) { for(unsigned int j=0;j<caltype.usedPorts.size();j++) {
@ -1278,6 +1286,117 @@ bool Calibration::validForDevice(QString serial) const
} }
} }
bool Calibration::hasDirectivity(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<double>(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<double>(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<double>(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<double>(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<double>(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<double>(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 bool Calibration::hasUnsavedChanges() const
{ {
return unsavedChanges; return unsavedChanges;
@ -1487,11 +1606,12 @@ bool Calibration::toFile(QString filename)
{ {
if(filename.isEmpty()) { if(filename.isEmpty()) {
QString fn = descriptiveCalName(); 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return false; return false;
} }
Preferences::getInstance().UISettings.Paths.cal = QFileInfo(filename).path();
} }
if(filename.toLower().endsWith(".cal")) { if(filename.toLower().endsWith(".cal")) {
@ -1511,11 +1631,12 @@ bool Calibration::toFile(QString filename)
bool Calibration::fromFile(QString filename) bool Calibration::fromFile(QString filename)
{ {
if(filename.isEmpty()) { 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return false; return false;
} }
Preferences::getInstance().UISettings.Paths.cal = QFileInfo(filename).path();
} }
// force correct file ending // force correct file ending
@ -1604,7 +1725,7 @@ std::vector<Calibration::Type> Calibration::getTypes()
return types; 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 { using RequiredMeasurements = struct {
CalibrationMeasurement::Base::Type type; CalibrationMeasurement::Base::Type type;
@ -1615,6 +1736,14 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl
case Type::None: case Type::None:
return true; // Always possible to reset the calibration return true; // Always possible to reset the calibration
case Type::SOLT: 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 // SOL measurements for every port
for(auto p : type.usedPorts) { for(auto p : type.usedPorts) {
required.push_back({.type = CalibrationMeasurement::Base::Type::Short, .port1 = p, .port2 = 0}); 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}); 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; break;
case Type::ThroughNormalization: case Type::ThroughNormalization:
// through measurements between all ports // through measurements between all ports
@ -1674,7 +1797,7 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl
foundMeasurements.push_back(meas); foundMeasurements.push_back(meas);
} }
} }
return hasFrequencyOverlap(foundMeasurements, startFreq, stopFreq, points); return hasFrequencyOverlap(foundMeasurements, startFreq, stopFreq, points, isLog);
} }
return false; return false;
} }
@ -1688,16 +1811,23 @@ bool Calibration::compute(Calibration::CalType type)
} }
double start, stop; double start, stop;
int numPoints; int numPoints;
if(!canCompute(type, &start, &stop, &numPoints)) { bool isLog;
if(!canCompute(type, &start, &stop, &numPoints, &isLog)) {
return false; return false;
} }
caltype = type; caltype = type;
try { try {
points.clear(); points.clear();
for(int i=0;i<numPoints;i++) { for(int i=0;i<numPoints;i++) {
double f = start + (stop - start) * i / (numPoints - 1); double f;
if(!isLog) {
f = start + (stop - start) * i / (numPoints - 1);
} else {
f = start * pow(10.0, i * log10(stop / start) / (numPoints - 1));
}
Point p; Point p;
switch(type.type) { switch(type.type) {
case Type::OSL: p = computeOSL(f); break;
case Type::SOLT: p = computeSOLT(f); break; case Type::SOLT: p = computeSOLT(f); break;
case Type::ThroughNormalization: p = computeThroughNormalization(f); break; case Type::ThroughNormalization: p = computeThroughNormalization(f); break;
case Type::TRL: p = computeTRL(f); break; case Type::TRL: p = computeTRL(f); break;
@ -1726,6 +1856,7 @@ void Calibration::reset()
int Calibration::minimumPorts(Calibration::Type type) int Calibration::minimumPorts(Calibration::Type type)
{ {
switch(type) { switch(type) {
case Type::OSL: return 1;
case Type::SOLT: return 1; case Type::SOLT: return 1;
case Type::ThroughNormalization: return 2; case Type::ThroughNormalization: return 2;
case Type::TRL: return 2; case Type::TRL: return 2;
@ -1850,11 +1981,13 @@ void Calibration::deleteMeasurements()
measurements.clear(); measurements.clear();
} }
bool Calibration::hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base *> m, double *startFreq, double *stopFreq, int *points) bool Calibration::hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base *> m, double *startFreq, double *stopFreq, int *points, bool *isLog)
{ {
double minResolution = std::numeric_limits<double>::max(); double minResolution = std::numeric_limits<double>::max();
double minFreq = 0; double minFreq = 0;
double maxFreq = std::numeric_limits<double>::max(); double maxFreq = std::numeric_limits<double>::max();
unsigned int logCount = 0;
unsigned int linCount = 0;
for(auto meas : m) { for(auto meas : m) {
if(meas->numPoints() < 2) { if(meas->numPoints() < 2) {
return false; return false;
@ -1869,6 +2002,38 @@ bool Calibration::hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base *
if(resolution < minResolution) { if(resolution < minResolution) {
minResolution = resolution; minResolution = resolution;
} }
// check whether the frequency points are more linear or more logarithmic
double minDiff = std::numeric_limits<double>::max();
double maxDiff = 0;
double minRatio = std::numeric_limits<double>::max();
double maxRatio = 0;
for(unsigned int i=1;i<meas->numPoints();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) { if(startFreq) {
*startFreq = minFreq; *startFreq = minFreq;
@ -1879,6 +2044,9 @@ bool Calibration::hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base *
if(points) { if(points) {
*points = (maxFreq - minFreq) / minResolution + 1; *points = (maxFreq - minFreq) / minResolution + 1;
} }
if(isLog) {
*isLog = logCount > linCount;
}
if(maxFreq > minFreq) { if(maxFreq > minFreq) {
return true; return true;
} else { } else {

View file

@ -14,11 +14,13 @@ class Calibration : public QObject, public Savable, public SCPINode
Q_OBJECT Q_OBJECT
friend class LibreCALDialog; friend class LibreCALDialog;
friend class CalibrationTests;
public: public:
Calibration(); Calibration();
enum class Type { enum class Type {
None, None,
OSL,
SOLT, SOLT,
ThroughNormalization, ThroughNormalization,
TRL, TRL,
@ -62,7 +64,7 @@ public:
static std::vector<Type> getTypes(); static std::vector<Type> getTypes();
// Checks whether all measurements for a specific calibration are available. // 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 // 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) // Resets the calibration (deletes all measurements and calculated coefficients)
void reset(); void reset();
// Returns the minimum number of ports for a given calibration type. // Returns the minimum number of ports for a given calibration type.
@ -98,6 +100,14 @@ public:
QString getValidDevice() const; QString getValidDevice() const;
bool validForDevice(QString serial) 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: public slots:
// Call once all datapoints of the current span have been added // Call once all datapoints of the current span have been added
void measurementsComplete(); void measurementsComplete();
@ -130,7 +140,7 @@ private:
void createDefaultMeasurements(DefaultMeasurements dm); void createDefaultMeasurements(DefaultMeasurements dm);
void deleteMeasurements(); void deleteMeasurements();
bool hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base*> m, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr); static bool hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base*> m, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr, bool *isLog = nullptr);
// returns all measurements that match the paramaters // returns all measurements that match the paramaters
std::vector<CalibrationMeasurement::Base*> findMeasurements(CalibrationMeasurement::Base::Type type, int port1 = 0, int port2 = 0); std::vector<CalibrationMeasurement::Base*> findMeasurements(CalibrationMeasurement::Base::Type type, int port1 = 0, int port2 = 0);
// returns the first measurement in the list that matches the parameters // returns the first measurement in the list that matches the parameters
@ -151,6 +161,7 @@ private:
std::vector<Point> points; std::vector<Point> points;
Point createInitializedPoint(double f); Point createInitializedPoint(double f);
Point computeOSL(double f);
Point computeSOLT(double f); Point computeSOLT(double f);
Point computeThroughNormalization(double f); Point computeThroughNormalization(double f);
Point computeTRL(double f); Point computeTRL(double f);

View file

@ -627,6 +627,10 @@ void CalibrationMeasurement::Isolation::addPoint(const DeviceDriver::VNAMeasurem
QString name = meas.first; QString name = meas.first;
unsigned int rcv = name.mid(1, 1).toInt() - 1; unsigned int rcv = name.mid(1, 1).toInt() - 1;
unsigned int src = name.mid(2, 1).toInt() - 1; unsigned int src = name.mid(2, 1).toInt() - 1;
if(rcv > 8 || src > 8) {
// skip
continue;
}
if(rcv >= p.S.size()) { if(rcv >= p.S.size()) {
p.S.resize(rcv + 1); p.S.resize(rcv + 1);
} }

View file

@ -41,6 +41,7 @@ public:
virtual double maxUsableFreq() = 0; virtual double maxUsableFreq() = 0;
virtual double minFreq() = 0; virtual double minFreq() = 0;
virtual double maxFreq() = 0; virtual double maxFreq() = 0;
virtual double getPointFreq(unsigned int p) = 0;
virtual unsigned int numPoints() = 0; virtual unsigned int numPoints() = 0;
virtual bool readyForMeasurement() {return false;} virtual bool readyForMeasurement() {return false;}
virtual bool readyForCalculation() {return false;} virtual bool readyForCalculation() {return false;}
@ -88,6 +89,7 @@ public:
virtual double maxUsableFreq() override; virtual double maxUsableFreq() override;
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();} virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;} 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 unsigned int numPoints() override {return points.size();}
virtual bool readyForMeasurement() override {return standard != nullptr;} virtual bool readyForMeasurement() override {return standard != nullptr;}
virtual bool readyForCalculation() override {return standard && points.size() > 0;} virtual bool readyForCalculation() override {return standard && points.size() > 0;}
@ -207,6 +209,7 @@ public:
virtual double maxUsableFreq() override; virtual double maxUsableFreq() override;
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();} virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;} 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 unsigned int numPoints() override {return points.size();}
virtual bool readyForMeasurement() override {return standard != nullptr;} virtual bool readyForMeasurement() override {return standard != nullptr;}
virtual bool readyForCalculation() override {return standard && points.size() > 0;} virtual bool readyForCalculation() override {return standard && points.size() > 0;}
@ -281,6 +284,7 @@ public:
virtual double maxUsableFreq() override {return maxFreq();} virtual double maxUsableFreq() override {return maxFreq();}
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();} virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;} 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 unsigned int numPoints() override;
virtual bool readyForMeasurement() override {return true;} virtual bool readyForMeasurement() override {return true;}
virtual bool readyForCalculation() override {return points.size() > 0;} virtual bool readyForCalculation() override {return points.size() > 0;}

View file

@ -0,0 +1,314 @@
#include "calibrationviewdialog.h"
#include "ui_calibrationviewdialog.h"
#include <QGraphicsSimpleTextItem>
#include <QVector2D>
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<QPointF> vertices, QColor color, QString label = QString(), bool arrow = false) {
// draw lines
for(unsigned int i=1;i<vertices.size();i++) {
scene->addLine(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<int> 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<enhanced.size();k++) {
if(k == enhanced.size() - 1) {
status += " and "+QString::number(enhanced[k]);
} else if(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();
}

View file

@ -0,0 +1,51 @@
#ifndef CALIBRATIONVIEWDIALOG_H
#define CALIBRATIONVIEWDIALOG_H
#include <QDialog>
#include "calibration.h"
#include <QGraphicsScene>
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

View file

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CalibrationViewDialog</class>
<widget class="QDialog" name="CalibrationViewDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1106</width>
<height>665</height>
</rect>
</property>
<property name="windowTitle">
<string>Calibration Error Term Model</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>View error term model when stimulus is at port</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="port">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>8</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style='color: darkgreen;'&gt;Green&lt;/span&gt; error terms have been calculated from calibration measurements.&lt;/p&gt;&lt;p&gt;&lt;span style='color: darkred;'&gt;Red&lt;/span&gt; error terms are at their default values (either 1 or 0).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QGraphicsView" name="view">
<property name="dragMode">
<enum>QGraphicsView::DragMode::ScrollHandDrag</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;D: Directivity, R: Reflection tracking, S: Source match, L: Receiver match, T: Transmission tracking, I: Isolation&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Which measurements are being corrected?</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTableWidget" name="table">
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::NoSelection</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>CalibrationViewDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>374</x>
<y>509</y>
</hint>
<hint type="destinationlabel">
<x>374</x>
<y>265</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -89,8 +89,9 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
accept(); accept();
}); });
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){ 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) { if(filename.length() > 0) {
Preferences::getInstance().UISettings.Paths.calkit = QFileInfo(filename).path();
try { try {
kit = Calkit::fromFile(filename); kit = Calkit::fromFile(filename);
} catch (runtime_error &e) { } catch (runtime_error &e) {
@ -103,8 +104,9 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
}); });
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){ 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) { if(filename.length() > 0) {
Preferences::getInstance().UISettings.Paths.calkit = QFileInfo(filename).path();
parseEntries(); parseEntries();
kit.toFile(filename); kit.toFile(filename);
} }

View file

@ -7,7 +7,7 @@ ManualCalibrationDialog::ManualCalibrationDialog(const TraceModel &model, Calibr
ui(new Ui::ManualCalibrationDialog) ui(new Ui::ManualCalibrationDialog)
{ {
ui->setupUi(this); 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->verticalLayout->insertWidget(1, traceSelector, 1.0);
ui->buttonBox->setEnabled(false); ui->buttonBox->setEnabled(false);
connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled); connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled);

View file

@ -64,8 +64,9 @@ void CSVImport::selectTrace(unsigned int index)
void CSVImport::on_browse_clicked() 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) { if (filename.length() > 0) {
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
ui->file->setText(filename); ui->file->setText(filename);
evaluateFile(); evaluateFile();
} }

View file

@ -112,8 +112,9 @@ void TouchstoneImport::setFile(QString filename)
void TouchstoneImport::on_browse_clicked() 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) { if (filename.length() > 0) {
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
ui->file->setText(filename); ui->file->setText(filename);
} }
} }

View file

@ -44,11 +44,12 @@ AmplitudeCalDialog::AmplitudeCalDialog(LibreVNADriver *dev, QWidget *parent) :
connect(ui->saveFile, &QPushButton::clicked, [=](){ connect(ui->saveFile, &QPushButton::clicked, [=](){
auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal"; auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal";
auto fileFilter = QString("Amplitude calibration files (*")+fileEnding+")"; 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.vnacaldata = QFileInfo(filename).path();
if(!filename.endsWith(fileEnding)) { if(!filename.endsWith(fileEnding)) {
filename.append(fileEnding); filename.append(fileEnding);
} }
@ -70,11 +71,12 @@ AmplitudeCalDialog::AmplitudeCalDialog(LibreVNADriver *dev, QWidget *parent) :
connect(ui->loadFile, &QPushButton::clicked, [=](){ connect(ui->loadFile, &QPushButton::clicked, [=](){
auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal"; auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal";
auto fileFilter = QString("Amplitude calibration files (*")+fileEnding+")"; 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.vnacaldata = QFileInfo(filename).path();
ifstream file; ifstream file;
file.open(filename.toStdString()); file.open(filename.toStdString());
if(!file.is_open()) { if(!file.is_open()) {

View file

@ -29,11 +29,12 @@ DevicePacketLogView::DevicePacketLogView(QWidget *parent) :
updateTree(); updateTree();
}); });
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){ 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path();
if(!filename.endsWith(".vnalog")) { if(!filename.endsWith(".vnalog")) {
filename.append(".vnalog"); filename.append(".vnalog");
} }
@ -43,11 +44,12 @@ DevicePacketLogView::DevicePacketLogView(QWidget *parent) :
file.close(); file.close();
}); });
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){ 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path();
ifstream file; ifstream file;
file.open(filename.toStdString()); file.open(filename.toStdString());
if(!file.is_open()) { if(!file.is_open()) {

View file

@ -52,8 +52,9 @@ bool FirmwareUpdateDialog::FirmwareUpdate(LibreVNADriver *dev, QString file)
void FirmwareUpdateDialog::on_bFile_clicked() void FirmwareUpdateDialog::on_bFile_clicked()
{ {
ui->bStart->setEnabled(false); 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) { if (filename.length() > 0) {
Preferences::getInstance().UISettings.Paths.firmware = QFileInfo(filename).path();
ui->lFile->setText(filename); ui->lFile->setText(filename);
reloadFile(); reloadFile();
} }

View file

@ -68,8 +68,9 @@ void DeviceLog::clear()
void DeviceLog::on_bToFile_clicked() 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) { if(filename.length() > 0) {
Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path();
// create file // create file
ofstream file; ofstream file;
file.open(filename.toStdString()); file.open(filename.toStdString());

View file

@ -6,6 +6,7 @@ HEADERS += \
Calibration/LibreCAL/usbdevice.h \ Calibration/LibreCAL/usbdevice.h \
Calibration/calibration.h \ Calibration/calibration.h \
Calibration/calibrationmeasurement.h \ Calibration/calibrationmeasurement.h \
Calibration/calibrationviewdialog.h \
Calibration/calkit.h \ Calibration/calkit.h \
Calibration/calkitdialog.h \ Calibration/calkitdialog.h \
Calibration/calstandard.h \ Calibration/calstandard.h \
@ -172,6 +173,7 @@ SOURCES += \
Calibration/LibreCAL/usbdevice.cpp \ Calibration/LibreCAL/usbdevice.cpp \
Calibration/calibration.cpp \ Calibration/calibration.cpp \
Calibration/calibrationmeasurement.cpp \ Calibration/calibrationmeasurement.cpp \
Calibration/calibrationviewdialog.cpp \
Calibration/calkit.cpp \ Calibration/calkit.cpp \
Calibration/calkitdialog.cpp \ Calibration/calkitdialog.cpp \
Calibration/calstandard.cpp \ Calibration/calstandard.cpp \
@ -342,6 +344,7 @@ FORMS += \
Calibration/LibreCAL/factoryUpdateDialog.ui \ Calibration/LibreCAL/factoryUpdateDialog.ui \
Calibration/LibreCAL/librecaldialog.ui \ Calibration/LibreCAL/librecaldialog.ui \
Calibration/calibrationdialogui.ui \ Calibration/calibrationdialogui.ui \
Calibration/calibrationviewdialog.ui \
Calibration/calkitdialog.ui \ Calibration/calkitdialog.ui \
Calibration/manualcalibrationdialog.ui \ Calibration/manualcalibrationdialog.ui \
CustomWidgets/csvimport.ui \ CustomWidgets/csvimport.ui \
@ -419,5 +422,5 @@ QMAKE_CXXFLAGS += -Wno-deprecated -Wno-deprecated-declarations -Wno-deprecated-c
CONFIG += c++17 CONFIG += c++17
REVISION = $$system(git rev-parse HEAD) REVISION = $$system(git rev-parse HEAD)
DEFINES += GITHASH=\\"\"$$REVISION\\"\" 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 DEFINES -= _UNICODE UNICODE

View file

@ -130,6 +130,7 @@ QString Marker::formatToString(Marker::Format f)
case Format::Inductance: return "Inductance"; case Format::Inductance: return "Inductance";
case Format::QualityFactor: return "Quality Factor"; case Format::QualityFactor: return "Quality Factor";
case Format::GroupDelay: return "Group Delay"; case Format::GroupDelay: return "Group Delay";
case Format::NumberOfPeaks: return "Number of peaks";
case Format::TOI: return "Third order intercept"; case Format::TOI: return "Third order intercept";
case Format::AvgTone: return "Average Tone Level"; case Format::AvgTone: return "Average Tone Level";
case Format::AvgModulationProduct: return "Average Modulation Product Level"; case Format::AvgModulationProduct: return "Average Modulation Product Level";
@ -194,8 +195,6 @@ std::vector<Marker::Format> Marker::applicableFormats()
case Type::Delta: case Type::Delta:
case Type::Maximum: case Type::Maximum:
case Type::Minimum: case Type::Minimum:
case Type::PeakTable:
case Type::NegativePeakTable:
if(Trace::isSAParameter(parentTrace->liveParameter())) { if(Trace::isSAParameter(parentTrace->liveParameter())) {
ret.push_back(Format::dBm); ret.push_back(Format::dBm);
ret.push_back(Format::dBuV); ret.push_back(Format::dBuV);
@ -218,6 +217,10 @@ std::vector<Marker::Format> Marker::applicableFormats()
} }
} }
break; break;
case Type::PeakTable:
case Type::NegativePeakTable:
ret.push_back(Format::NumberOfPeaks);
break;
default: default:
break; break;
} }
@ -228,8 +231,6 @@ std::vector<Marker::Format> Marker::applicableFormats()
case Type::Delta: case Type::Delta:
case Type::Maximum: case Type::Maximum:
case Type::Minimum: case Type::Minimum:
case Type::PeakTable:
case Type::NegativePeakTable:
if(Trace::isSAParameter(parentTrace->liveParameter())) { if(Trace::isSAParameter(parentTrace->liveParameter())) {
ret.push_back(Format::dBm); ret.push_back(Format::dBm);
ret.push_back(Format::dBuV); ret.push_back(Format::dBuV);
@ -253,6 +254,10 @@ std::vector<Marker::Format> Marker::applicableFormats()
} }
} }
break; break;
case Type::PeakTable:
case Type::NegativePeakTable:
ret.push_back(Format::NumberOfPeaks);
break;
case Type::Bandpass: case Type::Bandpass:
ret.push_back(Format::CenterBandwidth); ret.push_back(Format::CenterBandwidth);
ret.push_back(Format::InsertionLoss); ret.push_back(Format::InsertionLoss);
@ -451,7 +456,11 @@ QString Marker::readableData(Format f)
switch(type) { switch(type) {
case Type::PeakTable: case Type::PeakTable:
case Type::NegativePeakTable: 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: { case Type::Delta: {
if(!delta) { if(!delta) {
return "Invalid delta marker"; return "Invalid delta marker";
@ -588,6 +597,7 @@ QString Marker::readableData(Format f)
case Format::maxDeltaPos: case Format::maxDeltaPos:
return "max. Δ+:"+Unit::ToString(maxDeltaPos, "dB", " ", 4); return "max. Δ+:"+Unit::ToString(maxDeltaPos, "dB", " ", 4);
break; break;
case Format::NumberOfPeaks:
case Format::Last: case Format::Last:
return "Invalid"; return "Invalid";
} }
@ -892,10 +902,6 @@ void Marker::deltaDeleted()
void Marker::updateContextmenu() 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 // check if the contextmenu or one of its submenus is currently open
auto *activeWidget = QApplication::activePopupWidget(); auto *activeWidget = QApplication::activePopupWidget();
while (activeWidget) { while (activeWidget) {
@ -910,19 +916,22 @@ void Marker::updateContextmenu()
contextmenu.clear(); contextmenu.clear();
contextmenu.addSection("Marker"); contextmenu.addSection("Marker");
auto typemenu = contextmenu.addMenu("Type"); if(!parent) {
auto typegroup = new QActionGroup(&contextmenu); // type can only be changed for top level markers
for(auto t : getSupportedTypes()) { auto typemenu = contextmenu.addMenu("Type");
auto setTypeAction = new QAction(typeToString(t), typemenu); auto typegroup = new QActionGroup(&contextmenu);
setTypeAction->setCheckable(true); for(auto t : getSupportedTypes()) {
if(t == type) { auto setTypeAction = new QAction(typeToString(t), typemenu);
setTypeAction->setChecked(true); 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"); auto table = contextmenu.addMenu("Data Format in Table");
@ -965,48 +974,51 @@ void Marker::updateContextmenu()
} }
} }
contextmenu.addSeparator(); if(!parent) {
// grouping and deleting is only possible for top level markers
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(); 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() void Marker::traceTypeChanged()
@ -1231,6 +1243,7 @@ void Marker::setType(Marker::Type t)
helper->suffix = h.suffix; helper->suffix = h.suffix;
helper->assignTrace(parentTrace); helper->assignTrace(parentTrace);
helper->setType(h.type); helper->setType(h.type);
helper->setVisible(visible);
helperMarkers.push_back(helper); helperMarkers.push_back(helper);
} }
if(type == Type::Flatness) { 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<int>(&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) QWidget *Marker::getTypeEditor(QAbstractItemDelegate *delegate)
{ {
auto c = new QComboBox; auto c = new QComboBox;
@ -1771,11 +1813,7 @@ void Marker::setVisible(bool visible)
} }
QMenu *Marker::getContextMenu() { QMenu *Marker::getContextMenu() {
if(parent) { return &contextmenu;
return parent->getContextMenu();
} else {
return &contextmenu;
}
} }
void Marker::update() void Marker::update()
@ -1800,19 +1838,28 @@ void Marker::update()
break; break;
case Type::PeakTable: case Type::PeakTable:
case Type::NegativePeakTable: { case Type::NegativePeakTable: {
deleteHelperMarkers();
auto peaks = parentTrace->findPeakFrequencies(100, peakThreshold, 3.0, xmin, xmax, type == Type::NegativePeakTable); auto peaks = parentTrace->findPeakFrequencies(100, peakThreshold, 3.0, xmin, xmax, type == Type::NegativePeakTable);
char suffix = 'a'; for(unsigned int i=0;i<peaks.size();i++) {
for(auto p : peaks) { if(helperMarkers.size() <= i) {
auto helper = new Marker(model, number, this); // needs to create a new helper marker
helper->suffix = suffix; auto helper = new Marker(model, number, this);
helper->assignTrace(parentTrace); helper->suffix = QChar('a' + i);
helper->setPosition(p); helper->assignTrace(parentTrace);
helper->formatTable = formatTable; helper->updateContextmenu();
helper->formatGraph = formatGraph; helper->setVisible(visible);
helper->updateContextmenu(); helperMarkers.push_back(helper);
suffix++; }
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; break;

View file

@ -35,6 +35,8 @@ public:
Inductance, Inductance,
QualityFactor, QualityFactor,
GroupDelay, GroupDelay,
// Peak table
NumberOfPeaks,
// Noise marker parameters // Noise marker parameters
Noise, Noise,
PhaseNoise, PhaseNoise,
@ -109,8 +111,10 @@ public:
Last, Last,
}; };
Type getType() const; Type getType() const;
QWidget *getTraceEditor(QAbstractItemDelegate *delegate = nullptr);
void updateTraceFromEditor(QWidget *w);
QWidget *getTypeEditor(QAbstractItemDelegate *delegate = nullptr); QWidget *getTypeEditor(QAbstractItemDelegate *delegate = nullptr);
void updateTypeFromEditor(QWidget *c); void updateTypeFromEditor(QWidget *w);
SIUnitEdit* getSettingsEditor(); SIUnitEdit* getSettingsEditor();
QWidget *getRestrictEditor(); QWidget *getRestrictEditor();
void adjustSettings(double value); void adjustSettings(double value);

View file

@ -132,11 +132,11 @@ void MarkerModel::markerDataChanged(Marker *m)
// only update the other columns, do not override editor data // only update the other columns, do not override editor data
emit dataChanged(index(row, ColIndexData), index(row, ColIndexData)); emit dataChanged(index(row, ColIndexData), index(row, ColIndexData));
} else { } else {
emit dataChanged(index(row, ColIndexNumber), index(row, ColIndexData)); emit dataChanged(index(row, ColIndexSettings), index(row, ColIndexData));
// also update any potential helper markers // also update any potential helper markers
for(unsigned int i=0;i<m->getHelperMarkers().size();i++) { for(unsigned int i=0;i<m->getHelperMarkers().size();i++) {
auto modelIndex = createIndex(i, 0, m); 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 QWidget *MarkerTraceDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
{ {
auto model = (MarkerModel*) index.model(); auto marker = static_cast<const MarkerModel*>(index.model())->markerFromIndex(index);
auto c = new QComboBox(parent); auto editor = marker->getTraceEditor(const_cast<MarkerTraceDelegate*>(this));
c->setMaximumHeight(rowHeight); editor->setMaximumHeight(rowHeight);
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [c](int) { editor->setParent(parent);
c->clearFocus(); return editor;
});
auto traces = model->getModel().getTraces();
for(auto t : traces) {
MarkerWidgetTraceInfo info;
info.trace = t;
c->addItem(t->name(), QVariant::fromValue(info));
}
return c;
} }
void MarkerTraceDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const void MarkerTraceDelegate::setModelData(QWidget *editor, QAbstractItemModel *, const QModelIndex &index) const
{ {
auto marker = static_cast<const MarkerModel*>(index.model())->markerFromIndex(index); auto marker = static_cast<const MarkerModel*>(index.model())->markerFromIndex(index);
auto c = (QComboBox*) editor; marker->updateTraceFromEditor(editor);
MarkerWidgetTraceInfo markerInfo;
markerInfo.trace = marker->trace();
for(int i=0;i<c->count();i++) {
auto info = qvariant_cast<MarkerWidgetTraceInfo>(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()));
} }
QSize MarkerSettingsDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const QSize MarkerSettingsDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const

View file

@ -14,7 +14,6 @@ class MarkerTraceDelegate : public QStyledItemDelegate
Q_OBJECT Q_OBJECT
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override; QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override;
QWidget *createEditor(QWidget * parent, 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; void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
}; };

View file

@ -15,6 +15,7 @@ Math::Expression::Expression()
{ {
parser = new ParserX(pckCOMMON | pckUNIT | pckCOMPLEX); parser = new ParserX(pckCOMMON | pckUNIT | pckCOMPLEX);
parser->DefineVar("x", Variable(&x)); parser->DefineVar("x", Variable(&x));
dataType = DataType::Invalid;
expressionChanged(); expressionChanged();
} }
@ -37,10 +38,8 @@ void Math::Expression::edit()
{ {
auto d = new QDialog(); auto d = new QDialog();
auto ui = new Ui::ExpressionDialog; auto ui = new Ui::ExpressionDialog;
d->setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(d); ui->setupUi(d);
connect(d, &QDialog::finished, [=](){
delete ui;
});
ui->expEdit->setText(exp); ui->expEdit->setText(exp);
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){ connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
exp = ui->expEdit->text(); exp = ui->expEdit->text();
@ -93,7 +92,7 @@ void Math::Expression::inputSamplesChanged(unsigned int begin, unsigned int end)
data.resize(in.size()); data.resize(in.size());
// sanity check input values // sanity check input values
if(end > 0 && end > in.size()) { if(end > 0 && end > in.size()) {
end = in.size() - 1; end = in.size();
} }
if(end <= begin) { if(end <= begin) {
dataMutex.unlock(); dataMutex.unlock();
@ -119,6 +118,14 @@ void Math::Expression::inputSamplesChanged(unsigned int begin, unsigned int end)
emit outputSamplesChanged(begin, 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() void Math::Expression::expressionChanged()
{ {
if(exp.isEmpty()) { if(exp.isEmpty()) {

View file

@ -24,6 +24,7 @@ public:
public slots: public slots:
void inputSamplesChanged(unsigned int begin, unsigned int end) override; void inputSamplesChanged(unsigned int begin, unsigned int end) override;
virtual void inputTypeChanged(DataType type) override;
private slots: private slots:
void expressionChanged(); void expressionChanged();

View file

@ -130,7 +130,7 @@ public slots:
// some values of the input data have changed, begin/end determine which sample(s) has changed // 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)} 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: signals:
// emit this whenever a sample changed (alternatively, if all samples are about to change, emit outputDataChanged after they have changed) // emit this whenever a sample changed (alternatively, if all samples are about to change, emit outputDataChanged after they have changed)

View file

@ -403,11 +403,12 @@ void EyeDiagramPlot::updateContextMenu()
auto image = new QAction("Save image...", contextmenu); auto image = new QAction("Save image...", contextmenu);
contextmenu->addAction(image); contextmenu->addAction(image);
connect(image, &QAction::triggered, this, [=]() { 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
if(filename.endsWith(".png")) { if(filename.endsWith(".png")) {
filename.chop(4); filename.chop(4);
} }

View file

@ -318,6 +318,10 @@ void Trace::fillFromDatapoints(std::map<QString, Trace *> traceSet, const std::v
{ {
// remove all previous points // remove all previous points
for(auto m : traceSet) { for(auto m : traceSet) {
if(!m.second) {
// no trace, skip
continue;
}
if(!deembedded) { if(!deembedded) {
m.second->clear(); m.second->clear();
} else { } else {
@ -332,6 +336,10 @@ void Trace::fillFromDatapoints(std::map<QString, Trace *> traceSet, const std::v
td.y = m.second; td.y = m.second;
QString measurement = m.first; QString measurement = m.first;
if(traceSet.count(measurement)) { if(traceSet.count(measurement)) {
if(!traceSet[measurement]) {
// no trace, skip
continue;
}
if(!deembedded) { if(!deembedded) {
traceSet[measurement]->addData(td, DataType::Frequency); traceSet[measurement]->addData(td, DataType::Frequency);
} else { } else {
@ -1065,16 +1073,24 @@ std::vector<DeviceDriver::VNAMeasurement> Trace::assembleDatapoints(std::map<QSt
vector<DeviceDriver::VNAMeasurement> ret; vector<DeviceDriver::VNAMeasurement> ret;
// Sanity check traces // Sanity check traces
unsigned int samples = traceSet.begin()->second->size(); unsigned int samples = 0;
auto impedance = traceSet.begin()->second->getReferenceImpedance(); auto impedance = 0;
vector<double> freqs; vector<double> freqs;
for(auto m : traceSet) { for(auto m : traceSet) {
const Trace *t = m.second; 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"; qWarning() << "Selected traces do not have the same size";
return ret; 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"; qWarning() << "Selected traces do not have the same reference impedance";
return ret; return ret;
} }
@ -1098,13 +1114,22 @@ std::vector<DeviceDriver::VNAMeasurement> Trace::assembleDatapoints(std::map<QSt
} }
} }
if(samples == 0 || freqs.size() == 0) {
qWarning() << "Empty trace set";
return ret;
}
// Checks passed, assemble datapoints // Checks passed, assemble datapoints
for(unsigned int i=0;i<samples;i++) { for(unsigned int i=0;i<samples;i++) {
DeviceDriver::VNAMeasurement d; DeviceDriver::VNAMeasurement d;
for(auto m : traceSet) { for(auto m : traceSet) {
QString measurement = m.first; QString measurement = m.first;
const Trace *t = m.second; const Trace *t = m.second;
d.measurements[measurement] = t->sample(i).y; if(t) {
d.measurements[measurement] = t->sample(i).y;
} else {
d.measurements[measurement] = 0.0;
}
} }
d.pointNum = i; d.pointNum = i;
d.frequency = freqs[i]; d.frequency = freqs[i];

View file

@ -72,11 +72,12 @@ void TraceCSVExport::on_buttonBox_accepted()
return; 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
if(!filename.endsWith(".csv")) { if(!filename.endsWith(".csv")) {
filename.append(".csv"); filename.append(".csv");
} }

View file

@ -551,6 +551,10 @@ Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable)
} }
auto markers = t.first->getMarkers(); auto markers = t.first->getMarkers();
for(Marker* m : markers) { for(Marker* m : markers) {
if(!m->isVisible()) {
// can not interact with invisible markers, pretend that there is nothing here
continue;
}
if(!m->isMovable() && onlyMovable) { if(!m->isMovable() && onlyMovable) {
continue; continue;
} }
@ -563,14 +567,7 @@ Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable)
unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y();
if(distance < closestDistance) { if(distance < closestDistance) {
closestDistance = distance; closestDistance = distance;
if(m->getParent()) { closestMarker = m;
closestMarker = m->getParent();
if(closestMarker->getType() == Marker::Type::Flatness) {
closestMarker = m;
}
} else {
closestMarker = m;
}
} }
} }
} }

View file

@ -253,11 +253,12 @@ void TracePolar::updateContextMenu()
auto image = new QAction("Save image...", contextmenu); auto image = new QAction("Save image...", contextmenu);
contextmenu->addAction(image); contextmenu->addAction(image);
connect(image, &QAction::triggered, [=]() { 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
if(filename.endsWith(".png")) { if(filename.endsWith(".png")) {
filename.chop(4); filename.chop(4);
} }

View file

@ -18,22 +18,42 @@ TraceTouchstoneExport::TraceTouchstoneExport(TraceModel &model, QWidget *parent)
ui->selector->setPartialSelectionAllowed(true); ui->selector->setPartialSelectionAllowed(true);
connect(ui->selector, qOverload<>(&TraceSetSelector::selectionChanged), this, &TraceTouchstoneExport::selectionChanged); connect(ui->selector, qOverload<>(&TraceSetSelector::selectionChanged), this, &TraceTouchstoneExport::selectionChanged);
connect(ui->sbPorts, &QSpinBox::valueChanged, this, &TraceTouchstoneExport::setPortNum); connect(ui->sbPorts, &QSpinBox::valueChanged, this, &TraceTouchstoneExport::setPortNum);
// figure out how many ports the user most likely needs // restore the last used settings
unsigned int p; auto& pref = Preferences::getInstance();
for(p=4;p>=1;p--) { auto ports = pref.UISettings.TouchstoneExport.ports;
// do we have a trace name which could indicate such a number of ports? setPortNum(ports);
for(unsigned int i=1;i<=p;i++) { ui->cFormat->setCurrentIndex(pref.UISettings.TouchstoneExport.formatIndex);
auto n1 = "S"+QString::number(p)+QString::number(i); ui->cUnit->setCurrentIndex(pref.UISettings.TouchstoneExport.unitIndex);
auto n2 = "S"+QString::number(i)+QString::number(p);
// 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;i<traces.size();i++) {
for(auto t : model.getTraces()) { for(auto t : model.getTraces()) {
if(t->name().contains(n1) || t->name().contains(n2)) { if(t->name() == traces[i]) {
goto traceFound; 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() TraceTouchstoneExport::~TraceTouchstoneExport()
@ -41,7 +61,7 @@ TraceTouchstoneExport::~TraceTouchstoneExport()
delete ui; 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); return ui->selector->setTrace(portTo, portFrom, t);
} }
@ -66,7 +86,7 @@ bool TraceTouchstoneExport::setPortNum(unsigned int ports)
for(auto t : traces) { for(auto t : traces) {
if(t->name().contains(name)) { if(t->name().contains(name)) {
// this could be the correct trace // this could be the correct trace
setTrace(j, i, t); setTrace(i, j, t);
break; break;
} }
} }
@ -77,17 +97,18 @@ bool TraceTouchstoneExport::setPortNum(unsigned int ports)
void TraceTouchstoneExport::on_buttonBox_accepted() void TraceTouchstoneExport::on_buttonBox_accepted()
{ {
auto ports = ui->sbPorts->value(); unsigned int ports = ui->sbPorts->value();
QString extension = ".s"+QString::number(ports)+"p"; 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) { if(filename.length() > 0) {
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
auto t = Touchstone(ports); auto t = Touchstone(ports);
t.setReferenceImpedance(ui->selector->getReferenceImpedance()); t.setReferenceImpedance(ui->selector->getReferenceImpedance());
// add trace points to touchstone // add trace points to touchstone
for(unsigned int s=0;s<ui->selector->getPoints();s++) { for(unsigned int s=0;s<ui->selector->getPoints();s++) {
Touchstone::Datapoint tData; Touchstone::Datapoint tData;
for(int i=1;i<=ports;i++) { for(unsigned int i=1;i<=ports;i++) {
for(int j=1;j<=ports;j++) { for(unsigned int j=1;j<=ports;j++) {
auto t = ui->selector->getTrace(i, j); auto t = ui->selector->getTrace(i, j);
if(!t) { if(!t) {
// missing trace, set to 0 // missing trace, set to 0
@ -117,6 +138,25 @@ void TraceTouchstoneExport::on_buttonBox_accepted()
} }
t.toFile(filename, unit, format); 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;i<ports*ports;i++) {
auto t = ui->selector->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; delete this;
} }
} }

View file

@ -18,7 +18,7 @@ class TraceTouchstoneExport : public QDialog
public: public:
explicit TraceTouchstoneExport(TraceModel &model, QWidget *parent = nullptr); explicit TraceTouchstoneExport(TraceModel &model, QWidget *parent = nullptr);
~TraceTouchstoneExport(); ~TraceTouchstoneExport();
bool setTrace(int portFrom, int portTo, Trace *t); bool setTrace(int portTo, int portFrom, Trace *t);
bool setPortNum(unsigned int ports); bool setPortNum(unsigned int ports);
private slots: private slots:

View file

@ -213,11 +213,12 @@ void TraceWaterfall::updateContextMenu()
auto image = new QAction("Save image...", contextmenu); auto image = new QAction("Save image...", contextmenu);
contextmenu->addAction(image); contextmenu->addAction(image);
connect(image, &QAction::triggered, [=]() { 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
if(filename.endsWith(".png")) { if(filename.endsWith(".png")) {
filename.chop(4); filename.chop(4);
} }

View file

@ -222,8 +222,9 @@ void TraceWidget::importDialog()
} }
supported.chop(1); supported.chop(1);
supported += ")"; 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()) { if (!filename.isEmpty()) {
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
importFile(filename); importFile(filename);
} }
} }

View file

@ -350,11 +350,12 @@ void TraceXYPlot::updateContextMenu()
auto image = new QAction("Save image...", contextmenu); auto image = new QAction("Save image...", contextmenu);
contextmenu->addAction(image); contextmenu->addAction(image);
connect(image, &QAction::triggered, [=]() { 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
if(filename.endsWith(".png")) { if(filename.endsWith(".png")) {
filename.chop(4); filename.chop(4);
} }

View file

@ -242,11 +242,12 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
removeLine(index); removeLine(index);
}); });
connect(ui->exportLines, &QPushButton::clicked, this, [=](){ 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.limitLines = QFileInfo(filename).path();
if(!filename.endsWith(".limits")) { if(!filename.endsWith(".limits")) {
filename.append(".limits"); filename.append(".limits");
} }
@ -265,7 +266,7 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
}); });
connect(ui->importLines, &QPushButton::clicked, [=](){ 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; ifstream file;
file.open(filename.toStdString()); file.open(filename.toStdString());
if(!file.is_open()) { if(!file.is_open()) {

View file

@ -767,8 +767,9 @@ void MatchingComponent::mouseDoubleClickEvent(QMouseEvent *e)
Q_UNUSED(e); Q_UNUSED(e);
if(type == Type::DefinedThrough || type == Type::DefinedShunt) { if(type == Type::DefinedThrough || type == Type::DefinedShunt) {
// select new touchstone file // 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()) { if (!filename.isEmpty()) {
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
try { try {
*touchstone = Touchstone::fromFile(filename.toStdString()); *touchstone = Touchstone::fromFile(filename.toStdString());
} catch(const std::exception& e) { } catch(const std::exception& e) {

View file

@ -1,4 +1,4 @@
#include "vna.h" #include "vna.h"
#include "unit.h" #include "unit.h"
#include "CustomWidgets/toggleswitch.h" #include "CustomWidgets/toggleswitch.h"
@ -18,6 +18,7 @@
#include "CustomWidgets/informationbox.h" #include "CustomWidgets/informationbox.h"
#include "Deembedding/manualdeembeddingdialog.h" #include "Deembedding/manualdeembeddingdialog.h"
#include "Calibration/manualcalibrationdialog.h" #include "Calibration/manualcalibrationdialog.h"
#include "Calibration/calibrationviewdialog.h"
#include "Calibration/LibreCAL/librecaldialog.h" #include "Calibration/LibreCAL/librecaldialog.h"
#include "Util/util.h" #include "Util/util.h"
#include "Tools/parameters.h" #include "Tools/parameters.h"
@ -129,6 +130,16 @@ VNA::VNA(AppWindow *window, QString name)
calMenu->addSeparator(); 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"); auto calImportTerms = calMenu->addAction("Import error terms as traces");
calImportTerms->setEnabled(false); calImportTerms->setEnabled(false);
connect(calImportTerms, &QAction::triggered, [=](){ connect(calImportTerms, &QAction::triggered, [=](){

View file

@ -229,19 +229,21 @@ void AppWindow::SetupMenu()
connect(ui->actionDisconnect, &QAction::triggered, this, &AppWindow::DisconnectDevice); connect(ui->actionDisconnect, &QAction::triggered, this, &AppWindow::DisconnectDevice);
connect(ui->actionQuit, &QAction::triggered, this, &AppWindow::close); connect(ui->actionQuit, &QAction::triggered, this, &AppWindow::close);
connect(ui->actionSave_setup, &QAction::triggered, [=](){ 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.setup = QFileInfo(filename).path();
SaveSetup(filename); SaveSetup(filename);
}); });
connect(ui->actionLoad_setup, &QAction::triggered, [=](){ 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.setup = QFileInfo(filename).path();
LoadSetup(filename); LoadSetup(filename);
}); });
connect(ui->actionSave_image, &QAction::triggered, [=](){ connect(ui->actionSave_image, &QAction::triggered, [=](){

View file

@ -141,11 +141,12 @@ Mode::Type Mode::TypeFromName(QString s)
void Mode::saveSreenshot() 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()) { if(filename.isEmpty()) {
// aborted selection // aborted selection
return; return;
} }
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
if(filename.endsWith(".png")) { if(filename.endsWith(".png")) {
filename.chop(4); filename.chop(4);
} }

View file

@ -68,7 +68,7 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) :
ui->StartupStack->setCurrentWidget(ui->StartupPageSetupFile); ui->StartupStack->setCurrentWidget(ui->StartupPageSetupFile);
}); });
connect(ui->StartupBrowse, &QPushButton::clicked, [=](){ 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->setUnit("Hz");
ui->StartupSweepStart->setPrefixes(" kMG"); ui->StartupSweepStart->setPrefixes(" kMG");
@ -203,29 +203,31 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) :
emit p->updated(); emit p->updated();
}); });
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){ 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.length() > 0) {
if(!filename.toLower().endsWith(".vnapref")) { Preferences::getInstance().UISettings.Paths.pref = QFileInfo(filename).path();
if(!filename.toLower().endsWith(".vnapref")) {
filename.append(".vnapref"); filename.append(".vnapref");
} }
ofstream file; ofstream file;
file.open(filename.toStdString()); file.open(filename.toStdString());
updateFromGUI(); updateFromGUI();
file << setw(1) << p->toJSON(); file << setw(1) << p->toJSON();
file.close(); file.close();
} }
}); });
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){ 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) { if(filename.length() > 0) {
ifstream file; Preferences::getInstance().UISettings.Paths.pref = QFileInfo(filename).path();
file.open(filename.toStdString()); ifstream file;
nlohmann::json j; file.open(filename.toStdString());
file >> j; nlohmann::json j;
file.close(); file >> j;
p->fromJSON(j); file.close();
setInitialGUIState(); p->fromJSON(j);
emit p->updated(); setInitialGUIState();
emit p->updated();
} }
}); });
connect(ui->AcquisitionLimitTDRCheckbox, &QCheckBox::toggled, [=](bool enabled){ connect(ui->AcquisitionLimitTDRCheckbox, &QCheckBox::toggled, [=](bool enabled){
@ -274,6 +276,7 @@ void PreferencesDialog::setInitialGUIState()
ui->AcquisitionAlwaysExciteBoth->setChecked(p->Acquisition.alwaysExciteAllPorts); ui->AcquisitionAlwaysExciteBoth->setChecked(p->Acquisition.alwaysExciteAllPorts);
ui->AcquisitionAllowSegmentedSweep->setChecked(p->Acquisition.allowSegmentedSweep); ui->AcquisitionAllowSegmentedSweep->setChecked(p->Acquisition.allowSegmentedSweep);
ui->AcquisitionAllowCalStartWithUnstableLibreCALTemperature->setChecked(p->Acquisition.allowUseOfUnstableLibreCALTemp);
ui->AcquisitionAveragingMode->setCurrentIndex(p->Acquisition.useMedianAveraging ? 1 : 0); ui->AcquisitionAveragingMode->setCurrentIndex(p->Acquisition.useMedianAveraging ? 1 : 0);
ui->AcquisitionFullSpanBehavior->setCurrentIndex(p->Acquisition.fullSpanManual ? 1 : 0); ui->AcquisitionFullSpanBehavior->setCurrentIndex(p->Acquisition.fullSpanManual ? 1 : 0);
ui->AcquisitionFullSpanStart->setValue(p->Acquisition.fullSpanStart); ui->AcquisitionFullSpanStart->setValue(p->Acquisition.fullSpanStart);
@ -396,6 +399,7 @@ void PreferencesDialog::updateFromGUI()
p->Acquisition.alwaysExciteAllPorts = ui->AcquisitionAlwaysExciteBoth->isChecked(); p->Acquisition.alwaysExciteAllPorts = ui->AcquisitionAlwaysExciteBoth->isChecked();
p->Acquisition.allowSegmentedSweep = ui->AcquisitionAllowSegmentedSweep->isChecked(); p->Acquisition.allowSegmentedSweep = ui->AcquisitionAllowSegmentedSweep->isChecked();
p->Acquisition.allowUseOfUnstableLibreCALTemp = ui->AcquisitionAllowCalStartWithUnstableLibreCALTemperature->isChecked();
p->Acquisition.useMedianAveraging = ui->AcquisitionAveragingMode->currentIndex() == 1; p->Acquisition.useMedianAveraging = ui->AcquisitionAveragingMode->currentIndex() == 1;
p->Acquisition.fullSpanManual = ui->AcquisitionFullSpanBehavior->currentIndex() == 1; p->Acquisition.fullSpanManual = ui->AcquisitionFullSpanBehavior->currentIndex() == 1;
p->Acquisition.fullSpanStart = ui->AcquisitionFullSpanStart->value(); p->Acquisition.fullSpanStart = ui->AcquisitionFullSpanStart->value();

View file

@ -103,8 +103,11 @@ public:
} SA; } SA;
} Startup; } Startup;
struct { struct {
// VNA settings
bool alwaysExciteAllPorts; bool alwaysExciteAllPorts;
bool allowSegmentedSweep; bool allowSegmentedSweep;
bool allowUseOfUnstableLibreCALTemp;
bool useMedianAveraging; bool useMedianAveraging;
// Full span settings // Full span settings
@ -207,6 +210,26 @@ public:
bool saveTraceData; bool saveTraceData;
bool useNativeDialogs; bool useNativeDialogs;
} Debug; } 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 bool TCPoverride; // in case of manual port specification via command line
@ -257,6 +280,7 @@ private:
{&Startup.SA.averaging, "Startup.SA.averaging", 1}, {&Startup.SA.averaging, "Startup.SA.averaging", 1},
{&Acquisition.alwaysExciteAllPorts, "Acquisition.alwaysExciteBothPorts", true}, {&Acquisition.alwaysExciteAllPorts, "Acquisition.alwaysExciteBothPorts", true},
{&Acquisition.allowSegmentedSweep, "Acquisition.allowSegmentedSweep", true}, {&Acquisition.allowSegmentedSweep, "Acquisition.allowSegmentedSweep", true},
{&Acquisition.allowUseOfUnstableLibreCALTemp, "Acquisition.allowUseOfUnstableLibreCALTemp", true},
{&Acquisition.useMedianAveraging, "Acquisition.useMedianAveraging", false}, {&Acquisition.useMedianAveraging, "Acquisition.useMedianAveraging", false},
{&Acquisition.fullSpanManual, "Acquisition.fullSpanManual", false}, {&Acquisition.fullSpanManual, "Acquisition.fullSpanManual", false},
{&Acquisition.fullSpanStart, "Acquisition.fullSpanStart", 0.0}, {&Acquisition.fullSpanStart, "Acquisition.fullSpanStart", 0.0},
@ -374,6 +398,21 @@ private:
{&Debug.USBlogSizeLimit, "Debug.USBlogSizeLimit", 10000000.0}, {&Debug.USBlogSizeLimit, "Debug.USBlogSizeLimit", 10000000.0},
{&Debug.saveTraceData, "Debug.saveTraceData", false}, {&Debug.saveTraceData, "Debug.saveTraceData", false},
{&Debug.useNativeDialogs, "Debug.useNativeDialogs", true}, {&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", ""},
}}; }};
}; };

View file

@ -98,7 +98,7 @@
</size> </size>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>2</number> <number>1</number>
</property> </property>
<widget class="QWidget" name="Startup"> <widget class="QWidget" name="Startup">
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
@ -112,8 +112,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>522</width> <width>683</width>
<height>945</height> <height>902</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_13"> <layout class="QVBoxLayout" name="verticalLayout_13">
@ -713,8 +713,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>564</width> <width>697</width>
<height>477</height> <height>564</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_21"> <layout class="QVBoxLayout" name="verticalLayout_21">
@ -741,6 +741,22 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_17">
<property name="title">
<string>Electronic Calibration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_25">
<item>
<widget class="QCheckBox" name="AcquisitionAllowCalStartWithUnstableLibreCALTemperature">
<property name="text">
<string>Allow calibration to start before the LibreCAL temperature has stabilized</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -955,7 +971,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>683</width> <width>683</width>
<height>1217</height> <height>1182</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_22"> <layout class="QVBoxLayout" name="verticalLayout_22">
@ -1520,8 +1536,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>602</width> <width>486</width>
<height>628</height> <height>608</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_7"> <layout class="QVBoxLayout" name="verticalLayout_7">
@ -1880,8 +1896,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>168</width> <width>144</width>
<height>127</height> <height>124</height>
</rect> </rect>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_12"> <layout class="QHBoxLayout" name="horizontalLayout_12">
@ -2163,8 +2179,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>258</width> <width>697</width>
<height>241</height> <height>564</height>
</rect> </rect>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_19"> <layout class="QHBoxLayout" name="horizontalLayout_19">

View file

@ -12,6 +12,7 @@ SOURCES += \
../LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp \ ../LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp \
../LibreVNA-GUI/Calibration/calibration.cpp \ ../LibreVNA-GUI/Calibration/calibration.cpp \
../LibreVNA-GUI/Calibration/calibrationmeasurement.cpp \ ../LibreVNA-GUI/Calibration/calibrationmeasurement.cpp \
../LibreVNA-GUI/Calibration/calibrationviewdialog.cpp \
../LibreVNA-GUI/Calibration/calkit.cpp \ ../LibreVNA-GUI/Calibration/calkit.cpp \
../LibreVNA-GUI/Calibration/calkitdialog.cpp \ ../LibreVNA-GUI/Calibration/calkitdialog.cpp \
../LibreVNA-GUI/Calibration/calstandard.cpp \ ../LibreVNA-GUI/Calibration/calstandard.cpp \
@ -155,6 +156,7 @@ SOURCES += \
../LibreVNA-GUI/streamingserver.cpp \ ../LibreVNA-GUI/streamingserver.cpp \
../LibreVNA-GUI/touchstone.cpp \ ../LibreVNA-GUI/touchstone.cpp \
../LibreVNA-GUI/unit.cpp \ ../LibreVNA-GUI/unit.cpp \
calibrationtests.cpp \
ffttests.cpp \ ffttests.cpp \
impedancerenormalizationtests.cpp \ impedancerenormalizationtests.cpp \
main.cpp \ main.cpp \
@ -199,6 +201,7 @@ HEADERS += \
../LibreVNA-GUI/Calibration/LibreCAL/usbdevice.h \ ../LibreVNA-GUI/Calibration/LibreCAL/usbdevice.h \
../LibreVNA-GUI/Calibration/calibration.h \ ../LibreVNA-GUI/Calibration/calibration.h \
../LibreVNA-GUI/Calibration/calibrationmeasurement.h \ ../LibreVNA-GUI/Calibration/calibrationmeasurement.h \
../LibreVNA-GUI/Calibration/calibrationviewdialog.h \
../LibreVNA-GUI/Calibration/calkit.h \ ../LibreVNA-GUI/Calibration/calkit.h \
../LibreVNA-GUI/Calibration/calkitdialog.h \ ../LibreVNA-GUI/Calibration/calkitdialog.h \
../LibreVNA-GUI/Calibration/calstandard.h \ ../LibreVNA-GUI/Calibration/calstandard.h \
@ -354,6 +357,7 @@ HEADERS += \
../LibreVNA-GUI/streamingserver.h \ ../LibreVNA-GUI/streamingserver.h \
../LibreVNA-GUI/touchstone.h \ ../LibreVNA-GUI/touchstone.h \
../LibreVNA-GUI/unit.h \ ../LibreVNA-GUI/unit.h \
calibrationtests.h \
ffttests.h \ ffttests.h \
impedancerenormalizationtests.h \ impedancerenormalizationtests.h \
parametertests.h \ parametertests.h \
@ -376,6 +380,7 @@ FORMS += \
../LibreVNA-GUI/Calibration/LibreCAL/factoryUpdateDialog.ui \ ../LibreVNA-GUI/Calibration/LibreCAL/factoryUpdateDialog.ui \
../LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.ui \ ../LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.ui \
../LibreVNA-GUI/Calibration/calibrationdialogui.ui \ ../LibreVNA-GUI/Calibration/calibrationdialogui.ui \
../LibreVNA-GUI/Calibration/calibrationviewdialog.ui \
../LibreVNA-GUI/Calibration/calkitdialog.ui \ ../LibreVNA-GUI/Calibration/calkitdialog.ui \
../LibreVNA-GUI/Calibration/manualcalibrationdialog.ui \ ../LibreVNA-GUI/Calibration/manualcalibrationdialog.ui \
../LibreVNA-GUI/CustomWidgets/csvimport.ui \ ../LibreVNA-GUI/CustomWidgets/csvimport.ui \
@ -446,6 +451,6 @@ unix:LIBS += -L/usr/lib/
REVISION = $$system(git rev-parse HEAD) REVISION = $$system(git rev-parse HEAD)
DEFINES += GITHASH=\\"\"$$REVISION\\"\" 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 DEFINES -= _UNICODE UNICODE
win32:DEFINES += QMICROZ_LIBRARY win32:DEFINES += QMICROZ_LIBRARY

View file

@ -0,0 +1,136 @@
#include "calibrationtests.h"
#include "calibration.h"
CalibrationTests::CalibrationTests() {}
void CalibrationTests::LinearDetection()
{
// create some measurements
std::vector<CalibrationMeasurement::Base*> 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;i<points;i++) {
double f = startFreq + (stopFreq - startFreq) * i / (points-1);
DeviceDriver::VNAMeasurement meas;
meas.frequency = f;
meas.measurements["S11"] = 0.0;
m[0]->addPoint(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<CalibrationMeasurement::Base*> 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;i<points;i++) {
double f = startFreq * pow(10.0, i * log10(stopFreq / startFreq) / (points - 1));
DeviceDriver::VNAMeasurement meas;
meas.frequency = f;
meas.measurements["S11"] = 0.0;
m[0]->addPoint(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<CalibrationMeasurement::Base*> 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;i<points;i++) {
// one linear measurement, two log measurement
double flin = startFreq + (stopFreq - startFreq) * i / (points-1);
double flog = startFreq * pow(10.0, i * log10(stopFreq / startFreq) / (points - 1));
DeviceDriver::VNAMeasurement measlin;
measlin.frequency = flin;
measlin.measurements["S11"] = 0.0;
DeviceDriver::VNAMeasurement measlog;
measlog.frequency = flog;
measlog.measurements["S11"] = 0.0;
m[0]->addPoint(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);
}

View file

@ -0,0 +1,18 @@
#ifndef CALIBRATIONTESTS_H
#define CALIBRATIONTESTS_H
#include <QtTest>
class CalibrationTests : public QObject
{
Q_OBJECT
public:
CalibrationTests();
private slots:
void LinearDetection();
void LogDetection();
void MixedDetection();
};
#endif // CALIBRATIONTESTS_H

View file

@ -3,6 +3,7 @@
#include "parametertests.h" #include "parametertests.h"
#include "ffttests.h" #include "ffttests.h"
#include "impedancerenormalizationtests.h" #include "impedancerenormalizationtests.h"
#include "calibrationtests.h"
#include <QtTest> #include <QtTest>
@ -16,6 +17,7 @@ int main(int argc, char *argv[])
status |= QTest::qExec(new ParameterTests, argc, argv); status |= QTest::qExec(new ParameterTests, argc, argv);
status |= QTest::qExec(new fftTests, argc, argv); status |= QTest::qExec(new fftTests, argc, argv);
status |= QTest::qExec(new ImpedanceRenormalizationTests, argc, argv); status |= QTest::qExec(new ImpedanceRenormalizationTests, argc, argv);
status |= QTest::qExec(new CalibrationTests, argc, argv);
return status; return status;
} }

View file

@ -44,7 +44,7 @@
<listOptionValue builtIn="false" value="_SNK"/> <listOptionValue builtIn="false" value="_SNK"/>
<listOptionValue builtIn="false" value="FW_MAJOR=1"/> <listOptionValue builtIn="false" value="FW_MAJOR=1"/>
<listOptionValue builtIn="false" value="FW_MINOR=6"/> <listOptionValue builtIn="false" value="FW_MINOR=6"/>
<listOptionValue builtIn="false" value="FW_PATCH=3"/> <listOptionValue builtIn="false" value="FW_PATCH=4"/>
<listOptionValue builtIn="false" value="HW_REVISION='B'"/> <listOptionValue builtIn="false" value="HW_REVISION='B'"/>
<listOptionValue builtIn="false" value="USE_FULL_LL_DRIVER"/> <listOptionValue builtIn="false" value="USE_FULL_LL_DRIVER"/>
<listOptionValue builtIn="false" value="USE_HAL_DRIVER"/> <listOptionValue builtIn="false" value="USE_HAL_DRIVER"/>
@ -91,7 +91,7 @@
<listOptionValue builtIn="false" value="DEBUG"/> <listOptionValue builtIn="false" value="DEBUG"/>
<listOptionValue builtIn="false" value="FW_MAJOR=1"/> <listOptionValue builtIn="false" value="FW_MAJOR=1"/>
<listOptionValue builtIn="false" value="FW_MINOR=6"/> <listOptionValue builtIn="false" value="FW_MINOR=6"/>
<listOptionValue builtIn="false" value="FW_PATCH=3"/> <listOptionValue builtIn="false" value="FW_PATCH=4"/>
<listOptionValue builtIn="false" value="HW_REVISION='B'"/> <listOptionValue builtIn="false" value="HW_REVISION='B'"/>
<listOptionValue builtIn="false" value="USE_FULL_LL_DRIVER"/> <listOptionValue builtIn="false" value="USE_FULL_LL_DRIVER"/>
<listOptionValue builtIn="false" value="USE_HAL_DRIVER"/> <listOptionValue builtIn="false" value="USE_HAL_DRIVER"/>

View file

@ -9,6 +9,7 @@
#define DRIVERS_FLASH_HPP_ #define DRIVERS_FLASH_HPP_
#include "stm.hpp" #include "stm.hpp"
#include "delay.hpp"
class Flash { class Flash {
public: public:
@ -37,6 +38,7 @@ private:
void CS(bool high) { void CS(bool high) {
if(high) { if(high) {
CS_gpio->BSRR = CS_pin; CS_gpio->BSRR = CS_pin;
Delay::us(1);
} else { } else {
CS_gpio->BSRR = CS_pin << 16; CS_gpio->BSRR = CS_pin << 16;
} }

View file

@ -2,6 +2,7 @@
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include "algorithm.hpp"
#define LOG_LEVEL LOG_LEVEL_INFO #define LOG_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE "SI5351" #define LOG_MODULE "SI5351"
@ -50,7 +51,7 @@ bool Si5351C::ConfigureCLKIn(uint32_t clkin_freq) {
return success; 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) { if (frequency < 600000000 || frequency > 900000000) {
LOG_ERR("Requested PLL frequency out of range (600-900MHz): %lu", frequency); LOG_ERR("Requested PLL frequency out of range (600-900MHz): %lu", frequency);
return false; 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)"); LOG_ERR("Calculated divider out of range (15-90)");
return false; 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; FreqPLL[(int) pll] = frequency;
LOG_DEBUG("Setting PLL %c to %luHz", pll==PLL::A ? 'A' : 'B', frequency); LOG_DEBUG("Setting PLL %c to %luHz", pll==PLL::A ? 'A' : 'B', frequency);
return WritePLLConfig(c, pll); 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; ClkConfig c;
c.DivideBy4 = false; c.DivideBy4 = false;
c.IntegerMode = false; c.IntegerMode = false;
@ -84,7 +85,7 @@ bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStreng
c.source = source; c.source = source;
c.strength = strength; c.strength = strength;
uint32_t pllFreq = PLLFreqOverride > 0 ? PLLFreqOverride : FreqPLL[(int) source]; uint32_t pllFreq = FreqPLL[(int) source];
if (clknum > 5) { if (clknum > 5) {
// outputs 6 and 7 are integer dividers only // outputs 6 and 7 are integer dividers only
uint32_t div = pllFreq / frequency; uint32_t div = pllFreq / frequency;
@ -113,7 +114,7 @@ bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStreng
return false; 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); LOG_DEBUG("Setting CLK%d to %luHz", clknum, frequency);
return WriteClkConfig(c, clknum); 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, 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) // see https://www.silabs.com/documents/public/application-notes/AN619.pdf (page 3/6)
uint32_t a = f_pll / f; uint32_t a = f_pll / f;
int32_t f_rem = f_pll - f * a; 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;
uint32_t best_c = (1UL << 20) - 1; uint32_t best_b;
uint32_t best_b = (uint64_t) f_rem * best_c / f; 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 // convert to Si5351C parameters
uint32_t floor = 128 * best_b / best_c; uint32_t floor = 128 * best_b / best_c;
P1 = 128 * a + floor - 512; P1 = 128 * a + floor - 512;

View file

@ -31,8 +31,8 @@ public:
}; };
bool Init(uint32_t clkin_freq = 0); bool Init(uint32_t clkin_freq = 0);
bool ConfigureCLKIn(uint32_t clkin_freq); bool ConfigureCLKIn(uint32_t clkin_freq);
bool SetPLL(PLL pll, uint32_t frequency, PLLSource src); 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, uint32_t PLLFreqOverride = 0); 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 SetBypass(uint8_t clknum, PLLSource source, DriveStrength strength = DriveStrength::mA2);
bool SetCLKtoXTAL(uint8_t clknum); bool SetCLKtoXTAL(uint8_t clknum);
bool SetCLKToCLKIN(uint8_t clknum); bool SetCLKToCLKIN(uint8_t clknum);
@ -48,7 +48,7 @@ public:
bool WriteRawCLKConfig(uint8_t clknum, const uint8_t *config); bool WriteRawCLKConfig(uint8_t clknum, const uint8_t *config);
bool ReadRawCLKConfig(uint8_t clknum, uint8_t *config); bool ReadRawCLKConfig(uint8_t clknum, uint8_t *config);
private: 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 { enum class Reg : uint8_t {
DeviceStatus = 0, DeviceStatus = 0,
InterruptStatusSticky = 1, InterruptStatusSticky = 1,

View file

@ -2,6 +2,7 @@
#include "stm.hpp" #include "stm.hpp"
#include <cmath> #include <cmath>
#include <algorithm>
Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float ratio, uint32_t max_denom) { Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float ratio, uint32_t max_denom) {
RationalApproximation result; RationalApproximation result;
@ -42,3 +43,47 @@ Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float rati
} }
return result; 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);
}

View file

@ -9,6 +9,7 @@ using RationalApproximation = struct _rationalapproximation {
uint32_t denom; uint32_t denom;
}; };
RationalApproximation BestRationalApproximation(RationalApproximation ratio, uint32_t max_denom);
RationalApproximation BestRationalApproximation(float ratio, uint32_t max_denom); RationalApproximation BestRationalApproximation(float ratio, uint32_t max_denom);
} }

View file

@ -189,7 +189,10 @@ bool MAX2871::SetFrequency(uint64_t f) {
LOG_DEBUG("Looking for best fractional match"); LOG_DEBUG("Looking for best fractional match");
float fraction = (float) rem_f / f_PFD; 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) { if (approx.denom == approx.num) {
// got an impossible result due to floating point limitations(?) // got an impossible result due to floating point limitations(?)

View file

@ -34,7 +34,7 @@ void Generator::Setup(Protocol::GeneratorSettings g) {
if(g.frequency < HW::BandSwitchFrequency) { if(g.frequency < HW::BandSwitchFrequency) {
bandSelect = true; bandSelect = true;
FPGA::Disable(FPGA::Periphery::SourceChip); 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); amplitude.lowBandPower);
Si5351.Enable(SiChannel::LowbandSource); Si5351.Enable(SiChannel::LowbandSource);
} else { } else {

View file

@ -16,7 +16,7 @@ void Manual::Setup(Protocol::ManualControl m) {
FPGA::AbortSweep(); FPGA::AbortSweep();
// Configure lowband source // Configure lowband source
if (m.V1.SourceLowEN) { 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); (Si5351C::DriveStrength) m.V1.SourceLowPower);
Si5351.Enable(SiChannel::LowbandSource); Si5351.Enable(SiChannel::LowbandSource);
} else { } else {

View file

@ -94,7 +94,7 @@ static void StartNextSample() {
} }
attenuator = amplitude.attenuator; attenuator = amplitude.attenuator;
if(trackingFreq < HW::BandSwitchFrequency) { 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::SourceChip);
FPGA::Disable(FPGA::Periphery::SourceRF); FPGA::Disable(FPGA::Periphery::SourceRF);
trackingLowband = true; 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) // 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) { if((uint32_t) abs(LO2freq - lastLO2) > actualRBW / 100) {
// Si5351.SetPLL(Si5351C::PLL::B, LO2freq*HW::LO2Multiplier, HW::Ref::getSource()); // 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::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2, false);
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2); Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2, false);
lastLO2 = LO2freq; lastLO2 = LO2freq;
} }
if (s.UseDFT) { if (s.UseDFT) {

View file

@ -87,14 +87,14 @@ static bool setPLLFrequencies(uint64_t f) {
} }
LOFreq = f + HW::getIF1(); LOFreq = f + HW::getIF1();
} }
if(sourceFreq > HW::BandSwitchFrequency) { if(sourceFreq >= HW::BandSwitchFrequency) {
Source.SetFrequency(sourceFreq); Source.SetFrequency(sourceFreq);
} }
LO1.SetFrequency(LOFreq); LO1.SetFrequency(LOFreq);
bool needsRefSwitch = false; bool needsRefSwitch = false;
if(settings.suppressPeaks) { if(settings.suppressPeaks) {
// Integer spurs can cause a small peak. // Integer spurs can cause a small peak.
if(sourceFreq > HW::BandSwitchFrequency) { if(sourceFreq >= HW::BandSwitchFrequency) {
uint32_t sourceDist = Source.DistanceToIntegerSpur(); uint32_t sourceDist = Source.DistanceToIntegerSpur();
if((sourceDist > 0) && (sourceDist < 3 * HW::getIF2())) { if((sourceDist > 0) && (sourceDist < 3 * HW::getIF2())) {
LOG_DEBUG("Source spur at %lu: %lu", (uint32_t) f, sourceDist); 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::Port1LO2);
Si5351.Enable(SiChannel::Port2LO2); Si5351.Enable(SiChannel::Port2LO2);
Si5351.Enable(SiChannel::RefLO2); 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.ResetPLL(Si5351C::PLL::B);
Si5351.WaitForLock(Si5351C::PLL::B, 10); Si5351.WaitForLock(Si5351C::PLL::B, 10);
@ -458,7 +458,7 @@ void VNA::SweepHalted() {
} }
// need the Si5351 as Source // 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; static bool lowbandDisabled = false;
if (pointCnt == 0) { if (pointCnt == 0) {
// First point in sweep, switch to correct source // 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::Source, PLLRefFreqs[sourceRefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8); Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
last_LO2 = HW::getIF1() - HW::getIF2(); 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.ResetPLL(Si5351C::PLL::B);
Si5351.WaitForLock(Si5351C::PLL::B, 10); Si5351.WaitForLock(Si5351C::PLL::B, 10);
HAL_Delay(2); HAL_Delay(2);
@ -514,7 +514,7 @@ void VNA::SweepHalted() {
Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8); Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
} }
if(needs2LOshift(frequency, last_LO2, actualBandwidth, &last_LO2)) { 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.ResetPLL(Si5351C::PLL::B);
Si5351.WaitForLock(Si5351C::PLL::B, 10); 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 // PLL reset causes the 2.LO to turn off briefly and then ramp on back, needs delay before next point

View file

@ -101,7 +101,7 @@ MCU = $(CPU) -mthumb $(FLOAT-ABI) $(FPU)
C_DEFS = \ C_DEFS = \
-DFW_MAJOR=1 \ -DFW_MAJOR=1 \
-DFW_MINOR=6 \ -DFW_MINOR=6 \
-DFW_PATCH=3 \ -DFW_PATCH=4 \
-DDEBUG \ -DDEBUG \
-DUSE_FULL_LL_DRIVER \ -DUSE_FULL_LL_DRIVER \
-DHW_REVISION="'B'" \ -DHW_REVISION="'B'" \