Merge branch 'master' into prototype_testing

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

View file

@ -88,22 +88,22 @@ jobs:
path: Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI
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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,3 +1,5 @@
# LibreVNA
![Build workflow status](https://github.com/jankae/LibreVNA/actions/workflows/Build.yml/badge.svg) ![Build workflow status](https://github.com/jankae/LibreVNA/actions/workflows/Test.yml/badge.svg) ![Build workflow status](https://github.com/jankae/LibreVNA/actions/workflows/HIL_Tests.yml/badge.svg)
![LibreVNA](Software/PC_Application/LibreVNA-GUI/resources/banner.png)

View file

@ -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()

View file

@ -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");
});

View file

@ -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 {

View file

@ -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);

View file

@ -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);
}

View file

@ -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;}

View file

@ -0,0 +1,314 @@
#include "calibrationviewdialog.h"
#include "ui_calibrationviewdialog.h"
#include <QGraphicsSimpleTextItem>
#include <QVector2D>
const QColor CalibrationViewDialog::colorNoCal = Qt::darkRed;
const QColor CalibrationViewDialog::colorHasCal = Qt::darkGreen;
CalibrationViewDialog::CalibrationViewDialog(Calibration *cal, unsigned int ports, QWidget *parent)
: QDialog(parent)
, ui(new Ui::CalibrationViewDialog)
, cal(cal)
, ports(ports)
{
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
ui->port->setMaximum(ports);
scene = new QGraphicsScene();
populateScene();
ui->view->setScene(scene);
connect(ui->port, &QSpinBox::valueChanged, this, &CalibrationViewDialog::populateScene);
}
CalibrationViewDialog::~CalibrationViewDialog()
{
delete ui;
}
void CalibrationViewDialog::populateScene()
{
scene->clear();
auto colorF = QApplication::palette().text().color();
auto colorB = QApplication::palette().base().color();
auto pen = QPen(colorF);
auto drawDot = [this](float x, float y, float size, QPen pen = QPen(), QBrush brush = QBrush()) {
scene->addEllipse(x - size/2, y - size/2, size, size, pen, brush);
};
auto drawText = [this](float x, float y, QString s, QColor color, auto alignH = Qt::AlignCenter, auto alignV = Qt::AlignCenter, float rotation = 0.0f) {
auto text = scene->addText(s);
text->setRotation(rotation);
switch(alignH) {
default:
case Qt::AlignLeft: break;
case Qt::AlignCenter:
case Qt::AlignHCenter: x -= text->boundingRect().bottomRight().x()/2; break;
case Qt::AlignRight: x -= text->boundingRect().bottomRight().x(); break;
}
switch(alignV) {
default:
case Qt::AlignTop: break;
case Qt::AlignCenter:
case Qt::AlignVCenter: y -= text->boundingRect().bottomRight().y()/2; break;
case Qt::AlignBottom: y -= text->boundingRect().bottomRight().y(); break;
}
text->setPos(QPointF(x, y));
text->setDefaultTextColor(color);
};
auto drawPath = [this, drawText](QList<QPointF> vertices, QColor color, QString label = QString(), bool arrow = false) {
// draw lines
for(unsigned int i=1;i<vertices.size();i++) {
scene->addLine(QLineF(vertices[i-1], vertices[i]), color);
if(i == vertices.size() / 2) {
// this is the middle segment, add label and arrow if required
auto midPoint = (vertices[i-1]+vertices[i])/2;
QVector2D direction = QVector2D(vertices[i] - vertices[i-1]);
direction.normalize();
auto ortho = QVector2D(-direction.y(), direction.x());
if(arrow) {
auto poly = QPolygonF({
QPointF(midPoint + direction.toPointF()*arrowLength/2),
QPointF(midPoint - direction.toPointF()*arrowLength/2 + ortho.toPointF()*arrowWidth/2),
QPointF(midPoint - direction.toPointF()*arrowLength/2 - ortho.toPointF()*arrowWidth/2)
});
scene->addPolygon(poly, color, color);
}
if(!label.isEmpty()) {
auto pos = midPoint;
if(label.startsWith("_")) {
label.remove(0, 1);
pos -= ortho.toPointF() * labelDistance;
} else {
pos += ortho.toPointF() * labelDistance;
}
auto alignH = abs(direction.x()) > abs(direction.y()) ? Qt::AlignCenter : Qt::AlignLeft;
auto alignV = abs(direction.y()) > abs(direction.x()) ? Qt::AlignCenter : Qt::AlignTop;
drawText(pos.x(), pos.y(), label, color, alignH, alignV);
}
}
}
};
auto DUTwidth = 2 * ports * pathSpacing;
auto DUTstartX = ports * pathSpacing + 2*boxSpacing;
// set the overall scene size
scene->setSceneRect(0, 0, marginLeft + DUTstartX + DUTwidth + marginRight, marginTop + ports * portHeight + marginBottom);
// create the DUT
// rectangle
scene->addRect(marginLeft + DUTstartX, marginTop, DUTwidth, ports * portHeight, pen);
drawText(marginLeft + DUTstartX + DUTwidth/2, marginTop, "DUT", colorF, Qt::AlignCenter, Qt::AlignTop);
// ports
for(unsigned int i=1;i<=ports;i++) {
// input marker
drawDot(marginLeft + DUTstartX, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, portSize, colorF, colorB);
// output marker
drawDot(marginLeft + DUTstartX, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, portSize, colorF, colorB);
}
// the reflection path
drawPath({
QPointF(marginLeft + DUTstartX + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
QPointF(marginLeft + DUTstartX + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
}, colorF, "_S"+QString::number(ui->port->value())+QString::number(ui->port->value()), true);
// the forward transmission paths
float xstart = marginLeft + DUTstartX + pathSpacing;
for(unsigned int i=1;i<=ports;i++) {
if((int) i == ui->port->value()) {
// skip, this is the reflection path
continue;
}
drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
drawPath({
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
QPointF(marginLeft + DUTstartX + portSize/2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
}, colorF, QString((int) i > ui->port->value() ? "_" : "") + "S"+QString::number(i)+QString::number(ui->port->value()), true);
xstart += pathSpacing;
}
// the reverse transmission paths
bool first = true;
for(unsigned int i=1;i<=ports;i++) {
if((int) i == ui->port->value()) {
// skip, this is the reflection path
continue;
}
if(first) {
first = false;
drawDot(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
drawPath({
QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
}, colorF, "", false);
} else {
drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
}
drawPath({
QPointF(marginLeft + DUTstartX + portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
}, colorF, QString((int) i > ui->port->value() ? "" : "_") + "S"+QString::number(ui->port->value())+QString::number(i), true);
xstart += pathSpacing;
}
// isolation, transmission and receiver match paths
xstart = marginLeft;
for(unsigned int i=1;i<=ports;i++) {
if((int) i == ui->port->value()) {
// skip, this is the reflection path
continue;
}
// isolation
drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
drawPath({
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
}, colorF);
drawPath({
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
}, cal->hasIsolation(ui->port->value(), i) ? colorHasCal : colorNoCal, QString((int) i > ui->port->value() ? "_" : "") + "I"+QString::number(i)+QString::number(ui->port->value()), true);
drawPath({
QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
QPointF(marginLeft, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
}, colorF);
xstart += pathSpacing;
// transmission
drawDot(xstart, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
drawPath({
QPointF(marginLeft + DUTstartX - portSize / 2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
}, colorF);
drawPath({
QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
QPointF(xstart, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
}, cal->hasTransmissionTracking(ui->port->value(), i) ? colorHasCal : colorNoCal, "_T"+QString::number(i)+QString::number(ui->port->value()), true);
// Reveicer match
drawDot(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
drawPath({
QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portForwardYOffset),
QPointF(marginLeft + DUTstartX - portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset),
}, cal->hasReceiverMatch(ui->port->value(), i) ? colorHasCal : colorNoCal, "L"+QString::number(i)+QString::number(ui->port->value()), true);
}
// reflection error box
drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
drawDot(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
drawDot(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
drawDot(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
drawDot(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
// unity paths
drawPath({
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(marginLeft + DUTstartX - portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
}, colorF, "", true);
drawPath({
QPointF(marginLeft + DUTstartX - portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
}, colorF);
drawPath({
QPointF(marginLeft + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
}, colorF);
// directivity
drawPath({
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
}, cal->hasDirectivity(ui->port->value()) ? colorHasCal : colorNoCal, "_D"+QString::number(ui->port->value()), true);
// reflection tracking
drawPath({
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
}, cal->hasReflectionTracking(ui->port->value()) ? colorHasCal : colorNoCal, "_R"+QString::number(ui->port->value()), true);
// source match
drawPath({
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
}, cal->hasSourceMatch(ui->port->value()) ? colorHasCal : colorNoCal, "S"+QString::number(ui->port->value()), true);
// create the VNA ports
for(unsigned int i=1;i<=ports;i++) {
// stimulus port
if(i == (unsigned int) ui->port->value()) {
drawDot(marginLeft, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, portSize, colorF, colorB);
drawText(marginLeft - portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, "a"+QString::number(i), colorF, Qt::AlignRight, Qt::AlignCenter);
}
// output marker
drawDot(marginLeft, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, portSize, colorF, colorB);
drawText(marginLeft - portSize/2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, "b"+QString::number(i), colorF, Qt::AlignRight, Qt::AlignCenter);
}
// Fill the measurement correction table
ui->table->clear();
ui->table->setRowCount(ports*ports);
ui->table->setColumnCount(2);
ui->table->setHorizontalHeaderLabels({"Parameter", "Calibration Status"});
for(unsigned int i=1;i<=ports;i++) {
for(unsigned int j=1;j<=ports;j++) {
auto row = (i-1)*ports+j-1;
// add parameter
ui->table->setItem(row, 0, new QTableWidgetItem("S"+QString::number(j)+QString::number(i)));
// check the calibration status
QString status = "Uncalibrated";
if(i == j) {
// check reflection parameters
if(cal->hasSourceMatch(i) && cal->hasDirectivity(i) && cal->hasReflectionTracking(i)) {
// we are calibrated
status = "Calibrated";
// check if we have enhanced responses
QList<int> enhanced;
for(unsigned int k=1;k<=ports;k++) {
if(k==i) {
continue;
}
if(cal->hasReceiverMatch(i, k)) {
enhanced.append(k);
}
}
if(enhanced.size() == 1) {
status += " with enhanced response from port "+QString::number(enhanced[0]);
} else if(enhanced.size() > 1) {
status += " with enhanced response from ports ";
for(unsigned int k=0;k<enhanced.size();k++) {
if(k == enhanced.size() - 1) {
status += " and "+QString::number(enhanced[k]);
} else if(k > 0) {
status += ", ";
}
status += QString::number(enhanced[k]);
}
}
}
} else {
// check transmission calibration
if(cal->hasTransmissionTracking(i, j)) {
// we are calibrated
status = "Calibrated";
// check if we have isolation terms
if(cal->hasIsolation(i, j)) {
status += " with isolation measurement";
}
}
}
// add calibration status
ui->table->setItem(row, 1, new QTableWidgetItem(status));
}
}
ui->table->resizeColumnsToContents();
}

View file

@ -0,0 +1,51 @@
#ifndef CALIBRATIONVIEWDIALOG_H
#define CALIBRATIONVIEWDIALOG_H
#include <QDialog>
#include "calibration.h"
#include <QGraphicsScene>
namespace Ui {
class CalibrationViewDialog;
}
class CalibrationViewDialog : public QDialog
{
Q_OBJECT
public:
explicit CalibrationViewDialog(Calibration *cal, unsigned int ports, QWidget *parent = nullptr);
~CalibrationViewDialog();
private slots:
void populateScene();
private:
static constexpr int marginTop = 10;
static constexpr int marginBottom = 10;
static constexpr int marginLeft = 30;
static constexpr int marginRight = 10;
static constexpr int portHeight = 170;
static constexpr int portForwardYOffset = -50;
static constexpr int portReverseYOffset = 50;
static constexpr int boxSpacing = portReverseYOffset - portForwardYOffset;
static constexpr int portSize = 10;
static constexpr int arrowLength = 15;
static constexpr int arrowWidth = 10;
static constexpr int junctionSize = 6;
static constexpr int labelDistance = 6;
static constexpr int pathSpacing = 40;
static const QColor colorNoCal;
static const QColor colorHasCal;
Ui::CalibrationViewDialog *ui;
Calibration *cal;
unsigned int ports;
QGraphicsScene *scene;
};
#endif // CALIBRATIONVIEWDIALOG_H

View file

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

View file

@ -89,8 +89,9 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
accept();
});
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);
}

View file

@ -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);

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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()) {

View file

@ -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()) {

View file

@ -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();
}

View file

@ -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());

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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;
};

View file

@ -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()) {

View file

@ -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();

View file

@ -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)

View file

@ -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);
}

View file

@ -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];

View file

@ -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");
}

View file

@ -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;
}
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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:

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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()) {

View file

@ -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) {

View file

@ -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, [=](){

View file

@ -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, [=](){

View file

@ -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);
}

View file

@ -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();

View file

@ -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", ""},
}};
};

View file

@ -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">

View file

@ -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

View file

@ -0,0 +1,136 @@
#include "calibrationtests.h"
#include "calibration.h"
CalibrationTests::CalibrationTests() {}
void CalibrationTests::LinearDetection()
{
// create some measurements
std::vector<CalibrationMeasurement::Base*> m;
double startFreq = 100000;
double stopFreq = 6000000000;
int points = 1001;
Calibration cal;
cal.getKit().setIdealDefault();
auto open = new CalibrationMeasurement::Open(&cal);
open->setPort(1);
m.push_back(open);
auto _short = new CalibrationMeasurement::Short(&cal);
_short->setPort(1);
m.push_back(_short);
auto load = new CalibrationMeasurement::Load(&cal);
load->setPort(1);
m.push_back(load);
for(int i=0;i<points;i++) {
double f = startFreq + (stopFreq - startFreq) * i / (points-1);
DeviceDriver::VNAMeasurement meas;
meas.frequency = f;
meas.measurements["S11"] = 0.0;
m[0]->addPoint(meas);
m[1]->addPoint(meas);
m[2]->addPoint(meas);
}
// verify correct detection
double detectedStart;
double detectedStop;
int detectedPoints;
bool detectedLog;
Calibration::hasFrequencyOverlap(m, &detectedStart, &detectedStop, &detectedPoints, &detectedLog);
QVERIFY(qFuzzyCompare(detectedStart, startFreq));
QVERIFY(qFuzzyCompare(detectedStop, stopFreq));
QVERIFY(detectedPoints == points);
QVERIFY(detectedLog == false);
}
void CalibrationTests::LogDetection()
{
// create some measurements
std::vector<CalibrationMeasurement::Base*> m;
double startFreq = 100000;
double stopFreq = 6000000000;
int points = 1001;
Calibration cal;
cal.getKit().setIdealDefault();
auto open = new CalibrationMeasurement::Open(&cal);
open->setPort(1);
m.push_back(open);
auto _short = new CalibrationMeasurement::Short(&cal);
_short->setPort(1);
m.push_back(_short);
auto load = new CalibrationMeasurement::Load(&cal);
load->setPort(1);
m.push_back(load);
for(int i=0;i<points;i++) {
double f = startFreq * pow(10.0, i * log10(stopFreq / startFreq) / (points - 1));
DeviceDriver::VNAMeasurement meas;
meas.frequency = f;
meas.measurements["S11"] = 0.0;
m[0]->addPoint(meas);
m[1]->addPoint(meas);
m[2]->addPoint(meas);
}
// verify correct detection
double detectedStart;
double detectedStop;
int detectedPoints;
bool detectedLog;
Calibration::hasFrequencyOverlap(m, &detectedStart, &detectedStop, &detectedPoints, &detectedLog);
QVERIFY(qFuzzyCompare(detectedStart, startFreq));
QVERIFY(qFuzzyCompare(detectedStop, stopFreq));
QVERIFY(detectedPoints == points);
QVERIFY(detectedLog == true);
}
void CalibrationTests::MixedDetection()
{
// create some measurements
std::vector<CalibrationMeasurement::Base*> m;
double startFreq = 100000;
double stopFreq = 6000000000;
int points = 1001;
Calibration cal;
cal.getKit().setIdealDefault();
auto open = new CalibrationMeasurement::Open(&cal);
open->setPort(1);
m.push_back(open);
auto _short = new CalibrationMeasurement::Short(&cal);
_short->setPort(1);
m.push_back(_short);
auto load = new CalibrationMeasurement::Load(&cal);
load->setPort(1);
m.push_back(load);
for(int i=0;i<points;i++) {
// one linear measurement, two log measurement
double flin = startFreq + (stopFreq - startFreq) * i / (points-1);
double flog = startFreq * pow(10.0, i * log10(stopFreq / startFreq) / (points - 1));
DeviceDriver::VNAMeasurement measlin;
measlin.frequency = flin;
measlin.measurements["S11"] = 0.0;
DeviceDriver::VNAMeasurement measlog;
measlog.frequency = flog;
measlog.measurements["S11"] = 0.0;
m[0]->addPoint(measlin);
m[1]->addPoint(measlog);
m[2]->addPoint(measlog);
}
// verify correct detection
double detectedStart;
double detectedStop;
int detectedPoints;
bool detectedLog;
Calibration::hasFrequencyOverlap(m, &detectedStart, &detectedStop, &detectedPoints, &detectedLog);
QVERIFY(qFuzzyCompare(detectedStart, startFreq));
QVERIFY(qFuzzyCompare(detectedStop, stopFreq));
QVERIFY(detectedPoints == points);
QVERIFY(detectedLog == true);
}

View file

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

View file

@ -3,6 +3,7 @@
#include "parametertests.h"
#include "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;
}

View file

@ -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"/>

View file

@ -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;
}

View file

@ -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;

View file

@ -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,

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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(?)

View file

@ -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 {

View file

@ -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 {

View file

@ -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) {

View file

@ -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

View file

@ -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'" \