mirror of
https://github.com/jankae/LibreVNA.git
synced 2025-12-06 07:12:10 +01:00
Merge branch 'master' into prototype_testing
This commit is contained in:
commit
8a74eedfa0
17
.github/workflows/Build.yml
vendored
17
.github/workflows/Build.yml
vendored
|
|
@ -88,22 +88,22 @@ jobs:
|
|||
path: Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI
|
||||
|
||||
PC_Application_Windows:
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: msys2/setup-msys2@v2
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: '6.2.0'
|
||||
arch: 'win64_mingw81'
|
||||
version: '6.2.4'
|
||||
arch: 'win64_mingw'
|
||||
|
||||
- name: Download libusb
|
||||
run: |
|
||||
curl -o libusb.7z -L https://github.com/libusb/libusb/releases/download/v1.0.25/libusb-1.0.25.7z
|
||||
7z x libusb.7z -r -olibusb
|
||||
Xcopy /E /I /Y libusb\include ..\Qt\6.2.0\mingw81_64\include
|
||||
Xcopy /E /I /Y libusb\include %QT_ROOT_DIR%\include
|
||||
Xcopy /E /I /Y libusb\MinGW64\static\libusb-1.0.a Software\PC_Application\LibreVNA-GUI
|
||||
shell: cmd
|
||||
|
||||
|
|
@ -134,10 +134,9 @@ jobs:
|
|||
cd Software/PC_Application/LibreVNA-GUI/release
|
||||
del *.o *.cpp
|
||||
windeployqt.exe .
|
||||
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libwinpthread-1.dll .
|
||||
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libgcc_s_seh-1.dll .
|
||||
copy "..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libstdc++-6.dll" .
|
||||
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\Qt6OpenGL.dll .
|
||||
copy %QT_ROOT_DIR%\bin\libwinpthread-1.dll .
|
||||
copy %QT_ROOT_DIR%\bin\libgcc_s_seh-1.dll .
|
||||
copy %QT_ROOT_DIR%\bin\Qt6OpenGL.dll .
|
||||
shell: cmd
|
||||
|
||||
- name: Upload
|
||||
|
|
|
|||
17
.github/workflows/Release_tag_stable.yml
vendored
17
.github/workflows/Release_tag_stable.yml
vendored
|
|
@ -104,22 +104,22 @@ jobs:
|
|||
|
||||
PC_Application_Windows:
|
||||
needs: PC_Application_Ubuntu
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: msys2/setup-msys2@v2
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: '6.2.0'
|
||||
arch: 'win64_mingw81'
|
||||
version: '6.2.4'
|
||||
arch: 'win64_mingw'
|
||||
|
||||
- name: Download libusb
|
||||
run: |
|
||||
curl -o libusb.7z -L https://github.com/libusb/libusb/releases/download/v1.0.25/libusb-1.0.25.7z
|
||||
7z x libusb.7z -r -olibusb
|
||||
Xcopy /E /I /Y libusb\include ..\Qt\6.2.0\mingw81_64\include
|
||||
Xcopy /E /I /Y libusb\include %QT_ROOT_DIR%\include
|
||||
Xcopy /E /I /Y libusb\MinGW64\static\libusb-1.0.a Software\PC_Application\LibreVNA-GUI
|
||||
shell: cmd
|
||||
|
||||
|
|
@ -145,10 +145,9 @@ jobs:
|
|||
cd Software/PC_Application/LibreVNA-GUI/release
|
||||
del *.o *.cpp
|
||||
windeployqt.exe .
|
||||
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libwinpthread-1.dll .
|
||||
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libgcc_s_seh-1.dll .
|
||||
copy "..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\libstdc++-6.dll" .
|
||||
copy ..\..\..\..\..\Qt\6.2.0\mingw81_64\bin\Qt6OpenGL.dll .
|
||||
copy %QT_ROOT_DIR%\bin\libwinpthread-1.dll .
|
||||
copy %QT_ROOT_DIR%\bin\libgcc_s_seh-1.dll .
|
||||
copy %QT_ROOT_DIR%\bin\Qt6OpenGL.dll .
|
||||
shell: cmd
|
||||
|
||||
- name: Zip app
|
||||
|
|
|
|||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -1,6 +1,21 @@
|
|||
# Changelog
|
||||
|
||||
## v1.6.4
|
||||
|
||||
Critical bugfix for the embedded firmware:
|
||||
|
||||
- Fix SPI flash timing, see #315
|
||||
|
||||
Minor improvement for the GUI:
|
||||
|
||||
- Option to add titles to graphs
|
||||
- Show trace names even when only enabled on secondary Y axis
|
||||
- Add x axis variable to the available variables for formulas in "from math" traces
|
||||
|
||||
|
||||
|
||||
## v1.6.3
|
||||
|
||||
Bugfixes and quality of life improvements
|
||||
|
||||
- Windows and macOS: add icon to GUI application
|
||||
|
|
|
|||
|
|
@ -13075,7 +13075,7 @@
|
|||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "MPN" "LM3370SD-4221"
|
||||
(property "MPN" "LM3370SD-3021"
|
||||
(at 101.6 190.5 0)
|
||||
(effects
|
||||
(font
|
||||
|
|
@ -13970,7 +13970,7 @@
|
|||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "MPN" "LM3370SD-4221"
|
||||
(property "MPN" "LM3370SD-3021"
|
||||
(at 101.6 231.14 0)
|
||||
(effects
|
||||
(font
|
||||
|
|
@ -16667,7 +16667,7 @@
|
|||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "MPN" "LM3370SD-4221"
|
||||
(property "MPN" "LM3370SD-3021"
|
||||
(at 208.28 180.34 0)
|
||||
(effects
|
||||
(font
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# LibreVNA
|
||||
|
||||
  
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ import subprocess
|
|||
|
||||
class TestUpdate(TestBase):
|
||||
def test_Update(self):
|
||||
# first update: actually update to the firmware version we want to test
|
||||
self.vna.cmd("DEV:UPDATE ../../combined.vnafw", timeout=60)
|
||||
|
||||
# second update: check that we still have a working firmware update with this version
|
||||
self.vna.cmd("DEV:UPDATE ../../combined.vnafw", timeout=60)
|
||||
|
||||
reported = self.vna.query("DEV:INF:FWREVISION?")
|
||||
major = subprocess.check_output("grep -oP '(?<=FW_MAJOR=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip()
|
||||
minor = subprocess.check_output("grep -oP '(?<=FW_MINOR=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "caldevice.h"
|
||||
#include "usbdevice.h"
|
||||
#include "CustomWidgets/informationbox.h"
|
||||
#include "preferences.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
|
|
@ -234,6 +235,14 @@ void LibreCALDialog::updateCalibrationStartStatus()
|
|||
canStart = validatePortSelection(true);
|
||||
}
|
||||
|
||||
if(canStart) {
|
||||
if(!Preferences::getInstance().Acquisition.allowUseOfUnstableLibreCALTemp && !device->stabilized()) {
|
||||
canStart = false;
|
||||
ui->lCalibrationStatus->setText("LibreCAL temperature unstable");
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
|
||||
}
|
||||
}
|
||||
|
||||
ui->start->setEnabled(canStart);
|
||||
if(canStart) {
|
||||
ui->lCalibrationStatus->setText("Ready to start");
|
||||
|
|
@ -259,6 +268,7 @@ void LibreCALDialog::updateDeviceStatus()
|
|||
ui->lDeviceStatus->setText("Heating up, please wait with calibration");
|
||||
ui->lDeviceStatus->setStyleSheet("QLabel { color : orange; }");
|
||||
}
|
||||
updateCalibrationStartStatus();
|
||||
}
|
||||
|
||||
void LibreCALDialog::determineAutoPorts()
|
||||
|
|
@ -420,6 +430,7 @@ void LibreCALDialog::stopSweep()
|
|||
void LibreCALDialog::startCalibration()
|
||||
{
|
||||
disableUI();
|
||||
busy = true;
|
||||
|
||||
ui->progressCal->setValue(0);
|
||||
ui->lCalibrationStatus->setText("Creating calibration kit from coefficients...");
|
||||
|
|
@ -592,6 +603,7 @@ void LibreCALDialog::startCalibration()
|
|||
disconnect(cal, &Calibration::measurementsUpdated, this, nullptr);
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
|
||||
enableUI();
|
||||
busy = false;
|
||||
break;
|
||||
}
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
|
||||
|
|
@ -609,6 +621,7 @@ void LibreCALDialog::startCalibration()
|
|||
connect(cal, &Calibration::measurementsAborted, this, [=](){
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
|
||||
enableUI();
|
||||
busy = false;
|
||||
ui->lCalibrationStatus->setText("Ready to start");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@ QString Calibration::TypeToString(Calibration::Type type)
|
|||
{
|
||||
switch(type) {
|
||||
case Type::None: return "None";
|
||||
case Type::OSL: return "OSL";
|
||||
case Type::SOLT: return "SOLT";
|
||||
case Type::ThroughNormalization: return "ThroughNormalization";
|
||||
case Type::TRL: return "TRL";
|
||||
|
|
@ -708,19 +709,19 @@ Calibration::Point Calibration::createInitializedPoint(double f) {
|
|||
point.frequency = f;
|
||||
// resize vectors
|
||||
point.D.resize(caltype.usedPorts.size(), 0.0);
|
||||
point.R.resize(caltype.usedPorts.size(), 0.0);
|
||||
point.R.resize(caltype.usedPorts.size(), 1.0);
|
||||
point.S.resize(caltype.usedPorts.size(), 0.0);
|
||||
|
||||
point.L.resize(caltype.usedPorts.size());
|
||||
point.T.resize(caltype.usedPorts.size());
|
||||
point.I.resize(caltype.usedPorts.size());
|
||||
fill(point.L.begin(), point.L.end(), vector<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));
|
||||
return point;
|
||||
}
|
||||
|
||||
Calibration::Point Calibration::computeSOLT(double f)
|
||||
Calibration::Point Calibration::computeOSL(double f)
|
||||
{
|
||||
Point point = createInitializedPoint(f);
|
||||
|
||||
|
|
@ -762,6 +763,13 @@ Calibration::Point Calibration::computeSOLT(double f)
|
|||
auto delta = (l_c * l_m * (o_m - s_m) + o_c * o_m * (s_m - l_m) + s_c * s_m * (l_m - o_m)) / denom;
|
||||
point.R[i] = point.D[i] * point.S[i] - delta;
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
Calibration::Point Calibration::computeSOLT(double f)
|
||||
{
|
||||
Point point = computeOSL(f);
|
||||
|
||||
// calculate forward match and transmission
|
||||
for(unsigned int i=0;i<caltype.usedPorts.size();i++) {
|
||||
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
|
||||
{
|
||||
return unsavedChanges;
|
||||
|
|
@ -1487,11 +1606,12 @@ bool Calibration::toFile(QString filename)
|
|||
{
|
||||
if(filename.isEmpty()) {
|
||||
QString fn = descriptiveCalName();
|
||||
filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", fn, "Calibration files (*.cal)", nullptr, Preferences::QFileDialogOptions());
|
||||
filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.cal + "/" + fn, "Calibration files (*.cal)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return false;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.cal = QFileInfo(filename).path();
|
||||
}
|
||||
|
||||
if(filename.toLower().endsWith(".cal")) {
|
||||
|
|
@ -1511,11 +1631,12 @@ bool Calibration::toFile(QString filename)
|
|||
bool Calibration::fromFile(QString filename)
|
||||
{
|
||||
if(filename.isEmpty()) {
|
||||
filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", "", "Calibration files (*.cal)", nullptr, Preferences::QFileDialogOptions());
|
||||
filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", Preferences::getInstance().UISettings.Paths.cal, "Calibration files (*.cal)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return false;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.cal = QFileInfo(filename).path();
|
||||
}
|
||||
|
||||
// force correct file ending
|
||||
|
|
@ -1604,7 +1725,7 @@ std::vector<Calibration::Type> Calibration::getTypes()
|
|||
return types;
|
||||
}
|
||||
|
||||
bool Calibration::canCompute(Calibration::CalType type, double *startFreq, double *stopFreq, int *points)
|
||||
bool Calibration::canCompute(Calibration::CalType type, double *startFreq, double *stopFreq, int *points, bool *isLog)
|
||||
{
|
||||
using RequiredMeasurements = struct {
|
||||
CalibrationMeasurement::Base::Type type;
|
||||
|
|
@ -1615,6 +1736,14 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl
|
|||
case Type::None:
|
||||
return true; // Always possible to reset the calibration
|
||||
case Type::SOLT:
|
||||
// through measurements between all ports
|
||||
for(unsigned int i=1;i<=type.usedPorts.size();i++) {
|
||||
for(unsigned int j=i+1;j<=type.usedPorts.size();j++) {
|
||||
required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j});
|
||||
}
|
||||
}
|
||||
[[fallthrough]];
|
||||
case Type::OSL:
|
||||
// SOL measurements for every port
|
||||
for(auto p : type.usedPorts) {
|
||||
required.push_back({.type = CalibrationMeasurement::Base::Type::Short, .port1 = p, .port2 = 0});
|
||||
|
|
@ -1627,12 +1756,6 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl
|
|||
required.push_back({.type = CalibrationMeasurement::Base::Type::Load, .port1 = p, .port2 = 0});
|
||||
}
|
||||
}
|
||||
// through measurements between all ports
|
||||
for(unsigned int i=1;i<=type.usedPorts.size();i++) {
|
||||
for(unsigned int j=i+1;j<=type.usedPorts.size();j++) {
|
||||
required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Type::ThroughNormalization:
|
||||
// through measurements between all ports
|
||||
|
|
@ -1674,7 +1797,7 @@ bool Calibration::canCompute(Calibration::CalType type, double *startFreq, doubl
|
|||
foundMeasurements.push_back(meas);
|
||||
}
|
||||
}
|
||||
return hasFrequencyOverlap(foundMeasurements, startFreq, stopFreq, points);
|
||||
return hasFrequencyOverlap(foundMeasurements, startFreq, stopFreq, points, isLog);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1688,16 +1811,23 @@ bool Calibration::compute(Calibration::CalType type)
|
|||
}
|
||||
double start, stop;
|
||||
int numPoints;
|
||||
if(!canCompute(type, &start, &stop, &numPoints)) {
|
||||
bool isLog;
|
||||
if(!canCompute(type, &start, &stop, &numPoints, &isLog)) {
|
||||
return false;
|
||||
}
|
||||
caltype = type;
|
||||
try {
|
||||
points.clear();
|
||||
for(int i=0;i<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;
|
||||
switch(type.type) {
|
||||
case Type::OSL: p = computeOSL(f); break;
|
||||
case Type::SOLT: p = computeSOLT(f); break;
|
||||
case Type::ThroughNormalization: p = computeThroughNormalization(f); break;
|
||||
case Type::TRL: p = computeTRL(f); break;
|
||||
|
|
@ -1726,6 +1856,7 @@ void Calibration::reset()
|
|||
int Calibration::minimumPorts(Calibration::Type type)
|
||||
{
|
||||
switch(type) {
|
||||
case Type::OSL: return 1;
|
||||
case Type::SOLT: return 1;
|
||||
case Type::ThroughNormalization: return 2;
|
||||
case Type::TRL: return 2;
|
||||
|
|
@ -1850,11 +1981,13 @@ void Calibration::deleteMeasurements()
|
|||
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 minFreq = 0;
|
||||
double maxFreq = std::numeric_limits<double>::max();
|
||||
unsigned int logCount = 0;
|
||||
unsigned int linCount = 0;
|
||||
for(auto meas : m) {
|
||||
if(meas->numPoints() < 2) {
|
||||
return false;
|
||||
|
|
@ -1869,6 +2002,38 @@ bool Calibration::hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base *
|
|||
if(resolution < minResolution) {
|
||||
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) {
|
||||
*startFreq = minFreq;
|
||||
|
|
@ -1879,6 +2044,9 @@ bool Calibration::hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base *
|
|||
if(points) {
|
||||
*points = (maxFreq - minFreq) / minResolution + 1;
|
||||
}
|
||||
if(isLog) {
|
||||
*isLog = logCount > linCount;
|
||||
}
|
||||
if(maxFreq > minFreq) {
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ class Calibration : public QObject, public Savable, public SCPINode
|
|||
Q_OBJECT
|
||||
|
||||
friend class LibreCALDialog;
|
||||
friend class CalibrationTests;
|
||||
public:
|
||||
Calibration();
|
||||
|
||||
enum class Type {
|
||||
None,
|
||||
OSL,
|
||||
SOLT,
|
||||
ThroughNormalization,
|
||||
TRL,
|
||||
|
|
@ -62,7 +64,7 @@ public:
|
|||
static std::vector<Type> getTypes();
|
||||
// Checks whether all measurements for a specific calibration are available.
|
||||
// If pointer to the frequency/points variables are given, the start/stop frequency and number of points the calibration will have after the calculation is stored there
|
||||
bool canCompute(CalType type, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr);
|
||||
bool canCompute(CalType type, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr, bool *isLog = nullptr);
|
||||
// Resets the calibration (deletes all measurements and calculated coefficients)
|
||||
void reset();
|
||||
// Returns the minimum number of ports for a given calibration type.
|
||||
|
|
@ -98,6 +100,14 @@ public:
|
|||
QString getValidDevice() const;
|
||||
bool validForDevice(QString serial) const;
|
||||
|
||||
// query whether error terms coefficients are available. Port count starts at 1
|
||||
bool hasDirectivity(unsigned int port);
|
||||
bool hasReflectionTracking(unsigned int port);
|
||||
bool hasSourceMatch(unsigned int port);
|
||||
bool hasReceiverMatch(unsigned int sourcePort, unsigned int receivePort);
|
||||
bool hasTransmissionTracking(unsigned int sourcePort, unsigned int receivePort);
|
||||
bool hasIsolation(unsigned int sourcePort, unsigned int receivePort);
|
||||
|
||||
public slots:
|
||||
// Call once all datapoints of the current span have been added
|
||||
void measurementsComplete();
|
||||
|
|
@ -130,7 +140,7 @@ private:
|
|||
void createDefaultMeasurements(DefaultMeasurements dm);
|
||||
void deleteMeasurements();
|
||||
|
||||
bool hasFrequencyOverlap(std::vector<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
|
||||
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
|
||||
|
|
@ -151,6 +161,7 @@ private:
|
|||
std::vector<Point> points;
|
||||
|
||||
Point createInitializedPoint(double f);
|
||||
Point computeOSL(double f);
|
||||
Point computeSOLT(double f);
|
||||
Point computeThroughNormalization(double f);
|
||||
Point computeTRL(double f);
|
||||
|
|
|
|||
|
|
@ -627,6 +627,10 @@ void CalibrationMeasurement::Isolation::addPoint(const DeviceDriver::VNAMeasurem
|
|||
QString name = meas.first;
|
||||
unsigned int rcv = name.mid(1, 1).toInt() - 1;
|
||||
unsigned int src = name.mid(2, 1).toInt() - 1;
|
||||
if(rcv > 8 || src > 8) {
|
||||
// skip
|
||||
continue;
|
||||
}
|
||||
if(rcv >= p.S.size()) {
|
||||
p.S.resize(rcv + 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ public:
|
|||
virtual double maxUsableFreq() = 0;
|
||||
virtual double minFreq() = 0;
|
||||
virtual double maxFreq() = 0;
|
||||
virtual double getPointFreq(unsigned int p) = 0;
|
||||
virtual unsigned int numPoints() = 0;
|
||||
virtual bool readyForMeasurement() {return false;}
|
||||
virtual bool readyForCalculation() {return false;}
|
||||
|
|
@ -88,6 +89,7 @@ public:
|
|||
virtual double maxUsableFreq() override;
|
||||
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
|
||||
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;}
|
||||
virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;}
|
||||
virtual unsigned int numPoints() override {return points.size();}
|
||||
virtual bool readyForMeasurement() override {return standard != nullptr;}
|
||||
virtual bool readyForCalculation() override {return standard && points.size() > 0;}
|
||||
|
|
@ -207,6 +209,7 @@ public:
|
|||
virtual double maxUsableFreq() override;
|
||||
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
|
||||
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;}
|
||||
virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;}
|
||||
virtual unsigned int numPoints() override {return points.size();}
|
||||
virtual bool readyForMeasurement() override {return standard != nullptr;}
|
||||
virtual bool readyForCalculation() override {return standard && points.size() > 0;}
|
||||
|
|
@ -281,6 +284,7 @@ public:
|
|||
virtual double maxUsableFreq() override {return maxFreq();}
|
||||
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
|
||||
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;}
|
||||
virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;}
|
||||
virtual unsigned int numPoints() override;
|
||||
virtual bool readyForMeasurement() override {return true;}
|
||||
virtual bool readyForCalculation() override {return points.size() > 0;}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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><html><head/><body><p><span style='color: darkgreen;'>Green</span> error terms have been calculated from calibration measurements.</p><p><span style='color: darkred;'>Red</span> error terms are at their default values (either 1 or 0).</p></body></html></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><html><head/><body><p>D: Directivity, R: Reflection tracking, S: Source match, L: Receiver match, T: Transmission tracking, I: Isolation</p></body></html></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>
|
||||
|
|
@ -89,8 +89,9 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
|
|||
accept();
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){
|
||||
auto filename = QFileDialog::getOpenFileName(this, "Open calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(this, "Open calibration kit coefficients", Preferences::getInstance().UISettings.Paths.calkit, "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.calkit = QFileInfo(filename).path();
|
||||
try {
|
||||
kit = Calkit::fromFile(filename);
|
||||
} catch (runtime_error &e) {
|
||||
|
|
@ -103,8 +104,9 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
|
|||
});
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Save calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Save calibration kit coefficients", Preferences::getInstance().UISettings.Paths.calkit, "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.calkit = QFileInfo(filename).path();
|
||||
parseEntries();
|
||||
kit.toFile(filename);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ ManualCalibrationDialog::ManualCalibrationDialog(const TraceModel &model, Calibr
|
|||
ui(new Ui::ManualCalibrationDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
auto traceSelector = new SparamTraceSelector(model, cal->getCaltype().usedPorts);
|
||||
auto traceSelector = new SparamTraceSelector(model, cal->getCaltype().usedPorts, true);
|
||||
ui->verticalLayout->insertWidget(1, traceSelector, 1.0);
|
||||
ui->buttonBox->setEnabled(false);
|
||||
connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled);
|
||||
|
|
|
|||
|
|
@ -64,8 +64,9 @@ void CSVImport::selectTrace(unsigned int index)
|
|||
|
||||
void CSVImport::on_browse_clicked()
|
||||
{
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions());
|
||||
if (filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
|
||||
ui->file->setText(filename);
|
||||
evaluateFile();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,8 +112,9 @@ void TouchstoneImport::setFile(QString filename)
|
|||
|
||||
void TouchstoneImport::on_browse_clicked()
|
||||
{
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, Preferences::QFileDialogOptions());
|
||||
if (filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
|
||||
ui->file->setText(filename);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,11 +44,12 @@ AmplitudeCalDialog::AmplitudeCalDialog(LibreVNADriver *dev, QWidget *parent) :
|
|||
connect(ui->saveFile, &QPushButton::clicked, [=](){
|
||||
auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal";
|
||||
auto fileFilter = QString("Amplitude calibration files (*")+fileEnding+")";
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", "", fileFilter, nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.vnacaldata, fileFilter, nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.vnacaldata = QFileInfo(filename).path();
|
||||
if(!filename.endsWith(fileEnding)) {
|
||||
filename.append(fileEnding);
|
||||
}
|
||||
|
|
@ -70,11 +71,12 @@ AmplitudeCalDialog::AmplitudeCalDialog(LibreVNADriver *dev, QWidget *parent) :
|
|||
connect(ui->loadFile, &QPushButton::clicked, [=](){
|
||||
auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal";
|
||||
auto fileFilter = QString("Amplitude calibration files (*")+fileEnding+")";
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Save calibration data", "", fileFilter, nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.vnacaldata, fileFilter, nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.vnacaldata = QFileInfo(filename).path();
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
if(!file.is_open()) {
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@ DevicePacketLogView::DevicePacketLogView(QWidget *parent) :
|
|||
updateTree();
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){
|
||||
QString filename = QFileDialog::getSaveFileName(nullptr, "Load LibreVNA log data", "", "LibreVNA log files (*.vnalog)", nullptr, Preferences::QFileDialogOptions());
|
||||
QString filename = QFileDialog::getSaveFileName(nullptr, "Save LibreVNA log data", Preferences::getInstance().UISettings.Paths.packetlog, "LibreVNA log files (*.vnalog)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path();
|
||||
if(!filename.endsWith(".vnalog")) {
|
||||
filename.append(".vnalog");
|
||||
}
|
||||
|
|
@ -43,11 +44,12 @@ DevicePacketLogView::DevicePacketLogView(QWidget *parent) :
|
|||
file.close();
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){
|
||||
QString filename = QFileDialog::getOpenFileName(nullptr, "Load LibreVNA log data", "", "LibreVNA log files (*.vnalog)", nullptr, Preferences::QFileDialogOptions());
|
||||
QString filename = QFileDialog::getOpenFileName(nullptr, "Load LibreVNA log data", Preferences::getInstance().UISettings.Paths.packetlog, "LibreVNA log files (*.vnalog)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path();
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
if(!file.is_open()) {
|
||||
|
|
|
|||
|
|
@ -52,8 +52,9 @@ bool FirmwareUpdateDialog::FirmwareUpdate(LibreVNADriver *dev, QString file)
|
|||
void FirmwareUpdateDialog::on_bFile_clicked()
|
||||
{
|
||||
ui->bStart->setEnabled(false);
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open firmware file", "", "Firmware file (*.vnafw)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open firmware file", Preferences::getInstance().UISettings.Paths.firmware, "Firmware file (*.vnafw)", nullptr, Preferences::QFileDialogOptions());
|
||||
if (filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.firmware = QFileInfo(filename).path();
|
||||
ui->lFile->setText(filename);
|
||||
reloadFile();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,8 +68,9 @@ void DeviceLog::clear()
|
|||
|
||||
void DeviceLog::on_bToFile_clicked()
|
||||
{
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Select file for device log", "", "", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Select file for device log", Preferences::getInstance().UISettings.Paths.packetlog, "", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.packetlog = QFileInfo(filename).path();
|
||||
// create file
|
||||
ofstream file;
|
||||
file.open(filename.toStdString());
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ HEADERS += \
|
|||
Calibration/LibreCAL/usbdevice.h \
|
||||
Calibration/calibration.h \
|
||||
Calibration/calibrationmeasurement.h \
|
||||
Calibration/calibrationviewdialog.h \
|
||||
Calibration/calkit.h \
|
||||
Calibration/calkitdialog.h \
|
||||
Calibration/calstandard.h \
|
||||
|
|
@ -172,6 +173,7 @@ SOURCES += \
|
|||
Calibration/LibreCAL/usbdevice.cpp \
|
||||
Calibration/calibration.cpp \
|
||||
Calibration/calibrationmeasurement.cpp \
|
||||
Calibration/calibrationviewdialog.cpp \
|
||||
Calibration/calkit.cpp \
|
||||
Calibration/calkitdialog.cpp \
|
||||
Calibration/calstandard.cpp \
|
||||
|
|
@ -342,6 +344,7 @@ FORMS += \
|
|||
Calibration/LibreCAL/factoryUpdateDialog.ui \
|
||||
Calibration/LibreCAL/librecaldialog.ui \
|
||||
Calibration/calibrationdialogui.ui \
|
||||
Calibration/calibrationviewdialog.ui \
|
||||
Calibration/calkitdialog.ui \
|
||||
Calibration/manualcalibrationdialog.ui \
|
||||
CustomWidgets/csvimport.ui \
|
||||
|
|
@ -419,5 +422,5 @@ QMAKE_CXXFLAGS += -Wno-deprecated -Wno-deprecated-declarations -Wno-deprecated-c
|
|||
CONFIG += c++17
|
||||
REVISION = $$system(git rev-parse HEAD)
|
||||
DEFINES += GITHASH=\\"\"$$REVISION\\"\"
|
||||
DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=3 FW_SUFFIX=""
|
||||
DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=4 FW_SUFFIX=""
|
||||
DEFINES -= _UNICODE UNICODE
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ QString Marker::formatToString(Marker::Format f)
|
|||
case Format::Inductance: return "Inductance";
|
||||
case Format::QualityFactor: return "Quality Factor";
|
||||
case Format::GroupDelay: return "Group Delay";
|
||||
case Format::NumberOfPeaks: return "Number of peaks";
|
||||
case Format::TOI: return "Third order intercept";
|
||||
case Format::AvgTone: return "Average Tone Level";
|
||||
case Format::AvgModulationProduct: return "Average Modulation Product Level";
|
||||
|
|
@ -194,8 +195,6 @@ std::vector<Marker::Format> Marker::applicableFormats()
|
|||
case Type::Delta:
|
||||
case Type::Maximum:
|
||||
case Type::Minimum:
|
||||
case Type::PeakTable:
|
||||
case Type::NegativePeakTable:
|
||||
if(Trace::isSAParameter(parentTrace->liveParameter())) {
|
||||
ret.push_back(Format::dBm);
|
||||
ret.push_back(Format::dBuV);
|
||||
|
|
@ -218,6 +217,10 @@ std::vector<Marker::Format> Marker::applicableFormats()
|
|||
}
|
||||
}
|
||||
break;
|
||||
case Type::PeakTable:
|
||||
case Type::NegativePeakTable:
|
||||
ret.push_back(Format::NumberOfPeaks);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -228,8 +231,6 @@ std::vector<Marker::Format> Marker::applicableFormats()
|
|||
case Type::Delta:
|
||||
case Type::Maximum:
|
||||
case Type::Minimum:
|
||||
case Type::PeakTable:
|
||||
case Type::NegativePeakTable:
|
||||
if(Trace::isSAParameter(parentTrace->liveParameter())) {
|
||||
ret.push_back(Format::dBm);
|
||||
ret.push_back(Format::dBuV);
|
||||
|
|
@ -253,6 +254,10 @@ std::vector<Marker::Format> Marker::applicableFormats()
|
|||
}
|
||||
}
|
||||
break;
|
||||
case Type::PeakTable:
|
||||
case Type::NegativePeakTable:
|
||||
ret.push_back(Format::NumberOfPeaks);
|
||||
break;
|
||||
case Type::Bandpass:
|
||||
ret.push_back(Format::CenterBandwidth);
|
||||
ret.push_back(Format::InsertionLoss);
|
||||
|
|
@ -451,7 +456,11 @@ QString Marker::readableData(Format f)
|
|||
switch(type) {
|
||||
case Type::PeakTable:
|
||||
case Type::NegativePeakTable:
|
||||
return "Found " + QString::number(helperMarkers.size()) + " peaks";
|
||||
switch(f) {
|
||||
case Format::NumberOfPeaks: return "Found " + QString::number(helperMarkers.size()) + " peak" + (helperMarkers.size() == 1 ? "" : "s");
|
||||
default: return "Invalid";
|
||||
}
|
||||
break;
|
||||
case Type::Delta: {
|
||||
if(!delta) {
|
||||
return "Invalid delta marker";
|
||||
|
|
@ -588,6 +597,7 @@ QString Marker::readableData(Format f)
|
|||
case Format::maxDeltaPos:
|
||||
return "max. Δ+:"+Unit::ToString(maxDeltaPos, "dB", " ", 4);
|
||||
break;
|
||||
case Format::NumberOfPeaks:
|
||||
case Format::Last:
|
||||
return "Invalid";
|
||||
}
|
||||
|
|
@ -892,10 +902,6 @@ void Marker::deltaDeleted()
|
|||
|
||||
void Marker::updateContextmenu()
|
||||
{
|
||||
if(parent) {
|
||||
// do nothing, using contextmenu from parent anyway
|
||||
return;
|
||||
}
|
||||
// check if the contextmenu or one of its submenus is currently open
|
||||
auto *activeWidget = QApplication::activePopupWidget();
|
||||
while (activeWidget) {
|
||||
|
|
@ -910,19 +916,22 @@ void Marker::updateContextmenu()
|
|||
contextmenu.clear();
|
||||
contextmenu.addSection("Marker");
|
||||
|
||||
auto typemenu = contextmenu.addMenu("Type");
|
||||
auto typegroup = new QActionGroup(&contextmenu);
|
||||
for(auto t : getSupportedTypes()) {
|
||||
auto setTypeAction = new QAction(typeToString(t), typemenu);
|
||||
setTypeAction->setCheckable(true);
|
||||
if(t == type) {
|
||||
setTypeAction->setChecked(true);
|
||||
if(!parent) {
|
||||
// type can only be changed for top level markers
|
||||
auto typemenu = contextmenu.addMenu("Type");
|
||||
auto typegroup = new QActionGroup(&contextmenu);
|
||||
for(auto t : getSupportedTypes()) {
|
||||
auto setTypeAction = new QAction(typeToString(t), typemenu);
|
||||
setTypeAction->setCheckable(true);
|
||||
if(t == type) {
|
||||
setTypeAction->setChecked(true);
|
||||
}
|
||||
connect(setTypeAction, &QAction::triggered, [=](){
|
||||
setType(t);
|
||||
});
|
||||
typegroup->addAction(setTypeAction);
|
||||
typemenu->addAction(setTypeAction);
|
||||
}
|
||||
connect(setTypeAction, &QAction::triggered, [=](){
|
||||
setType(t);
|
||||
});
|
||||
typegroup->addAction(setTypeAction);
|
||||
typemenu->addAction(setTypeAction);
|
||||
}
|
||||
|
||||
auto table = contextmenu.addMenu("Data Format in Table");
|
||||
|
|
@ -965,48 +974,51 @@ void Marker::updateContextmenu()
|
|||
}
|
||||
}
|
||||
|
||||
contextmenu.addSeparator();
|
||||
|
||||
bool needsSeparator = false;
|
||||
if((applicableGroups.size() > 0 && group == nullptr) || applicableGroups.size() > 1) {
|
||||
// there are other groups available than the one the marker might already be assigned to
|
||||
auto addGroupMenu = new QMenu("Add to linked group");
|
||||
auto groupGroup = new QActionGroup(addGroupMenu);
|
||||
for(auto g : model->getGroups()) {
|
||||
auto addGroupAction = new QAction(QString::number(g->getNumber()));
|
||||
groupGroup->addAction(addGroupAction);
|
||||
addGroupAction->setCheckable(true);
|
||||
if(g == group) {
|
||||
// already assigned to this group
|
||||
addGroupAction->setChecked(true);
|
||||
}
|
||||
connect(addGroupAction, &QAction::triggered, [=](bool checked){
|
||||
if(checked) {
|
||||
g->add(this);
|
||||
}
|
||||
});
|
||||
addGroupMenu->addAction(addGroupAction);
|
||||
}
|
||||
contextmenu.addMenu(addGroupMenu);
|
||||
needsSeparator = true;
|
||||
}
|
||||
if(group != nullptr) {
|
||||
// "remove from group" available
|
||||
auto removeGroup = new QAction("Remove from linked group", &contextmenu);
|
||||
connect(removeGroup, &QAction::triggered, [=](){
|
||||
group->remove(this);
|
||||
});
|
||||
contextmenu.addAction(removeGroup);
|
||||
needsSeparator = true;
|
||||
}
|
||||
if(needsSeparator) {
|
||||
if(!parent) {
|
||||
// grouping and deleting is only possible for top level markers
|
||||
contextmenu.addSeparator();
|
||||
|
||||
bool needsSeparator = false;
|
||||
if((applicableGroups.size() > 0 && group == nullptr) || applicableGroups.size() > 1) {
|
||||
// there are other groups available than the one the marker might already be assigned to
|
||||
auto addGroupMenu = new QMenu("Add to linked group");
|
||||
auto groupGroup = new QActionGroup(addGroupMenu);
|
||||
for(auto g : model->getGroups()) {
|
||||
auto addGroupAction = new QAction(QString::number(g->getNumber()));
|
||||
groupGroup->addAction(addGroupAction);
|
||||
addGroupAction->setCheckable(true);
|
||||
if(g == group) {
|
||||
// already assigned to this group
|
||||
addGroupAction->setChecked(true);
|
||||
}
|
||||
connect(addGroupAction, &QAction::triggered, [=](bool checked){
|
||||
if(checked) {
|
||||
g->add(this);
|
||||
}
|
||||
});
|
||||
addGroupMenu->addAction(addGroupAction);
|
||||
}
|
||||
contextmenu.addMenu(addGroupMenu);
|
||||
needsSeparator = true;
|
||||
}
|
||||
if(group != nullptr) {
|
||||
// "remove from group" available
|
||||
auto removeGroup = new QAction("Remove from linked group", &contextmenu);
|
||||
connect(removeGroup, &QAction::triggered, [=](){
|
||||
group->remove(this);
|
||||
});
|
||||
contextmenu.addAction(removeGroup);
|
||||
needsSeparator = true;
|
||||
}
|
||||
if(needsSeparator) {
|
||||
contextmenu.addSeparator();
|
||||
}
|
||||
|
||||
|
||||
auto deleteAction = new QAction("Delete", &contextmenu);
|
||||
connect(deleteAction, &QAction::triggered, this, &Marker::deleteLater);
|
||||
contextmenu.addAction(deleteAction);
|
||||
}
|
||||
|
||||
|
||||
auto deleteAction = new QAction("Delete", &contextmenu);
|
||||
connect(deleteAction, &QAction::triggered, this, &Marker::deleteLater);
|
||||
contextmenu.addAction(deleteAction);
|
||||
}
|
||||
|
||||
void Marker::traceTypeChanged()
|
||||
|
|
@ -1231,6 +1243,7 @@ void Marker::setType(Marker::Type t)
|
|||
helper->suffix = h.suffix;
|
||||
helper->assignTrace(parentTrace);
|
||||
helper->setType(h.type);
|
||||
helper->setVisible(visible);
|
||||
helperMarkers.push_back(helper);
|
||||
}
|
||||
if(type == Type::Flatness) {
|
||||
|
|
@ -1467,6 +1480,35 @@ void Marker::setNumber(int value)
|
|||
}
|
||||
}
|
||||
|
||||
QWidget *Marker::getTraceEditor(QAbstractItemDelegate *delegate)
|
||||
{
|
||||
auto c = new QComboBox;
|
||||
for(auto t : model->getModel().getTraces()) {
|
||||
c->addItem(t->name());
|
||||
if(parentTrace == t) {
|
||||
// select this item
|
||||
c->setCurrentIndex(c->count() - 1);
|
||||
}
|
||||
}
|
||||
connect(c, qOverload<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)
|
||||
{
|
||||
auto c = new QComboBox;
|
||||
|
|
@ -1771,11 +1813,7 @@ void Marker::setVisible(bool visible)
|
|||
}
|
||||
|
||||
QMenu *Marker::getContextMenu() {
|
||||
if(parent) {
|
||||
return parent->getContextMenu();
|
||||
} else {
|
||||
return &contextmenu;
|
||||
}
|
||||
return &contextmenu;
|
||||
}
|
||||
|
||||
void Marker::update()
|
||||
|
|
@ -1800,19 +1838,28 @@ void Marker::update()
|
|||
break;
|
||||
case Type::PeakTable:
|
||||
case Type::NegativePeakTable: {
|
||||
deleteHelperMarkers();
|
||||
auto peaks = parentTrace->findPeakFrequencies(100, peakThreshold, 3.0, xmin, xmax, type == Type::NegativePeakTable);
|
||||
char suffix = 'a';
|
||||
for(auto p : peaks) {
|
||||
auto helper = new Marker(model, number, this);
|
||||
helper->suffix = suffix;
|
||||
helper->assignTrace(parentTrace);
|
||||
helper->setPosition(p);
|
||||
helper->formatTable = formatTable;
|
||||
helper->formatGraph = formatGraph;
|
||||
helper->updateContextmenu();
|
||||
suffix++;
|
||||
helperMarkers.push_back(helper);
|
||||
for(unsigned int i=0;i<peaks.size();i++) {
|
||||
if(helperMarkers.size() <= i) {
|
||||
// needs to create a new helper marker
|
||||
auto helper = new Marker(model, number, this);
|
||||
helper->suffix = QChar('a' + i);
|
||||
helper->assignTrace(parentTrace);
|
||||
helper->updateContextmenu();
|
||||
helper->setVisible(visible);
|
||||
helperMarkers.push_back(helper);
|
||||
}
|
||||
// update the position of the helper marker
|
||||
helperMarkers[i]->setPosition(peaks[i]);
|
||||
}
|
||||
if(helperMarkers.size() > peaks.size()) {
|
||||
// need to remove some helper markers
|
||||
emit beginRemoveHelperMarkers(this);
|
||||
for(unsigned int i = peaks.size(); i< helperMarkers.size();i++) {
|
||||
delete helperMarkers[i];
|
||||
}
|
||||
helperMarkers.resize(peaks.size());
|
||||
emit endRemoveHelperMarkers(this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ public:
|
|||
Inductance,
|
||||
QualityFactor,
|
||||
GroupDelay,
|
||||
// Peak table
|
||||
NumberOfPeaks,
|
||||
// Noise marker parameters
|
||||
Noise,
|
||||
PhaseNoise,
|
||||
|
|
@ -109,8 +111,10 @@ public:
|
|||
Last,
|
||||
};
|
||||
Type getType() const;
|
||||
QWidget *getTraceEditor(QAbstractItemDelegate *delegate = nullptr);
|
||||
void updateTraceFromEditor(QWidget *w);
|
||||
QWidget *getTypeEditor(QAbstractItemDelegate *delegate = nullptr);
|
||||
void updateTypeFromEditor(QWidget *c);
|
||||
void updateTypeFromEditor(QWidget *w);
|
||||
SIUnitEdit* getSettingsEditor();
|
||||
QWidget *getRestrictEditor();
|
||||
void adjustSettings(double value);
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ void MarkerModel::markerDataChanged(Marker *m)
|
|||
// only update the other columns, do not override editor data
|
||||
emit dataChanged(index(row, ColIndexData), index(row, ColIndexData));
|
||||
} else {
|
||||
emit dataChanged(index(row, ColIndexNumber), index(row, ColIndexData));
|
||||
emit dataChanged(index(row, ColIndexSettings), index(row, ColIndexData));
|
||||
// also update any potential helper markers
|
||||
for(unsigned int i=0;i<m->getHelperMarkers().size();i++) {
|
||||
auto modelIndex = createIndex(i, 0, m);
|
||||
emit dataChanged(index(i, ColIndexNumber, modelIndex), index(i, ColIndexData, modelIndex));
|
||||
emit dataChanged(index(i, ColIndexSettings, modelIndex), index(i, ColIndexData, modelIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -436,41 +436,17 @@ QSize MarkerTraceDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIn
|
|||
|
||||
QWidget *MarkerTraceDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
|
||||
{
|
||||
auto model = (MarkerModel*) index.model();
|
||||
auto c = new QComboBox(parent);
|
||||
c->setMaximumHeight(rowHeight);
|
||||
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [c](int) {
|
||||
c->clearFocus();
|
||||
});
|
||||
auto traces = model->getModel().getTraces();
|
||||
for(auto t : traces) {
|
||||
MarkerWidgetTraceInfo info;
|
||||
info.trace = t;
|
||||
c->addItem(t->name(), QVariant::fromValue(info));
|
||||
}
|
||||
return c;
|
||||
auto marker = static_cast<const MarkerModel*>(index.model())->markerFromIndex(index);
|
||||
auto editor = marker->getTraceEditor(const_cast<MarkerTraceDelegate*>(this));
|
||||
editor->setMaximumHeight(rowHeight);
|
||||
editor->setParent(parent);
|
||||
return editor;
|
||||
}
|
||||
|
||||
void MarkerTraceDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
void MarkerTraceDelegate::setModelData(QWidget *editor, QAbstractItemModel *, const QModelIndex &index) const
|
||||
{
|
||||
auto marker = static_cast<const MarkerModel*>(index.model())->markerFromIndex(index);
|
||||
auto c = (QComboBox*) 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()));
|
||||
marker->updateTraceFromEditor(editor);
|
||||
}
|
||||
|
||||
QSize MarkerSettingsDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ class MarkerTraceDelegate : public QStyledItemDelegate
|
|||
Q_OBJECT
|
||||
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override;
|
||||
QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
|
||||
void setEditorData(QWidget * editor, const QModelIndex & index) const override;
|
||||
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Math::Expression::Expression()
|
|||
{
|
||||
parser = new ParserX(pckCOMMON | pckUNIT | pckCOMPLEX);
|
||||
parser->DefineVar("x", Variable(&x));
|
||||
dataType = DataType::Invalid;
|
||||
expressionChanged();
|
||||
}
|
||||
|
||||
|
|
@ -37,10 +38,8 @@ void Math::Expression::edit()
|
|||
{
|
||||
auto d = new QDialog();
|
||||
auto ui = new Ui::ExpressionDialog;
|
||||
d->setAttribute(Qt::WA_DeleteOnClose);
|
||||
ui->setupUi(d);
|
||||
connect(d, &QDialog::finished, [=](){
|
||||
delete ui;
|
||||
});
|
||||
ui->expEdit->setText(exp);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
|
||||
exp = ui->expEdit->text();
|
||||
|
|
@ -93,7 +92,7 @@ void Math::Expression::inputSamplesChanged(unsigned int begin, unsigned int end)
|
|||
data.resize(in.size());
|
||||
// sanity check input values
|
||||
if(end > 0 && end > in.size()) {
|
||||
end = in.size() - 1;
|
||||
end = in.size();
|
||||
}
|
||||
if(end <= begin) {
|
||||
dataMutex.unlock();
|
||||
|
|
@ -119,6 +118,14 @@ void Math::Expression::inputSamplesChanged(unsigned int begin, unsigned int end)
|
|||
emit outputSamplesChanged(begin, end);
|
||||
}
|
||||
|
||||
void Math::Expression::inputTypeChanged(DataType type)
|
||||
{
|
||||
// call base class slot
|
||||
TraceMath::inputTypeChanged(type);
|
||||
// we need to evaluate the expression again to create the correct variables
|
||||
expressionChanged();
|
||||
}
|
||||
|
||||
void Math::Expression::expressionChanged()
|
||||
{
|
||||
if(exp.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
virtual void inputTypeChanged(DataType type) override;
|
||||
|
||||
private slots:
|
||||
void expressionChanged();
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ public slots:
|
|||
// some values of the input data have changed, begin/end determine which sample(s) has changed
|
||||
virtual void inputSamplesChanged(unsigned int begin, unsigned int end){Q_UNUSED(begin) Q_UNUSED(end)}
|
||||
|
||||
void inputTypeChanged(DataType type);
|
||||
virtual void inputTypeChanged(DataType type);
|
||||
|
||||
signals:
|
||||
// emit this whenever a sample changed (alternatively, if all samples are about to change, emit outputDataChanged after they have changed)
|
||||
|
|
|
|||
|
|
@ -403,11 +403,12 @@ void EyeDiagramPlot::updateContextMenu()
|
|||
auto image = new QAction("Save image...", contextmenu);
|
||||
contextmenu->addAction(image);
|
||||
connect(image, &QAction::triggered, this, [=]() {
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
|
||||
if(filename.endsWith(".png")) {
|
||||
filename.chop(4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,6 +318,10 @@ void Trace::fillFromDatapoints(std::map<QString, Trace *> traceSet, const std::v
|
|||
{
|
||||
// remove all previous points
|
||||
for(auto m : traceSet) {
|
||||
if(!m.second) {
|
||||
// no trace, skip
|
||||
continue;
|
||||
}
|
||||
if(!deembedded) {
|
||||
m.second->clear();
|
||||
} else {
|
||||
|
|
@ -332,6 +336,10 @@ void Trace::fillFromDatapoints(std::map<QString, Trace *> traceSet, const std::v
|
|||
td.y = m.second;
|
||||
QString measurement = m.first;
|
||||
if(traceSet.count(measurement)) {
|
||||
if(!traceSet[measurement]) {
|
||||
// no trace, skip
|
||||
continue;
|
||||
}
|
||||
if(!deembedded) {
|
||||
traceSet[measurement]->addData(td, DataType::Frequency);
|
||||
} else {
|
||||
|
|
@ -1065,16 +1073,24 @@ std::vector<DeviceDriver::VNAMeasurement> Trace::assembleDatapoints(std::map<QSt
|
|||
vector<DeviceDriver::VNAMeasurement> ret;
|
||||
|
||||
// Sanity check traces
|
||||
unsigned int samples = traceSet.begin()->second->size();
|
||||
auto impedance = traceSet.begin()->second->getReferenceImpedance();
|
||||
unsigned int samples = 0;
|
||||
auto impedance = 0;
|
||||
vector<double> freqs;
|
||||
for(auto m : traceSet) {
|
||||
const Trace *t = m.second;
|
||||
if(t->size() != samples) {
|
||||
if(!t) {
|
||||
// trace not valid, skip
|
||||
continue;
|
||||
}
|
||||
if(samples == 0) {
|
||||
samples = t->size();
|
||||
} else if(t->size() != samples) {
|
||||
qWarning() << "Selected traces do not have the same size";
|
||||
return ret;
|
||||
}
|
||||
if(t->getReferenceImpedance() != impedance) {
|
||||
if(impedance == 0) {
|
||||
impedance = t->getReferenceImpedance();
|
||||
} else if(t->getReferenceImpedance() != impedance) {
|
||||
qWarning() << "Selected traces do not have the same reference impedance";
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1098,13 +1114,22 @@ std::vector<DeviceDriver::VNAMeasurement> Trace::assembleDatapoints(std::map<QSt
|
|||
}
|
||||
}
|
||||
|
||||
if(samples == 0 || freqs.size() == 0) {
|
||||
qWarning() << "Empty trace set";
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Checks passed, assemble datapoints
|
||||
for(unsigned int i=0;i<samples;i++) {
|
||||
DeviceDriver::VNAMeasurement d;
|
||||
for(auto m : traceSet) {
|
||||
QString measurement = m.first;
|
||||
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.frequency = freqs[i];
|
||||
|
|
|
|||
|
|
@ -72,11 +72,12 @@ void TraceCSVExport::on_buttonBox_accepted()
|
|||
return;
|
||||
}
|
||||
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", "", "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.data, "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
|
||||
if(!filename.endsWith(".csv")) {
|
||||
filename.append(".csv");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -551,6 +551,10 @@ Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable)
|
|||
}
|
||||
auto markers = t.first->getMarkers();
|
||||
for(Marker* m : markers) {
|
||||
if(!m->isVisible()) {
|
||||
// can not interact with invisible markers, pretend that there is nothing here
|
||||
continue;
|
||||
}
|
||||
if(!m->isMovable() && onlyMovable) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -563,14 +567,7 @@ Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable)
|
|||
unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y();
|
||||
if(distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
if(m->getParent()) {
|
||||
closestMarker = m->getParent();
|
||||
if(closestMarker->getType() == Marker::Type::Flatness) {
|
||||
closestMarker = m;
|
||||
}
|
||||
} else {
|
||||
closestMarker = m;
|
||||
}
|
||||
closestMarker = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,11 +253,12 @@ void TracePolar::updateContextMenu()
|
|||
auto image = new QAction("Save image...", contextmenu);
|
||||
contextmenu->addAction(image);
|
||||
connect(image, &QAction::triggered, [=]() {
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
|
||||
if(filename.endsWith(".png")) {
|
||||
filename.chop(4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,22 +18,42 @@ TraceTouchstoneExport::TraceTouchstoneExport(TraceModel &model, QWidget *parent)
|
|||
ui->selector->setPartialSelectionAllowed(true);
|
||||
connect(ui->selector, qOverload<>(&TraceSetSelector::selectionChanged), this, &TraceTouchstoneExport::selectionChanged);
|
||||
connect(ui->sbPorts, &QSpinBox::valueChanged, this, &TraceTouchstoneExport::setPortNum);
|
||||
// figure out how many ports the user most likely needs
|
||||
unsigned int p;
|
||||
for(p=4;p>=1;p--) {
|
||||
// do we have a trace name which could indicate such a number of ports?
|
||||
for(unsigned int i=1;i<=p;i++) {
|
||||
auto n1 = "S"+QString::number(p)+QString::number(i);
|
||||
auto n2 = "S"+QString::number(i)+QString::number(p);
|
||||
// restore the last used settings
|
||||
auto& pref = Preferences::getInstance();
|
||||
auto ports = pref.UISettings.TouchstoneExport.ports;
|
||||
setPortNum(ports);
|
||||
ui->cFormat->setCurrentIndex(pref.UISettings.TouchstoneExport.formatIndex);
|
||||
ui->cUnit->setCurrentIndex(pref.UISettings.TouchstoneExport.unitIndex);
|
||||
|
||||
// attempt to set the traces that were exported last
|
||||
QStringList traces = pref.UISettings.TouchstoneExport.exportedTraceNames.split(",");
|
||||
if(traces.size() == ports * ports) {
|
||||
// got the correct number of traces
|
||||
for(unsigned int i=0;i<traces.size();i++) {
|
||||
for(auto t : model.getTraces()) {
|
||||
if(t->name().contains(n1) || t->name().contains(n2)) {
|
||||
goto traceFound;
|
||||
if(t->name() == traces[i]) {
|
||||
setTrace(i / ports + 1, i % ports + 1, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
traceFound:
|
||||
setPortNum(p);
|
||||
|
||||
// unsigned int p;
|
||||
// for(p=4;p>=1;p--) {
|
||||
// // do we have a trace name which could indicate such a number of ports?
|
||||
// for(unsigned int i=1;i<=p;i++) {
|
||||
// auto n1 = "S"+QString::number(p)+QString::number(i);
|
||||
// auto n2 = "S"+QString::number(i)+QString::number(p);
|
||||
// for(auto t : model.getTraces()) {
|
||||
// if(t->name().contains(n1) || t->name().contains(n2)) {
|
||||
// goto traceFound;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// traceFound:
|
||||
// setPortNum(p);
|
||||
}
|
||||
|
||||
TraceTouchstoneExport::~TraceTouchstoneExport()
|
||||
|
|
@ -41,7 +61,7 @@ TraceTouchstoneExport::~TraceTouchstoneExport()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
bool TraceTouchstoneExport::setTrace(int portFrom, int portTo, Trace *t)
|
||||
bool TraceTouchstoneExport::setTrace(int portTo, int portFrom, Trace *t)
|
||||
{
|
||||
return ui->selector->setTrace(portTo, portFrom, t);
|
||||
}
|
||||
|
|
@ -66,7 +86,7 @@ bool TraceTouchstoneExport::setPortNum(unsigned int ports)
|
|||
for(auto t : traces) {
|
||||
if(t->name().contains(name)) {
|
||||
// this could be the correct trace
|
||||
setTrace(j, i, t);
|
||||
setTrace(i, j, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -77,17 +97,18 @@ bool TraceTouchstoneExport::setPortNum(unsigned int ports)
|
|||
|
||||
void TraceTouchstoneExport::on_buttonBox_accepted()
|
||||
{
|
||||
auto ports = ui->sbPorts->value();
|
||||
unsigned int ports = ui->sbPorts->value();
|
||||
QString extension = ".s"+QString::number(ports)+"p";
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", "", "Touchstone files (*"+extension+")", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", Preferences::getInstance().UISettings.Paths.data, "Touchstone files (*"+extension+")", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
|
||||
auto t = Touchstone(ports);
|
||||
t.setReferenceImpedance(ui->selector->getReferenceImpedance());
|
||||
// add trace points to touchstone
|
||||
for(unsigned int s=0;s<ui->selector->getPoints();s++) {
|
||||
Touchstone::Datapoint tData;
|
||||
for(int i=1;i<=ports;i++) {
|
||||
for(int j=1;j<=ports;j++) {
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
for(unsigned int j=1;j<=ports;j++) {
|
||||
auto t = ui->selector->getTrace(i, j);
|
||||
if(!t) {
|
||||
// missing trace, set to 0
|
||||
|
|
@ -117,6 +138,25 @@ void TraceTouchstoneExport::on_buttonBox_accepted()
|
|||
}
|
||||
|
||||
t.toFile(filename, unit, format);
|
||||
|
||||
// update preferences for next call
|
||||
auto& pref = Preferences::getInstance();
|
||||
pref.UISettings.TouchstoneExport.ports = ui->sbPorts->value();
|
||||
pref.UISettings.TouchstoneExport.formatIndex = ui->cFormat->currentIndex();
|
||||
pref.UISettings.TouchstoneExport.unitIndex = ui->cUnit->currentIndex();
|
||||
QString traceNames = "";
|
||||
for(unsigned int i=0;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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class TraceTouchstoneExport : public QDialog
|
|||
public:
|
||||
explicit TraceTouchstoneExport(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TraceTouchstoneExport();
|
||||
bool setTrace(int portFrom, int portTo, Trace *t);
|
||||
bool setTrace(int portTo, int portFrom, Trace *t);
|
||||
bool setPortNum(unsigned int ports);
|
||||
|
||||
private slots:
|
||||
|
|
|
|||
|
|
@ -213,11 +213,12 @@ void TraceWaterfall::updateContextMenu()
|
|||
auto image = new QAction("Save image...", contextmenu);
|
||||
contextmenu->addAction(image);
|
||||
connect(image, &QAction::triggered, [=]() {
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
|
||||
if(filename.endsWith(".png")) {
|
||||
filename.chop(4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,8 +222,9 @@ void TraceWidget::importDialog()
|
|||
}
|
||||
supported.chop(1);
|
||||
supported += ")";
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", supported, nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, supported, nullptr, Preferences::QFileDialogOptions());
|
||||
if (!filename.isEmpty()) {
|
||||
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
|
||||
importFile(filename);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,11 +350,12 @@ void TraceXYPlot::updateContextMenu()
|
|||
auto image = new QAction("Save image...", contextmenu);
|
||||
contextmenu->addAction(image);
|
||||
connect(image, &QAction::triggered, [=]() {
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
|
||||
if(filename.endsWith(".png")) {
|
||||
filename.chop(4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,11 +242,12 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
|
|||
removeLine(index);
|
||||
});
|
||||
connect(ui->exportLines, &QPushButton::clicked, this, [=](){
|
||||
QString filename = QFileDialog::getSaveFileName(nullptr, "Save limit lines", "", "Limit files (*.limits)", nullptr, Preferences::QFileDialogOptions());
|
||||
QString filename = QFileDialog::getSaveFileName(nullptr, "Save limit lines", Preferences::getInstance().UISettings.Paths.limitLines, "Limit files (*.limits)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.limitLines = QFileInfo(filename).path();
|
||||
if(!filename.endsWith(".limits")) {
|
||||
filename.append(".limits");
|
||||
}
|
||||
|
|
@ -265,7 +266,7 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
|
|||
|
||||
});
|
||||
connect(ui->importLines, &QPushButton::clicked, [=](){
|
||||
QString filename = QFileDialog::getOpenFileName(nullptr, "Load limit lines", "", "Limit files (*.limits)", nullptr, Preferences::QFileDialogOptions());
|
||||
QString filename = QFileDialog::getOpenFileName(nullptr, "Load limit lines", Preferences::getInstance().UISettings.Paths.limitLines, "Limit files (*.limits)", nullptr, Preferences::QFileDialogOptions());
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
if(!file.is_open()) {
|
||||
|
|
|
|||
|
|
@ -767,8 +767,9 @@ void MatchingComponent::mouseDoubleClickEvent(QMouseEvent *e)
|
|||
Q_UNUSED(e);
|
||||
if(type == Type::DefinedThrough || type == Type::DefinedShunt) {
|
||||
// select new touchstone file
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s2p)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, "Touchstone files (*.s2p)", nullptr, Preferences::QFileDialogOptions());
|
||||
if (!filename.isEmpty()) {
|
||||
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
|
||||
try {
|
||||
*touchstone = Touchstone::fromFile(filename.toStdString());
|
||||
} catch(const std::exception& e) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "vna.h"
|
||||
#include "vna.h"
|
||||
|
||||
#include "unit.h"
|
||||
#include "CustomWidgets/toggleswitch.h"
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
#include "CustomWidgets/informationbox.h"
|
||||
#include "Deembedding/manualdeembeddingdialog.h"
|
||||
#include "Calibration/manualcalibrationdialog.h"
|
||||
#include "Calibration/calibrationviewdialog.h"
|
||||
#include "Calibration/LibreCAL/librecaldialog.h"
|
||||
#include "Util/util.h"
|
||||
#include "Tools/parameters.h"
|
||||
|
|
@ -129,6 +130,16 @@ VNA::VNA(AppWindow *window, QString name)
|
|||
|
||||
calMenu->addSeparator();
|
||||
|
||||
auto calViewTerms = calMenu->addAction("View error term model");
|
||||
connect(calViewTerms, &QAction::triggered, [=](){
|
||||
auto dialog = new CalibrationViewDialog(&cal, DeviceDriver::getInfo(window->getDevice()).Limits.VNA.ports);
|
||||
if(AppWindow::showGUI()) {
|
||||
dialog->show();
|
||||
}
|
||||
});
|
||||
|
||||
calMenu->addSeparator();
|
||||
|
||||
auto calImportTerms = calMenu->addAction("Import error terms as traces");
|
||||
calImportTerms->setEnabled(false);
|
||||
connect(calImportTerms, &QAction::triggered, [=](){
|
||||
|
|
|
|||
|
|
@ -229,19 +229,21 @@ void AppWindow::SetupMenu()
|
|||
connect(ui->actionDisconnect, &QAction::triggered, this, &AppWindow::DisconnectDevice);
|
||||
connect(ui->actionQuit, &QAction::triggered, this, &AppWindow::close);
|
||||
connect(ui->actionSave_setup, &QAction::triggered, [=](){
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save setup data", "", "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save setup data", Preferences::getInstance().UISettings.Paths.setup, "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.setup = QFileInfo(filename).path();
|
||||
SaveSetup(filename);
|
||||
});
|
||||
connect(ui->actionLoad_setup, &QAction::triggered, [=](){
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Load setup data", "", "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Load setup data", Preferences::getInstance().UISettings.Paths.setup, "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.setup = QFileInfo(filename).path();
|
||||
LoadSetup(filename);
|
||||
});
|
||||
connect(ui->actionSave_image, &QAction::triggered, [=](){
|
||||
|
|
|
|||
|
|
@ -141,11 +141,12 @@ Mode::Type Mode::TypeFromName(QString s)
|
|||
|
||||
void Mode::saveSreenshot()
|
||||
{
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", Preferences::getInstance().UISettings.Paths.image, "PNG image files (*.png)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.image = QFileInfo(filename).path();
|
||||
if(filename.endsWith(".png")) {
|
||||
filename.chop(4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) :
|
|||
ui->StartupStack->setCurrentWidget(ui->StartupPageSetupFile);
|
||||
});
|
||||
connect(ui->StartupBrowse, &QPushButton::clicked, [=](){
|
||||
ui->StartupSetupFile->setText(QFileDialog::getOpenFileName(nullptr, "Select startup setup file", "", "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions()));
|
||||
ui->StartupSetupFile->setText(QFileDialog::getOpenFileName(nullptr, "Select startup setup file", Preferences::getInstance().UISettings.Paths.setup, "Setup files (*.setup)", nullptr, Preferences::QFileDialogOptions()));
|
||||
});
|
||||
ui->StartupSweepStart->setUnit("Hz");
|
||||
ui->StartupSweepStart->setPrefixes(" kMG");
|
||||
|
|
@ -203,29 +203,31 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) :
|
|||
emit p->updated();
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Save preferences", "", "LibreVNA preferences files (*.vnapref)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Save preferences", Preferences::getInstance().UISettings.Paths.pref, "LibreVNA preferences files (*.vnapref)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.length() > 0) {
|
||||
if(!filename.toLower().endsWith(".vnapref")) {
|
||||
Preferences::getInstance().UISettings.Paths.pref = QFileInfo(filename).path();
|
||||
if(!filename.toLower().endsWith(".vnapref")) {
|
||||
filename.append(".vnapref");
|
||||
}
|
||||
ofstream file;
|
||||
file.open(filename.toStdString());
|
||||
updateFromGUI();
|
||||
file << setw(1) << p->toJSON();
|
||||
file.close();
|
||||
}
|
||||
ofstream file;
|
||||
file.open(filename.toStdString());
|
||||
updateFromGUI();
|
||||
file << setw(1) << p->toJSON();
|
||||
file.close();
|
||||
}
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){
|
||||
auto filename = QFileDialog::getOpenFileName(this, "Load preferences", "", "LibreVNA preferences files (*.vnapref)", nullptr, Preferences::QFileDialogOptions());
|
||||
auto filename = QFileDialog::getOpenFileName(this, "Load preferences", Preferences::getInstance().UISettings.Paths.pref, "LibreVNA preferences files (*.vnapref)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.length() > 0) {
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
file.close();
|
||||
p->fromJSON(j);
|
||||
setInitialGUIState();
|
||||
emit p->updated();
|
||||
Preferences::getInstance().UISettings.Paths.pref = QFileInfo(filename).path();
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
file.close();
|
||||
p->fromJSON(j);
|
||||
setInitialGUIState();
|
||||
emit p->updated();
|
||||
}
|
||||
});
|
||||
connect(ui->AcquisitionLimitTDRCheckbox, &QCheckBox::toggled, [=](bool enabled){
|
||||
|
|
@ -274,6 +276,7 @@ void PreferencesDialog::setInitialGUIState()
|
|||
|
||||
ui->AcquisitionAlwaysExciteBoth->setChecked(p->Acquisition.alwaysExciteAllPorts);
|
||||
ui->AcquisitionAllowSegmentedSweep->setChecked(p->Acquisition.allowSegmentedSweep);
|
||||
ui->AcquisitionAllowCalStartWithUnstableLibreCALTemperature->setChecked(p->Acquisition.allowUseOfUnstableLibreCALTemp);
|
||||
ui->AcquisitionAveragingMode->setCurrentIndex(p->Acquisition.useMedianAveraging ? 1 : 0);
|
||||
ui->AcquisitionFullSpanBehavior->setCurrentIndex(p->Acquisition.fullSpanManual ? 1 : 0);
|
||||
ui->AcquisitionFullSpanStart->setValue(p->Acquisition.fullSpanStart);
|
||||
|
|
@ -396,6 +399,7 @@ void PreferencesDialog::updateFromGUI()
|
|||
|
||||
p->Acquisition.alwaysExciteAllPorts = ui->AcquisitionAlwaysExciteBoth->isChecked();
|
||||
p->Acquisition.allowSegmentedSweep = ui->AcquisitionAllowSegmentedSweep->isChecked();
|
||||
p->Acquisition.allowUseOfUnstableLibreCALTemp = ui->AcquisitionAllowCalStartWithUnstableLibreCALTemperature->isChecked();
|
||||
p->Acquisition.useMedianAveraging = ui->AcquisitionAveragingMode->currentIndex() == 1;
|
||||
p->Acquisition.fullSpanManual = ui->AcquisitionFullSpanBehavior->currentIndex() == 1;
|
||||
p->Acquisition.fullSpanStart = ui->AcquisitionFullSpanStart->value();
|
||||
|
|
|
|||
|
|
@ -103,8 +103,11 @@ public:
|
|||
} SA;
|
||||
} Startup;
|
||||
struct {
|
||||
// VNA settings
|
||||
bool alwaysExciteAllPorts;
|
||||
bool allowSegmentedSweep;
|
||||
bool allowUseOfUnstableLibreCALTemp;
|
||||
|
||||
bool useMedianAveraging;
|
||||
|
||||
// Full span settings
|
||||
|
|
@ -207,6 +210,26 @@ public:
|
|||
bool saveTraceData;
|
||||
bool useNativeDialogs;
|
||||
} Debug;
|
||||
struct {
|
||||
struct {
|
||||
unsigned int ports;
|
||||
unsigned int formatIndex;
|
||||
unsigned int unitIndex;
|
||||
QString exportedTraceNames;
|
||||
} TouchstoneExport;
|
||||
struct {
|
||||
QString setup;
|
||||
QString cal;
|
||||
QString calkit;
|
||||
QString data;
|
||||
QString image;
|
||||
QString vnacaldata;
|
||||
QString packetlog;
|
||||
QString limitLines;
|
||||
QString pref;
|
||||
QString firmware;
|
||||
} Paths;
|
||||
} UISettings;
|
||||
|
||||
bool TCPoverride; // in case of manual port specification via command line
|
||||
|
||||
|
|
@ -257,6 +280,7 @@ private:
|
|||
{&Startup.SA.averaging, "Startup.SA.averaging", 1},
|
||||
{&Acquisition.alwaysExciteAllPorts, "Acquisition.alwaysExciteBothPorts", true},
|
||||
{&Acquisition.allowSegmentedSweep, "Acquisition.allowSegmentedSweep", true},
|
||||
{&Acquisition.allowUseOfUnstableLibreCALTemp, "Acquisition.allowUseOfUnstableLibreCALTemp", true},
|
||||
{&Acquisition.useMedianAveraging, "Acquisition.useMedianAveraging", false},
|
||||
{&Acquisition.fullSpanManual, "Acquisition.fullSpanManual", false},
|
||||
{&Acquisition.fullSpanStart, "Acquisition.fullSpanStart", 0.0},
|
||||
|
|
@ -374,6 +398,21 @@ private:
|
|||
{&Debug.USBlogSizeLimit, "Debug.USBlogSizeLimit", 10000000.0},
|
||||
{&Debug.saveTraceData, "Debug.saveTraceData", false},
|
||||
{&Debug.useNativeDialogs, "Debug.useNativeDialogs", true},
|
||||
|
||||
{&UISettings.TouchstoneExport.ports, "UISettings.TouchstoneExport.ports", 2},
|
||||
{&UISettings.TouchstoneExport.formatIndex, "UISettings.TouchstoneExport.formatIndex", 2},
|
||||
{&UISettings.TouchstoneExport.unitIndex, "UISettings.TouchstoneExport.unitIndex", 3},
|
||||
{&UISettings.TouchstoneExport.exportedTraceNames, "UISettings.TouchstoneExport.exportedTraceNames", ""},
|
||||
{&UISettings.Paths.setup, "UISettings.Paths.setup", ""},
|
||||
{&UISettings.Paths.cal, "UISettings.Paths.cal", ""},
|
||||
{&UISettings.Paths.calkit, "UISettings.Paths.calkit", ""},
|
||||
{&UISettings.Paths.data, "UISettings.Paths.data", ""},
|
||||
{&UISettings.Paths.image, "UISettings.Paths.image", ""},
|
||||
{&UISettings.Paths.vnacaldata, "UISettings.Paths.vnacaldata", ""},
|
||||
{&UISettings.Paths.packetlog, "UISettings.Paths.packetlog", ""},
|
||||
{&UISettings.Paths.limitLines, "UISettings.Paths.limitLines", ""},
|
||||
{&UISettings.Paths.pref, "UISettings.Paths.pref", ""},
|
||||
{&UISettings.Paths.firmware, "UISettings.Paths.firmware", ""},
|
||||
}};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="Startup">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
|
|
@ -112,8 +112,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>522</width>
|
||||
<height>945</height>
|
||||
<width>683</width>
|
||||
<height>902</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_13">
|
||||
|
|
@ -713,8 +713,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>564</width>
|
||||
<height>477</height>
|
||||
<width>697</width>
|
||||
<height>564</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_21">
|
||||
|
|
@ -741,6 +741,22 @@
|
|||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -955,7 +971,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>683</width>
|
||||
<height>1217</height>
|
||||
<height>1182</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_22">
|
||||
|
|
@ -1520,8 +1536,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>602</width>
|
||||
<height>628</height>
|
||||
<width>486</width>
|
||||
<height>608</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
|
|
@ -1880,8 +1896,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>168</width>
|
||||
<height>127</height>
|
||||
<width>144</width>
|
||||
<height>124</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||
|
|
@ -2163,8 +2179,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>258</width>
|
||||
<height>241</height>
|
||||
<width>697</width>
|
||||
<height>564</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_19">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ SOURCES += \
|
|||
../LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp \
|
||||
../LibreVNA-GUI/Calibration/calibration.cpp \
|
||||
../LibreVNA-GUI/Calibration/calibrationmeasurement.cpp \
|
||||
../LibreVNA-GUI/Calibration/calibrationviewdialog.cpp \
|
||||
../LibreVNA-GUI/Calibration/calkit.cpp \
|
||||
../LibreVNA-GUI/Calibration/calkitdialog.cpp \
|
||||
../LibreVNA-GUI/Calibration/calstandard.cpp \
|
||||
|
|
@ -155,6 +156,7 @@ SOURCES += \
|
|||
../LibreVNA-GUI/streamingserver.cpp \
|
||||
../LibreVNA-GUI/touchstone.cpp \
|
||||
../LibreVNA-GUI/unit.cpp \
|
||||
calibrationtests.cpp \
|
||||
ffttests.cpp \
|
||||
impedancerenormalizationtests.cpp \
|
||||
main.cpp \
|
||||
|
|
@ -199,6 +201,7 @@ HEADERS += \
|
|||
../LibreVNA-GUI/Calibration/LibreCAL/usbdevice.h \
|
||||
../LibreVNA-GUI/Calibration/calibration.h \
|
||||
../LibreVNA-GUI/Calibration/calibrationmeasurement.h \
|
||||
../LibreVNA-GUI/Calibration/calibrationviewdialog.h \
|
||||
../LibreVNA-GUI/Calibration/calkit.h \
|
||||
../LibreVNA-GUI/Calibration/calkitdialog.h \
|
||||
../LibreVNA-GUI/Calibration/calstandard.h \
|
||||
|
|
@ -354,6 +357,7 @@ HEADERS += \
|
|||
../LibreVNA-GUI/streamingserver.h \
|
||||
../LibreVNA-GUI/touchstone.h \
|
||||
../LibreVNA-GUI/unit.h \
|
||||
calibrationtests.h \
|
||||
ffttests.h \
|
||||
impedancerenormalizationtests.h \
|
||||
parametertests.h \
|
||||
|
|
@ -376,6 +380,7 @@ FORMS += \
|
|||
../LibreVNA-GUI/Calibration/LibreCAL/factoryUpdateDialog.ui \
|
||||
../LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.ui \
|
||||
../LibreVNA-GUI/Calibration/calibrationdialogui.ui \
|
||||
../LibreVNA-GUI/Calibration/calibrationviewdialog.ui \
|
||||
../LibreVNA-GUI/Calibration/calkitdialog.ui \
|
||||
../LibreVNA-GUI/Calibration/manualcalibrationdialog.ui \
|
||||
../LibreVNA-GUI/CustomWidgets/csvimport.ui \
|
||||
|
|
@ -446,6 +451,6 @@ unix:LIBS += -L/usr/lib/
|
|||
|
||||
REVISION = $$system(git rev-parse HEAD)
|
||||
DEFINES += GITHASH=\\"\"$$REVISION\\"\"
|
||||
DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=2 FW_SUFFIX=""#\\"\"-alpha.2\\"\"
|
||||
DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=4 FW_SUFFIX=""#\\"\"-alpha.2\\"\"
|
||||
DEFINES -= _UNICODE UNICODE
|
||||
win32:DEFINES += QMICROZ_LIBRARY
|
||||
|
|
|
|||
136
Software/PC_Application/LibreVNA-Test/calibrationtests.cpp
Normal file
136
Software/PC_Application/LibreVNA-Test/calibrationtests.cpp
Normal 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);
|
||||
}
|
||||
18
Software/PC_Application/LibreVNA-Test/calibrationtests.h
Normal file
18
Software/PC_Application/LibreVNA-Test/calibrationtests.h
Normal 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
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
#include "parametertests.h"
|
||||
#include "ffttests.h"
|
||||
#include "impedancerenormalizationtests.h"
|
||||
#include "calibrationtests.h"
|
||||
|
||||
#include <QtTest>
|
||||
|
||||
|
|
@ -16,6 +17,7 @@ int main(int argc, char *argv[])
|
|||
status |= QTest::qExec(new ParameterTests, argc, argv);
|
||||
status |= QTest::qExec(new fftTests, argc, argv);
|
||||
status |= QTest::qExec(new ImpedanceRenormalizationTests, argc, argv);
|
||||
status |= QTest::qExec(new CalibrationTests, argc, argv);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
<listOptionValue builtIn="false" value="_SNK"/>
|
||||
<listOptionValue builtIn="false" value="FW_MAJOR=1"/>
|
||||
<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="USE_FULL_LL_DRIVER"/>
|
||||
<listOptionValue builtIn="false" value="USE_HAL_DRIVER"/>
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
<listOptionValue builtIn="false" value="DEBUG"/>
|
||||
<listOptionValue builtIn="false" value="FW_MAJOR=1"/>
|
||||
<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="USE_FULL_LL_DRIVER"/>
|
||||
<listOptionValue builtIn="false" value="USE_HAL_DRIVER"/>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#define DRIVERS_FLASH_HPP_
|
||||
|
||||
#include "stm.hpp"
|
||||
#include "delay.hpp"
|
||||
|
||||
class Flash {
|
||||
public:
|
||||
|
|
@ -37,6 +38,7 @@ private:
|
|||
void CS(bool high) {
|
||||
if(high) {
|
||||
CS_gpio->BSRR = CS_pin;
|
||||
Delay::us(1);
|
||||
} else {
|
||||
CS_gpio->BSRR = CS_pin << 16;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include "algorithm.hpp"
|
||||
|
||||
#define LOG_LEVEL LOG_LEVEL_INFO
|
||||
#define LOG_MODULE "SI5351"
|
||||
|
|
@ -50,7 +51,7 @@ bool Si5351C::ConfigureCLKIn(uint32_t clkin_freq) {
|
|||
return success;
|
||||
}
|
||||
|
||||
bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src) {
|
||||
bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src, bool exactFrequency) {
|
||||
if (frequency < 600000000 || frequency > 900000000) {
|
||||
LOG_ERR("Requested PLL frequency out of range (600-900MHz): %lu", frequency);
|
||||
return false;
|
||||
|
|
@ -67,14 +68,14 @@ bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src) {
|
|||
LOG_ERR("Calculated divider out of range (15-90)");
|
||||
return false;
|
||||
}
|
||||
FindOptimalDivider(frequency, srcFreq, c.P1, c.P2, c.P3);
|
||||
FindOptimalDivider(frequency, srcFreq, c.P1, c.P2, c.P3, exactFrequency);
|
||||
|
||||
FreqPLL[(int) pll] = frequency;
|
||||
LOG_DEBUG("Setting PLL %c to %luHz", pll==PLL::A ? 'A' : 'B', frequency);
|
||||
return WritePLLConfig(c, pll);
|
||||
}
|
||||
|
||||
bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength, uint32_t PLLFreqOverride) {
|
||||
bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength, bool exactFrequency) {
|
||||
ClkConfig c;
|
||||
c.DivideBy4 = false;
|
||||
c.IntegerMode = false;
|
||||
|
|
@ -84,7 +85,7 @@ bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStreng
|
|||
c.source = source;
|
||||
c.strength = strength;
|
||||
|
||||
uint32_t pllFreq = PLLFreqOverride > 0 ? PLLFreqOverride : FreqPLL[(int) source];
|
||||
uint32_t pllFreq = FreqPLL[(int) source];
|
||||
if (clknum > 5) {
|
||||
// outputs 6 and 7 are integer dividers only
|
||||
uint32_t div = pllFreq / frequency;
|
||||
|
|
@ -113,7 +114,7 @@ bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStreng
|
|||
return false;
|
||||
}
|
||||
}
|
||||
FindOptimalDivider(pllFreq, frequency * c.RDiv, c.P1, c.P2, c.P3);
|
||||
FindOptimalDivider(pllFreq, frequency * c.RDiv, c.P1, c.P2, c.P3, exactFrequency);
|
||||
}
|
||||
LOG_DEBUG("Setting CLK%d to %luHz", clknum, frequency);
|
||||
return WriteClkConfig(c, clknum);
|
||||
|
|
@ -346,13 +347,26 @@ bool Si5351C::ResetPLL(PLL pll) {
|
|||
}
|
||||
|
||||
void Si5351C::FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1,
|
||||
uint32_t &P2, uint32_t &P3) {
|
||||
uint32_t &P2, uint32_t &P3, bool exact) {
|
||||
// see https://www.silabs.com/documents/public/application-notes/AN619.pdf (page 3/6)
|
||||
uint32_t a = f_pll / f;
|
||||
int32_t f_rem = f_pll - f * a;
|
||||
// always using the highest modulus divider results in less than 1Hz deviation for all frequencies, that is good enough
|
||||
uint32_t best_c = (1UL << 20) - 1;
|
||||
uint32_t best_b = (uint64_t) f_rem * best_c / f;
|
||||
uint32_t best_c;
|
||||
uint32_t best_b;
|
||||
if(exact) {
|
||||
// spend the effort to find the best divider possible
|
||||
Algorithm::RationalApproximation ratio;
|
||||
ratio.num = f_rem;
|
||||
ratio.denom = f;
|
||||
auto approx = Algorithm::BestRationalApproximation(ratio, (1UL << 20) - 1);
|
||||
best_c = approx.denom;
|
||||
best_b = approx.num;
|
||||
} else {
|
||||
// just use the highest denominator possible, this is good enough if no exact frequency is required
|
||||
best_c = (1UL << 20) - 1;
|
||||
best_b = (uint64_t) f_rem * best_c / f;
|
||||
}
|
||||
|
||||
// convert to Si5351C parameters
|
||||
uint32_t floor = 128 * best_b / best_c;
|
||||
P1 = 128 * a + floor - 512;
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ public:
|
|||
};
|
||||
bool Init(uint32_t clkin_freq = 0);
|
||||
bool ConfigureCLKIn(uint32_t clkin_freq);
|
||||
bool SetPLL(PLL pll, uint32_t frequency, PLLSource src);
|
||||
bool SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength = DriveStrength::mA2, uint32_t PLLFreqOverride = 0);
|
||||
bool SetPLL(PLL pll, uint32_t frequency, PLLSource src, bool exactFrequency=true);
|
||||
bool SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength = DriveStrength::mA2, bool exactFrequency=true);
|
||||
bool SetBypass(uint8_t clknum, PLLSource source, DriveStrength strength = DriveStrength::mA2);
|
||||
bool SetCLKtoXTAL(uint8_t clknum);
|
||||
bool SetCLKToCLKIN(uint8_t clknum);
|
||||
|
|
@ -48,7 +48,7 @@ public:
|
|||
bool WriteRawCLKConfig(uint8_t clknum, const uint8_t *config);
|
||||
bool ReadRawCLKConfig(uint8_t clknum, uint8_t *config);
|
||||
private:
|
||||
void FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1, uint32_t &P2, uint32_t &P3);
|
||||
void FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1, uint32_t &P2, uint32_t &P3, bool exact);
|
||||
enum class Reg : uint8_t {
|
||||
DeviceStatus = 0,
|
||||
InterruptStatusSticky = 1,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "stm.hpp"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float ratio, uint32_t max_denom) {
|
||||
RationalApproximation result;
|
||||
|
|
@ -42,3 +43,47 @@ Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float rati
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t gcd(uint32_t u, uint32_t v) {
|
||||
if(u==0) {
|
||||
return v;
|
||||
} else if(v==0) {
|
||||
return u;
|
||||
}
|
||||
|
||||
uint8_t i = __builtin_ctz(u);
|
||||
u >>= i;
|
||||
uint8_t j = __builtin_ctz(v);
|
||||
v >>= j;
|
||||
|
||||
uint8_t k = i < j ? i : j;
|
||||
while(true) {
|
||||
if(u > v) {
|
||||
std::swap(u, v);
|
||||
}
|
||||
v -= u;
|
||||
if(v==0) {
|
||||
return u << k;
|
||||
}
|
||||
v >>= __builtin_ctz(v);
|
||||
}
|
||||
}
|
||||
|
||||
Algorithm::RationalApproximation Algorithm::BestRationalApproximation(
|
||||
RationalApproximation ratio, uint32_t max_denom) {
|
||||
if(ratio.denom <= max_denom) {
|
||||
// nothing to do, we can just return the ratio as it is
|
||||
return ratio;
|
||||
}
|
||||
// Try to simplify the ratio.
|
||||
// Find greatest common divider
|
||||
uint32_t div = gcd(ratio.num, ratio.denom);
|
||||
ratio.num /= div;
|
||||
ratio.denom /= div;
|
||||
if(ratio.denom <= max_denom) {
|
||||
// small enough now, can use as is
|
||||
return ratio;
|
||||
}
|
||||
// no good, we need to approximate
|
||||
return Algorithm::BestRationalApproximation((float) ratio.num / ratio.denom, max_denom);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using RationalApproximation = struct _rationalapproximation {
|
|||
uint32_t denom;
|
||||
};
|
||||
|
||||
RationalApproximation BestRationalApproximation(RationalApproximation ratio, uint32_t max_denom);
|
||||
RationalApproximation BestRationalApproximation(float ratio, uint32_t max_denom);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,7 +189,10 @@ bool MAX2871::SetFrequency(uint64_t f) {
|
|||
LOG_DEBUG("Looking for best fractional match");
|
||||
float fraction = (float) rem_f / f_PFD;
|
||||
|
||||
auto approx = Algorithm::BestRationalApproximation(fraction, 4095);
|
||||
Algorithm::RationalApproximation ratio;
|
||||
ratio.num = rem_f;
|
||||
ratio.denom = f_PFD;
|
||||
auto approx = Algorithm::BestRationalApproximation(ratio, 4095);
|
||||
|
||||
if (approx.denom == approx.num) {
|
||||
// got an impossible result due to floating point limitations(?)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ void Generator::Setup(Protocol::GeneratorSettings g) {
|
|||
if(g.frequency < HW::BandSwitchFrequency) {
|
||||
bandSelect = true;
|
||||
FPGA::Disable(FPGA::Periphery::SourceChip);
|
||||
Si5351.SetCLK(SiChannel::LowbandSource, g.frequency, Si5351C::PLL::B,
|
||||
Si5351.SetCLK(SiChannel::LowbandSource, g.frequency, Si5351C::PLL::A,
|
||||
amplitude.lowBandPower);
|
||||
Si5351.Enable(SiChannel::LowbandSource);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ void Manual::Setup(Protocol::ManualControl m) {
|
|||
FPGA::AbortSweep();
|
||||
// Configure lowband source
|
||||
if (m.V1.SourceLowEN) {
|
||||
Si5351.SetCLK(SiChannel::LowbandSource, m.V1.SourceLowFrequency, Si5351C::PLL::B,
|
||||
Si5351.SetCLK(SiChannel::LowbandSource, m.V1.SourceLowFrequency, Si5351C::PLL::A,
|
||||
(Si5351C::DriveStrength) m.V1.SourceLowPower);
|
||||
Si5351.Enable(SiChannel::LowbandSource);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ static void StartNextSample() {
|
|||
}
|
||||
attenuator = amplitude.attenuator;
|
||||
if(trackingFreq < HW::BandSwitchFrequency) {
|
||||
Si5351.SetCLK(SiChannel::LowbandSource, trackingFreq, Si5351C::PLL::B, amplitude.lowBandPower);
|
||||
Si5351.SetCLK(SiChannel::LowbandSource, trackingFreq, Si5351C::PLL::A, amplitude.lowBandPower, false);
|
||||
FPGA::Disable(FPGA::Periphery::SourceChip);
|
||||
FPGA::Disable(FPGA::Periphery::SourceRF);
|
||||
trackingLowband = true;
|
||||
|
|
@ -198,8 +198,8 @@ static void StartNextSample() {
|
|||
// only adjust LO2 PLL if necessary (if the deviation is significantly less than the RBW it does not matter)
|
||||
if((uint32_t) abs(LO2freq - lastLO2) > actualRBW / 100) {
|
||||
// Si5351.SetPLL(Si5351C::PLL::B, LO2freq*HW::LO2Multiplier, HW::Ref::getSource());
|
||||
Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
|
||||
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
|
||||
Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2, false);
|
||||
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2, false);
|
||||
lastLO2 = LO2freq;
|
||||
}
|
||||
if (s.UseDFT) {
|
||||
|
|
|
|||
|
|
@ -87,14 +87,14 @@ static bool setPLLFrequencies(uint64_t f) {
|
|||
}
|
||||
LOFreq = f + HW::getIF1();
|
||||
}
|
||||
if(sourceFreq > HW::BandSwitchFrequency) {
|
||||
if(sourceFreq >= HW::BandSwitchFrequency) {
|
||||
Source.SetFrequency(sourceFreq);
|
||||
}
|
||||
LO1.SetFrequency(LOFreq);
|
||||
bool needsRefSwitch = false;
|
||||
if(settings.suppressPeaks) {
|
||||
// Integer spurs can cause a small peak.
|
||||
if(sourceFreq > HW::BandSwitchFrequency) {
|
||||
if(sourceFreq >= HW::BandSwitchFrequency) {
|
||||
uint32_t sourceDist = Source.DistanceToIntegerSpur();
|
||||
if((sourceDist > 0) && (sourceDist < 3 * HW::getIF2())) {
|
||||
LOG_DEBUG("Source spur at %lu: %lu", (uint32_t) f, sourceDist);
|
||||
|
|
@ -210,7 +210,7 @@ bool VNA::Setup(Protocol::SweepSettings s) {
|
|||
Si5351.Enable(SiChannel::Port1LO2);
|
||||
Si5351.Enable(SiChannel::Port2LO2);
|
||||
Si5351.Enable(SiChannel::RefLO2);
|
||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource());
|
||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false);
|
||||
Si5351.ResetPLL(Si5351C::PLL::B);
|
||||
Si5351.WaitForLock(Si5351C::PLL::B, 10);
|
||||
|
||||
|
|
@ -458,7 +458,7 @@ void VNA::SweepHalted() {
|
|||
}
|
||||
|
||||
// need the Si5351 as Source
|
||||
bool freqSuccess = Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::B, driveStrength);
|
||||
bool freqSuccess = Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::A, driveStrength, false);
|
||||
static bool lowbandDisabled = false;
|
||||
if (pointCnt == 0) {
|
||||
// First point in sweep, switch to correct source
|
||||
|
|
@ -499,7 +499,7 @@ void VNA::SweepHalted() {
|
|||
Si5351.SetCLK(SiChannel::Source, PLLRefFreqs[sourceRefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
||||
Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
||||
last_LO2 = HW::getIF1() - HW::getIF2();
|
||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource());
|
||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false);
|
||||
Si5351.ResetPLL(Si5351C::PLL::B);
|
||||
Si5351.WaitForLock(Si5351C::PLL::B, 10);
|
||||
HAL_Delay(2);
|
||||
|
|
@ -514,7 +514,7 @@ void VNA::SweepHalted() {
|
|||
Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
||||
}
|
||||
if(needs2LOshift(frequency, last_LO2, actualBandwidth, &last_LO2)) {
|
||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource());
|
||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false);
|
||||
Si5351.ResetPLL(Si5351C::PLL::B);
|
||||
Si5351.WaitForLock(Si5351C::PLL::B, 10);
|
||||
// PLL reset causes the 2.LO to turn off briefly and then ramp on back, needs delay before next point
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ MCU = $(CPU) -mthumb $(FLOAT-ABI) $(FPU)
|
|||
C_DEFS = \
|
||||
-DFW_MAJOR=1 \
|
||||
-DFW_MINOR=6 \
|
||||
-DFW_PATCH=3 \
|
||||
-DFW_PATCH=4 \
|
||||
-DDEBUG \
|
||||
-DUSE_FULL_LL_DRIVER \
|
||||
-DHW_REVISION="'B'" \
|
||||
|
|
|
|||
Loading…
Reference in a new issue