PC Application: partial firmware update dialog
57
AssembleFirmware.py
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import binascii
|
||||
|
||||
FPGA_BITSTREAM = "FPGA/VNA/top.bin"
|
||||
MCU_FW = "Software/VNA_embedded/Debug/VNA_embedded.bin"
|
||||
|
||||
HEADER_SIZE = 24
|
||||
|
||||
f = open("test.vnafw", "wb")
|
||||
f.write(bytes("VNA!", 'utf-8'))
|
||||
|
||||
bitstream = open(FPGA_BITSTREAM, "rb")
|
||||
firmware = open(MCU_FW, "rb")
|
||||
|
||||
size_FPGA = os.path.getsize(FPGA_BITSTREAM)
|
||||
size_MCU = os.path.getsize(MCU_FW)
|
||||
print("Got FPGA bitstream of size "+str(size_FPGA))
|
||||
print("Got MCU firmware of size "+str(size_MCU))
|
||||
|
||||
#Create header
|
||||
# Start address of FPGA bitstream
|
||||
f.write((HEADER_SIZE).to_bytes(4, byteorder='little'))
|
||||
# Size of FPGA bitstream
|
||||
f.write(size_FPGA.to_bytes(4, byteorder='little'))
|
||||
# Start address of MCU firmware
|
||||
f.write((HEADER_SIZE + size_FPGA).to_bytes(4, byteorder='little'))
|
||||
# Size of MCU firmware
|
||||
f.write(size_MCU.to_bytes(4, byteorder='little'))
|
||||
|
||||
# Calculate CRC
|
||||
def CRC32_from_file(filename, initial_CRC):
|
||||
buf = open(filename,'rb').read()
|
||||
buf = (binascii.crc32(buf, initial_CRC) & 0xFFFFFFFF)
|
||||
return buf
|
||||
|
||||
print("Calculating CRC...", end="")
|
||||
CRC = CRC32_from_file(FPGA_BITSTREAM, 0xFFFFFFFF)
|
||||
CRC = CRC32_from_file(MCU_FW, CRC)
|
||||
print(":"+hex(CRC))
|
||||
f.write(CRC.to_bytes(4, byteorder='little'))
|
||||
|
||||
# Check that we used the correct header size
|
||||
if f.tell() != HEADER_SIZE:
|
||||
print("Incorrect header size (defined as "+str(HEADER_SIZE)+" but actual header is of size "+str(f.tell())+")")
|
||||
exit(-1)
|
||||
|
||||
f.write(bitstream.read())
|
||||
f.write(firmware.read())
|
||||
|
||||
if f.tell() % 256 != 0:
|
||||
padding = 256 - f.tell() % 256
|
||||
f.write(bytearray(padding))
|
||||
|
||||
print("Created combined firmware file of size "+str(f.tell()))
|
||||
|
@ -254,6 +254,8 @@
|
||||
<status xil_pn:value="SuccessfullyRun"/>
|
||||
<status xil_pn:value="WarningsGenerated"/>
|
||||
<status xil_pn:value="ReadyToRun"/>
|
||||
<status xil_pn:value="OutOfDateForOutputs"/>
|
||||
<status xil_pn:value="OutputChanged"/>
|
||||
<outfile xil_pn:name="_xmsgs/map.xmsgs"/>
|
||||
<outfile xil_pn:name="top.pcf"/>
|
||||
<outfile xil_pn:name="top_map.map"/>
|
||||
|
74
Software/PC_Application/.gitignore
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
moc_*.h
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
1
Software/PC_Application/51-vna.rules
Normal file
@ -0,0 +1 @@
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="564e", MODE:="0666"
|
BIN
Software/PC_Application/Application
Executable file
116
Software/PC_Application/Application.pro
Normal file
@ -0,0 +1,116 @@
|
||||
HEADERS += \
|
||||
../VNA_embedded/Application/Communication/Protocol.hpp \
|
||||
Calibration/calibration.h \
|
||||
Calibration/calibrationtracedialog.h \
|
||||
Calibration/calkit.h \
|
||||
Calibration/calkitdialog.h \
|
||||
Calibration/measurementmodel.h \
|
||||
CustomWidgets/siunitedit.h \
|
||||
CustomWidgets/tilewidget.h \
|
||||
CustomWidgets/toggleswitch.h \
|
||||
CustomWidgets/touchstoneimport.h \
|
||||
Device/device.h \
|
||||
Device/devicelog.h \
|
||||
Device/firmwareupdatedialog.h \
|
||||
Device/manualcontroldialog.h \
|
||||
Menu/menu.h \
|
||||
Menu/menuaction.h \
|
||||
Menu/menubool.h \
|
||||
Menu/menuitem.h \
|
||||
Menu/menuvalue.h \
|
||||
Tools/eseries.h \
|
||||
Tools/impedancematchdialog.h \
|
||||
Traces/bodeplotaxisdialog.h \
|
||||
Traces/markerwidget.h \
|
||||
Traces/trace.h \
|
||||
Traces/tracebodeplot.h \
|
||||
Traces/traceeditdialog.h \
|
||||
Traces/traceexportdialog.h \
|
||||
Traces/traceimportdialog.h \
|
||||
Traces/tracemarker.h \
|
||||
Traces/tracemarkermodel.h \
|
||||
Traces/tracemodel.h \
|
||||
Traces/traceplot.h \
|
||||
Traces/tracesmithchart.h \
|
||||
Traces/tracewidget.h \
|
||||
averaging.h \
|
||||
qwtplotpiecewisecurve.h \
|
||||
touchstone.h \
|
||||
unit.h \
|
||||
valueinput.h \
|
||||
vna.h
|
||||
|
||||
SOURCES += \
|
||||
../VNA_embedded/Application/Communication/Protocol.cpp \
|
||||
Calibration/calibration.cpp \
|
||||
Calibration/calibrationtracedialog.cpp \
|
||||
Calibration/calkit.cpp \
|
||||
Calibration/calkitdialog.cpp \
|
||||
Calibration/measurementmodel.cpp \
|
||||
CustomWidgets/siunitedit.cpp \
|
||||
CustomWidgets/tilewidget.cpp \
|
||||
CustomWidgets/toggleswitch.cpp \
|
||||
CustomWidgets/touchstoneimport.cpp \
|
||||
Device/device.cpp \
|
||||
Device/devicelog.cpp \
|
||||
Device/firmwareupdatedialog.cpp \
|
||||
Device/manualcontroldialog.cpp \
|
||||
Menu/menu.cpp \
|
||||
Menu/menuaction.cpp \
|
||||
Menu/menubool.cpp \
|
||||
Menu/menuitem.cpp \
|
||||
Menu/menuvalue.cpp \
|
||||
Tools/eseries.cpp \
|
||||
Tools/impedancematchdialog.cpp \
|
||||
Traces/bodeplotaxisdialog.cpp \
|
||||
Traces/markerwidget.cpp \
|
||||
Traces/trace.cpp \
|
||||
Traces/tracebodeplot.cpp \
|
||||
Traces/traceeditdialog.cpp \
|
||||
Traces/traceexportdialog.cpp \
|
||||
Traces/traceimportdialog.cpp \
|
||||
Traces/tracemarker.cpp \
|
||||
Traces/tracemarkermodel.cpp \
|
||||
Traces/tracemodel.cpp \
|
||||
Traces/traceplot.cpp \
|
||||
Traces/tracesmithchart.cpp \
|
||||
Traces/tracewidget.cpp \
|
||||
averaging.cpp \
|
||||
main.cpp \
|
||||
qwtplotpiecewisecurve.cpp \
|
||||
touchstone.cpp \
|
||||
unit.cpp \
|
||||
valueinput.cpp \
|
||||
vna.cpp
|
||||
|
||||
LIBS += -lusb-1.0
|
||||
unix:INCLUDEPATH += /usr/include/qwt
|
||||
unix:LIBS += -L/usr/lib/ -lqwt-qt5
|
||||
win32:INCLUDEPATH += C:\Qwt-6.1.4\include
|
||||
win32:LIBS += -LC:\Qwt-6.1.4\lib -lqwt
|
||||
|
||||
QT += widgets
|
||||
|
||||
FORMS += \
|
||||
Calibration/calibrationtracedialog.ui \
|
||||
Calibration/calkitdialog.ui \
|
||||
CustomWidgets/tilewidget.ui \
|
||||
CustomWidgets/touchstoneimport.ui \
|
||||
Device/devicelog.ui \
|
||||
Device/firmwareupdatedialog.ui \
|
||||
Device/manualcontroldialog.ui \
|
||||
Tools/impedancematchdialog.ui \
|
||||
Traces/bodeplotaxisdialog.ui \
|
||||
Traces/markerwidget.ui \
|
||||
Traces/traceeditdialog.ui \
|
||||
Traces/traceexportdialog.ui \
|
||||
Traces/traceimportdialog.ui \
|
||||
Traces/tracewidget.ui \
|
||||
main.ui
|
||||
|
||||
DISTFILES +=
|
||||
|
||||
RESOURCES += \
|
||||
icons.qrc
|
||||
|
||||
CONFIG += c++14
|
666
Software/PC_Application/Calibration/calibration.cpp
Normal file
@ -0,0 +1,666 @@
|
||||
#include "calibration.h"
|
||||
#include <algorithm>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Calibration::Calibration()
|
||||
{
|
||||
// Creator vectors for measurements
|
||||
measurements[Measurement::Port1Open].datapoints = vector<Protocol::Datapoint>();
|
||||
measurements[Measurement::Port1Short].datapoints = vector<Protocol::Datapoint>();
|
||||
measurements[Measurement::Port1Load].datapoints = vector<Protocol::Datapoint>();
|
||||
measurements[Measurement::Port2Open].datapoints = vector<Protocol::Datapoint>();
|
||||
measurements[Measurement::Port2Short].datapoints = vector<Protocol::Datapoint>();
|
||||
measurements[Measurement::Port2Load].datapoints = vector<Protocol::Datapoint>();
|
||||
measurements[Measurement::Isolation].datapoints = vector<Protocol::Datapoint>();
|
||||
measurements[Measurement::Through].datapoints = vector<Protocol::Datapoint>();
|
||||
|
||||
type = Type::None;
|
||||
}
|
||||
|
||||
void Calibration::clearMeasurements()
|
||||
{
|
||||
for(auto m : measurements) {
|
||||
m.second.datapoints.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Calibration::clearMeasurement(Calibration::Measurement type)
|
||||
{
|
||||
measurements[type].datapoints.clear();
|
||||
measurements[type].timestamp = QDateTime();
|
||||
}
|
||||
|
||||
void Calibration::addMeasurement(Calibration::Measurement type, Protocol::Datapoint &d)
|
||||
{
|
||||
measurements[type].datapoints.push_back(d);
|
||||
measurements[type].timestamp = QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
bool Calibration::calculationPossible(Calibration::Type type)
|
||||
{
|
||||
std::vector<Measurement> requiredMeasurements;
|
||||
switch(type) {
|
||||
case Type::Port1SOL:
|
||||
requiredMeasurements.push_back(Measurement::Port1Open);
|
||||
requiredMeasurements.push_back(Measurement::Port1Short);
|
||||
requiredMeasurements.push_back(Measurement::Port1Load);
|
||||
break;
|
||||
case Type::Port2SOL:
|
||||
requiredMeasurements.push_back(Measurement::Port2Open);
|
||||
requiredMeasurements.push_back(Measurement::Port2Short);
|
||||
requiredMeasurements.push_back(Measurement::Port2Load);
|
||||
break;
|
||||
case Type::FullSOLT:
|
||||
requiredMeasurements.push_back(Measurement::Port1Open);
|
||||
requiredMeasurements.push_back(Measurement::Port1Short);
|
||||
requiredMeasurements.push_back(Measurement::Port1Load);
|
||||
requiredMeasurements.push_back(Measurement::Port2Open);
|
||||
requiredMeasurements.push_back(Measurement::Port2Short);
|
||||
requiredMeasurements.push_back(Measurement::Port2Load);
|
||||
requiredMeasurements.push_back(Measurement::Through);
|
||||
break;
|
||||
case Type::None:
|
||||
return false;
|
||||
}
|
||||
return SanityCheckSamples(requiredMeasurements);
|
||||
}
|
||||
|
||||
bool Calibration::constructErrorTerms(Calibration::Type type)
|
||||
{
|
||||
if(!calculationPossible(type)) {
|
||||
return false;
|
||||
}
|
||||
if(minFreq < kit.minFreq() || maxFreq > kit.maxFreq()) {
|
||||
// Calkit does not support complete calibration range
|
||||
QMessageBox::critical(nullptr, "Unable to perform calibration", "The calibration kit does not support the complete span. Please choose a different calibration kit or a narrower span.");
|
||||
return false;
|
||||
}
|
||||
switch(type) {
|
||||
case Type::Port1SOL:
|
||||
constructPort1SOL();
|
||||
break;
|
||||
case Type::Port2SOL:
|
||||
constructPort2SOL();
|
||||
break;
|
||||
case Type::FullSOLT:
|
||||
construct12TermPoints();
|
||||
break;
|
||||
case Type::None:
|
||||
break;
|
||||
}
|
||||
this->type = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Calibration::resetErrorTerms()
|
||||
{
|
||||
type = Type::None;
|
||||
points.clear();
|
||||
}
|
||||
|
||||
void Calibration::construct12TermPoints()
|
||||
{
|
||||
std::vector<Measurement> requiredMeasurements;
|
||||
requiredMeasurements.push_back(Measurement::Port1Open);
|
||||
requiredMeasurements.push_back(Measurement::Port1Short);
|
||||
requiredMeasurements.push_back(Measurement::Port1Load);
|
||||
requiredMeasurements.push_back(Measurement::Port2Open);
|
||||
requiredMeasurements.push_back(Measurement::Port2Short);
|
||||
requiredMeasurements.push_back(Measurement::Port2Load);
|
||||
requiredMeasurements.push_back(Measurement::Through);
|
||||
if(!SanityCheckSamples(requiredMeasurements)) {
|
||||
throw runtime_error("Missing/wrong calibration measurement");
|
||||
}
|
||||
requiredMeasurements.push_back(Measurement::Isolation);
|
||||
bool isolation_measured = SanityCheckSamples(requiredMeasurements);
|
||||
|
||||
// If we get here the calibration measurements are all okay
|
||||
points.clear();
|
||||
for(unsigned int i = 0;i<measurements[Measurement::Port1Open].datapoints.size();i++) {
|
||||
Point p;
|
||||
p.frequency = measurements[Measurement::Port1Open].datapoints[i].frequency;
|
||||
// extract required complex reflection/transmission factors from datapoints
|
||||
auto S11_open = complex<double>(measurements[Measurement::Port1Open].datapoints[i].real_S11, measurements[Measurement::Port1Open].datapoints[i].imag_S11);
|
||||
auto S11_short = complex<double>(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11);
|
||||
auto S11_load = complex<double>(measurements[Measurement::Port1Load].datapoints[i].real_S11, measurements[Measurement::Port1Load].datapoints[i].imag_S11);
|
||||
auto S22_open = complex<double>(measurements[Measurement::Port2Open].datapoints[i].real_S22, measurements[Measurement::Port2Open].datapoints[i].imag_S22);
|
||||
auto S22_short = complex<double>(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22);
|
||||
auto S22_load = complex<double>(measurements[Measurement::Port2Load].datapoints[i].real_S22, measurements[Measurement::Port2Load].datapoints[i].imag_S22);
|
||||
auto S21_isolation = complex<double>(0,0);
|
||||
auto S12_isolation = complex<double>(0,0);
|
||||
if(isolation_measured) {
|
||||
S21_isolation = complex<double>(measurements[Measurement::Isolation].datapoints[i].real_S21, measurements[Measurement::Isolation].datapoints[i].imag_S21);
|
||||
S12_isolation = complex<double>(measurements[Measurement::Isolation].datapoints[i].real_S12, measurements[Measurement::Isolation].datapoints[i].imag_S12);
|
||||
}
|
||||
auto S11_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S11, measurements[Measurement::Through].datapoints[i].imag_S11);
|
||||
auto S21_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S21, measurements[Measurement::Through].datapoints[i].imag_S21);
|
||||
auto S22_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S22, measurements[Measurement::Through].datapoints[i].imag_S22);
|
||||
auto S12_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S12, measurements[Measurement::Through].datapoints[i].imag_S12);
|
||||
|
||||
auto actual = kit.toReflection(p.frequency);
|
||||
// Forward calibration
|
||||
computeSOL(S11_short, S11_open, S11_load, p.fe00, p.fe11, p.fe10e01, actual.Open, actual.Short, actual.Load);
|
||||
p.fe30 = S21_isolation;
|
||||
// See page 17 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
// Formulas for S11M and S21M solved for e22 and e10e32
|
||||
auto deltaS = actual.ThroughS11*actual.ThroughS22 - actual.ThroughS21 * actual.ThroughS12;
|
||||
p.fe22 = ((S11_through - p.fe00)*(1.0 - p.fe11 * actual.ThroughS11)-actual.ThroughS11*p.fe10e01)
|
||||
/ ((S11_through - p.fe00)*(actual.ThroughS22-p.fe11*deltaS)-deltaS*p.fe10e01);
|
||||
p.fe10e32 = (S21_through - p.fe30)*(1.0 - p.fe11*actual.ThroughS11 - p.fe22*actual.ThroughS22 + p.fe11*p.fe22*deltaS) / actual.ThroughS21;
|
||||
// Reverse calibration
|
||||
computeSOL(S22_short, S22_open, S22_load, p.re33, p.re22, p.re23e32, actual.Open, actual.Short, actual.Load);
|
||||
p.re03 = S12_isolation;
|
||||
p.re11 = ((S22_through - p.re33)*(1.0 - p.re22 * actual.ThroughS22)-actual.ThroughS22*p.re23e32)
|
||||
/ ((S22_through - p.re33)*(actual.ThroughS11-p.re22*deltaS)-deltaS*p.re23e32);
|
||||
p.re23e01 = (S12_through - p.re03)*(1.0 - p.re11*actual.ThroughS11 - p.re22*actual.ThroughS22 + p.re11*p.re22*deltaS) / actual.ThroughS12;
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
void Calibration::constructPort1SOL()
|
||||
{
|
||||
std::vector<Measurement> requiredMeasurements;
|
||||
requiredMeasurements.push_back(Measurement::Port1Open);
|
||||
requiredMeasurements.push_back(Measurement::Port1Short);
|
||||
requiredMeasurements.push_back(Measurement::Port1Load);
|
||||
if(!SanityCheckSamples(requiredMeasurements)) {
|
||||
throw runtime_error("Missing/wrong calibration measurement");
|
||||
}
|
||||
|
||||
// If we get here the calibration measurements are all okay
|
||||
points.clear();
|
||||
for(unsigned int i = 0;i<measurements[Measurement::Port1Open].datapoints.size();i++) {
|
||||
Point p;
|
||||
p.frequency = measurements[Measurement::Port1Open].datapoints[i].frequency;
|
||||
// extract required complex reflection/transmission factors from datapoints
|
||||
auto S11_open = complex<double>(measurements[Measurement::Port1Open].datapoints[i].real_S11, measurements[Measurement::Port1Open].datapoints[i].imag_S11);
|
||||
auto S11_short = complex<double>(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11);
|
||||
auto S11_load = complex<double>(measurements[Measurement::Port1Load].datapoints[i].real_S11, measurements[Measurement::Port1Load].datapoints[i].imag_S11);
|
||||
// OSL port1
|
||||
auto actual = kit.toReflection(p.frequency);
|
||||
// See page 19 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
computeSOL(S11_short, S11_open, S11_load, p.fe00, p.fe11, p.fe10e01, actual.Open, actual.Short, actual.Load);
|
||||
// All other calibration coefficients to ideal values
|
||||
p.fe30 = 0.0;
|
||||
p.fe22 = 0.0;
|
||||
p.fe10e32 = 1.0;
|
||||
p.re33 = 0.0;
|
||||
p.re22 = 0.0;
|
||||
p.re23e32 = 1.0;
|
||||
p.re03 = 0.0;
|
||||
p.re11 = 0.0;
|
||||
p.re23e01 = 1.0;
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
void Calibration::constructPort2SOL()
|
||||
{
|
||||
std::vector<Measurement> requiredMeasurements;
|
||||
requiredMeasurements.push_back(Measurement::Port2Open);
|
||||
requiredMeasurements.push_back(Measurement::Port2Short);
|
||||
requiredMeasurements.push_back(Measurement::Port2Load);
|
||||
if(!SanityCheckSamples(requiredMeasurements)) {
|
||||
throw runtime_error("Missing/wrong calibration measurement");
|
||||
}
|
||||
|
||||
// If we get here the calibration measurements are all okay
|
||||
points.clear();
|
||||
for(unsigned int i = 0;i<measurements[Measurement::Port2Open].datapoints.size();i++) {
|
||||
Point p;
|
||||
p.frequency = measurements[Measurement::Port2Open].datapoints[i].frequency;
|
||||
// extract required complex reflection/transmission factors from datapoints
|
||||
auto S22_open = complex<double>(measurements[Measurement::Port2Open].datapoints[i].real_S22, measurements[Measurement::Port2Open].datapoints[i].imag_S22);
|
||||
auto S22_short = complex<double>(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22);
|
||||
auto S22_load = complex<double>(measurements[Measurement::Port2Load].datapoints[i].real_S22, measurements[Measurement::Port2Load].datapoints[i].imag_S22);
|
||||
// OSL port2
|
||||
auto actual = kit.toReflection(p.frequency);
|
||||
// See page 19 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
computeSOL(S22_short, S22_open, S22_load, p.re33, p.re22, p.re23e32, actual.Open, actual.Short, actual.Load);
|
||||
// All other calibration coefficients to ideal values
|
||||
p.fe30 = 0.0;
|
||||
p.fe22 = 0.0;
|
||||
p.fe10e32 = 1.0;
|
||||
p.fe00 = 0.0;
|
||||
p.fe11 = 0.0;
|
||||
p.fe10e01 = 1.0;
|
||||
p.re03 = 0.0;
|
||||
p.re11 = 0.0;
|
||||
p.re23e01 = 1.0;
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
void Calibration::correctMeasurement(Protocol::Datapoint &d)
|
||||
{
|
||||
if(type == Type::None) {
|
||||
// No calibration data, do nothing
|
||||
return;
|
||||
}
|
||||
// Convert measurements to complex variables
|
||||
auto S11m = complex<double>(d.real_S11, d.imag_S11);
|
||||
auto S21m = complex<double>(d.real_S21, d.imag_S21);
|
||||
auto S22m = complex<double>(d.real_S22, d.imag_S22);
|
||||
auto S12m = complex<double>(d.real_S12, d.imag_S12);
|
||||
|
||||
// find correct entry
|
||||
auto p = getCalibrationPoint(d);
|
||||
|
||||
// equations from page 20 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
auto denom = (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11) * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
|
||||
- (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01 * p.fe22 * p.re11;
|
||||
auto S11 = ((S11m - p.fe00) / p.fe10e01 * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
|
||||
- p.fe22 * (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01) / denom;
|
||||
auto S21 = ((S21m - p.fe30) / p.fe10e32 * (1.0 + (S22m - p.re33) / p.re23e32 * (p.re22 - p.fe22))) / denom;
|
||||
auto S22 = ((S22m - p.re33) / p.re23e32 * (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11)
|
||||
- p.re11 * (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01) / denom;
|
||||
auto S12 = ((S12m - p.re03) / p.re23e01 * (1.0 + (S11m - p.fe00) / p.fe10e01 * (p.fe11 - p.re11))) / denom;
|
||||
|
||||
d.real_S11 = S11.real();
|
||||
d.imag_S11 = S11.imag();
|
||||
d.real_S12 = S12.real();
|
||||
d.imag_S12 = S12.imag();
|
||||
d.real_S21 = S21.real();
|
||||
d.imag_S21 = S21.imag();
|
||||
d.real_S22 = S22.real();
|
||||
d.imag_S22 = S22.imag();
|
||||
}
|
||||
|
||||
Calibration::InterpolationType Calibration::getInterpolation(Protocol::SweepSettings settings)
|
||||
{
|
||||
if(!points.size()) {
|
||||
return InterpolationType::NoCalibration;
|
||||
}
|
||||
if(settings.f_start < points.front().frequency || settings.f_stop > points.back().frequency) {
|
||||
return InterpolationType::Extrapolate;
|
||||
}
|
||||
// Either exact or interpolation, check individual frequencies
|
||||
uint32_t f_step = (settings.f_stop - settings.f_start) / (settings.points - 1);
|
||||
for(uint64_t f = settings.f_start; f <= settings.f_stop; f += f_step) {
|
||||
if(find_if(points.begin(), points.end(), [&f](const Point& p){
|
||||
return abs(f - p.frequency) < 100;
|
||||
}) == points.end()) {
|
||||
return InterpolationType::Interpolate;
|
||||
}
|
||||
}
|
||||
// if we get here all frequency points were matched
|
||||
if(points.front().frequency == settings.f_start && points.back().frequency == settings.f_stop) {
|
||||
return InterpolationType::Unchanged;
|
||||
} else {
|
||||
return InterpolationType::Exact;
|
||||
}
|
||||
}
|
||||
|
||||
QString Calibration::MeasurementToString(Calibration::Measurement m)
|
||||
{
|
||||
switch(m) {
|
||||
case Measurement::Port1Open:
|
||||
return "Port 1 Open";
|
||||
case Measurement::Port1Short:
|
||||
return "Port 1 Short";
|
||||
case Measurement::Port1Load:
|
||||
return "Port 1 Load";
|
||||
case Measurement::Port2Open:
|
||||
return "Port 2 Open";
|
||||
case Measurement::Port2Short:
|
||||
return "Port 2 Short";
|
||||
case Measurement::Port2Load:
|
||||
return "Port 2 Load";
|
||||
case Measurement::Through:
|
||||
return "Through";
|
||||
case Measurement::Isolation:
|
||||
return "Isolation";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
QString Calibration::TypeToString(Calibration::Type t)
|
||||
{
|
||||
switch(t) {
|
||||
case Type::Port1SOL: return "Port 1"; break;
|
||||
case Type::Port2SOL: return "Port 2"; break;
|
||||
case Type::FullSOLT: return "SOLT"; break;
|
||||
default: return "None"; break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Calibration::Type> Calibration::Types()
|
||||
{
|
||||
const std::vector<Calibration::Type> ret = {Type::Port1SOL, Type::Port2SOL, Type::FullSOLT};
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<Calibration::Measurement> Calibration::Measurements(Calibration::Type type)
|
||||
{
|
||||
switch(type) {
|
||||
case Type::FullSOLT:
|
||||
case Type::None:
|
||||
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load, Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load, Measurement::Through, Measurement::Isolation};
|
||||
break;
|
||||
case Type::Port1SOL:
|
||||
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load};
|
||||
break;
|
||||
case Type::Port2SOL:
|
||||
return {Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load};
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Calibration::MeasurementInfo Calibration::getMeasurementInfo(Calibration::Measurement m)
|
||||
{
|
||||
MeasurementInfo info;
|
||||
switch(m) {
|
||||
case Measurement::Port1Short:
|
||||
info.name = "Port 1 short";
|
||||
info.prerequisites = "Short standard connected to port 1, port 2 open";
|
||||
break;
|
||||
case Measurement::Port1Open:
|
||||
info.name = "Port 1 open";
|
||||
info.prerequisites = "Open standard connected to port 1, port 2 open";
|
||||
break;
|
||||
case Measurement::Port1Load:
|
||||
info.name = "Port 1 load";
|
||||
info.prerequisites = "Load standard connected to port 1, port 2 open";
|
||||
break;
|
||||
case Measurement::Port2Short:
|
||||
info.name = "Port 2 short";
|
||||
info.prerequisites = "Port 1 open, short standard connected to port 2";
|
||||
break;
|
||||
case Measurement::Port2Open:
|
||||
info.name = "Port 2 open";
|
||||
info.prerequisites = "Port 1 open, open standard connected to port 2";
|
||||
break;
|
||||
case Measurement::Port2Load:
|
||||
info.name = "Port 2 load";
|
||||
info.prerequisites = "Port 1 open, load standard connected to port 2";
|
||||
break;
|
||||
case Measurement::Through:
|
||||
info.name = "Through";
|
||||
info.prerequisites = "Port 1 connected to port 2 via through standard";
|
||||
break;
|
||||
case Measurement::Isolation:
|
||||
info.name = "Isolation";
|
||||
info.prerequisites = "Both ports terminated into 50 ohm";
|
||||
}
|
||||
info.points = measurements[m].datapoints.size();
|
||||
if(info.points > 0) {
|
||||
info.fmin = measurements[m].datapoints.front().frequency;
|
||||
info.fmax = measurements[m].datapoints.back().frequency;
|
||||
info.points = measurements[m].datapoints.size();
|
||||
}
|
||||
info.timestamp = measurements[m].timestamp;
|
||||
return info;
|
||||
}
|
||||
|
||||
std::vector<Trace *> Calibration::getErrorTermTraces()
|
||||
{
|
||||
std::vector<Trace*> traces;
|
||||
const QString traceNames[12] = {"e00", "F_e11", "e10e01", "e10e32", "F_e22", "e30", "e33", "R_e11", "e23e32", "e23e01", "R_e22", "e03"};
|
||||
constexpr bool reflection[12] = {true, true, false, false, true, false, true, true, false, false, true, false};
|
||||
for(int i=0;i<12;i++) {
|
||||
auto t = new Trace(traceNames[i], Qt::red);
|
||||
t->setCalibration(true);
|
||||
t->setReflection(reflection[i]);
|
||||
traces.push_back(t);
|
||||
}
|
||||
for(auto p : points) {
|
||||
Trace::Data d;
|
||||
d.frequency = p.frequency;
|
||||
for(int i=0;i<12;i++) {
|
||||
switch(i) {
|
||||
case 0: d.S = p.fe00; break;
|
||||
case 1: d.S = p.fe11; break;
|
||||
case 2: d.S = p.fe10e01; break;
|
||||
case 3: d.S = p.fe10e32; break;
|
||||
case 4: d.S = p.fe22; break;
|
||||
case 5: d.S = p.fe30; break;
|
||||
case 6: d.S = p.re33; break;
|
||||
case 7: d.S = p.re11; break;
|
||||
case 8: d.S = p.re23e32; break;
|
||||
case 9: d.S = p.re23e01; break;
|
||||
case 10: d.S = p.re22; break;
|
||||
case 11: d.S = p.re03; break;
|
||||
}
|
||||
traces[i]->addData(d);
|
||||
}
|
||||
}
|
||||
return traces;
|
||||
}
|
||||
|
||||
bool Calibration::openFromFile(QString filename)
|
||||
{
|
||||
if(filename.isEmpty()) {
|
||||
filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", "", "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to load associated calibration kit first (needs to be available when performing calibration)
|
||||
auto calkit_file = filename;
|
||||
auto dotPos = calkit_file.lastIndexOf('.');
|
||||
if(dotPos >= 0) {
|
||||
calkit_file.truncate(dotPos);
|
||||
}
|
||||
calkit_file.append(".calkit");
|
||||
try {
|
||||
kit = Calkit::fromFile(calkit_file.toStdString());
|
||||
} catch (runtime_error e) {
|
||||
QMessageBox::warning(nullptr, "Missing calibration kit", "The calibration kit file associated with the selected calibration could not be parsed. The calibration might not be accurate. (" + QString(e.what()) + ")");
|
||||
}
|
||||
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
try {
|
||||
file >> *this;
|
||||
} catch(runtime_error e) {
|
||||
QMessageBox::warning(nullptr, "File parsing error", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Calibration::saveToFile(QString filename)
|
||||
{
|
||||
if(filename.isEmpty()) {
|
||||
filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", "", "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// strip any potential file name extension and set default
|
||||
auto dotPos = filename.lastIndexOf('.');
|
||||
if(dotPos >= 0) {
|
||||
filename.truncate(dotPos);
|
||||
}
|
||||
auto calibration_file = filename;
|
||||
calibration_file.append(".cal");
|
||||
ofstream file;
|
||||
file.open(calibration_file.toStdString());
|
||||
file << *this;
|
||||
|
||||
auto calkit_file = filename;
|
||||
calkit_file.append(".calkit");
|
||||
kit.toFile(calkit_file.toStdString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ostream& operator<<(ostream &os, const Calibration &c)
|
||||
{
|
||||
for(auto m : c.measurements) {
|
||||
if(m.second.datapoints.size() > 0) {
|
||||
os << c.MeasurementToString(m.first).toStdString() << endl;
|
||||
os << m.second.timestamp.toSecsSinceEpoch() << endl;
|
||||
os << m.second.datapoints.size() << endl;
|
||||
for(auto p : m.second.datapoints) {
|
||||
os << p.pointNum << " " << p.frequency << " ";
|
||||
os << p.imag_S11 << " " << p.real_S11 << " " << p.imag_S21 << " " << p.real_S21 << " " << p.imag_S12 << " " << p.real_S12 << " " << p.imag_S22 << " " << p.real_S22;
|
||||
os << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
os << Calibration::TypeToString(c.getType()).toStdString() << endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
istream& operator >>(istream &in, Calibration &c)
|
||||
{
|
||||
std::string line;
|
||||
while(getline(in, line)) {
|
||||
for(auto m : Calibration::Measurements()) {
|
||||
if(Calibration::MeasurementToString(m) == QString::fromStdString(line)) {
|
||||
// this is the correct measurement
|
||||
c.clearMeasurement(m);
|
||||
uint timestamp;
|
||||
in >> timestamp;
|
||||
c.measurements[m].timestamp = QDateTime::fromSecsSinceEpoch(timestamp);
|
||||
unsigned int points;
|
||||
in >> points;
|
||||
for(unsigned int i=0;i<points;i++) {
|
||||
Protocol::Datapoint p;
|
||||
in >> p.pointNum >> p.frequency;
|
||||
in >> p.imag_S11 >> p.real_S11 >> p.imag_S21 >> p.real_S21 >> p.imag_S12 >> p.real_S12 >> p.imag_S22 >> p.real_S22;
|
||||
c.measurements[m].datapoints.push_back(p);
|
||||
if(in.eof() || in.bad() || in.fail()) {
|
||||
c.clearMeasurement(m);
|
||||
throw runtime_error("Failed to parse measurement \"" + line + "\", aborting calibration data import.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
for(auto t : Calibration::Types()) {
|
||||
if(Calibration::TypeToString(t) == QString::fromStdString(line)) {
|
||||
// try to apply this calibration type
|
||||
if(c.calculationPossible(t)) {
|
||||
c.constructErrorTerms(t);
|
||||
} else {
|
||||
throw runtime_error("Incomplete calibration data, the requested \"" + line + "\"-Calibration could not be performed.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
bool Calibration::SanityCheckSamples(std::vector<Calibration::Measurement> &requiredMeasurements)
|
||||
{
|
||||
// sanity check measurements, all need to be of the same size with the same frequencies (except for isolation which may be empty)
|
||||
vector<uint64_t> freqs;
|
||||
for(auto type : requiredMeasurements) {
|
||||
auto m = measurements[type];
|
||||
if(m.datapoints.size() == 0) {
|
||||
// empty required measurement
|
||||
return false;
|
||||
}
|
||||
if(freqs.size() == 0) {
|
||||
// this is the first measurement, create frequency vector
|
||||
for(auto p : m.datapoints) {
|
||||
freqs.push_back(p.frequency);
|
||||
}
|
||||
} else {
|
||||
// compare with already assembled frequency vector
|
||||
if(m.datapoints.size() != freqs.size()) {
|
||||
return false;
|
||||
}
|
||||
for(unsigned int i=0;i<freqs.size();i++) {
|
||||
if(m.datapoints[i].frequency != freqs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
minFreq = freqs.front();
|
||||
maxFreq = freqs.back();
|
||||
return true;
|
||||
}
|
||||
|
||||
Calibration::Point Calibration::getCalibrationPoint(Protocol::Datapoint &d)
|
||||
{
|
||||
if(!points.size()) {
|
||||
throw runtime_error("No calibration points available");
|
||||
}
|
||||
if(d.frequency <= points.front().frequency) {
|
||||
// use first point even for lower frequencies
|
||||
return points.front();
|
||||
}
|
||||
if(d.frequency >= points.back().frequency) {
|
||||
// use last point even for higher frequencies
|
||||
return points.back();
|
||||
}
|
||||
auto p = lower_bound(points.begin(), points.end(), d.frequency, [](Point p, uint64_t freq) -> bool {
|
||||
return p.frequency < freq;
|
||||
});
|
||||
if(p->frequency == d.frequency) {
|
||||
// Exact match, return point
|
||||
return *p;
|
||||
}
|
||||
// need to interpolate
|
||||
auto high = p;
|
||||
p--;
|
||||
auto low = p;
|
||||
double alpha = (d.frequency - low->frequency) / (high->frequency - low->frequency);
|
||||
Point ret;
|
||||
ret.frequency = d.frequency;
|
||||
ret.fe00 = low->fe00 * (1 - alpha) + high->fe00 * alpha;
|
||||
ret.fe11 = low->fe11 * (1 - alpha) + high->fe11 * alpha;
|
||||
ret.fe22 = low->fe22 * (1 - alpha) + high->fe22 * alpha;
|
||||
ret.fe30 = low->fe30 * (1 - alpha) + high->fe30 * alpha;
|
||||
ret.re03 = low->re03 * (1 - alpha) + high->re03 * alpha;
|
||||
ret.re11 = low->re11 * (1 - alpha) + high->re11 * alpha;
|
||||
ret.re22 = low->re22 * (1 - alpha) + high->re22 * alpha;
|
||||
ret.re33 = low->re33 * (1 - alpha) + high->re33 * alpha;
|
||||
ret.fe10e01 = low->fe10e01 * (1 - alpha) + high->fe10e01 * alpha;
|
||||
ret.fe10e32 = low->fe10e32 * (1 - alpha) + high->fe10e32 * alpha;
|
||||
ret.re23e01 = low->re23e01 * (1 - alpha) + high->re23e01 * alpha;
|
||||
ret.re23e32 = low->re23e32 * (1 - alpha) + high->re23e32 * alpha;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Calibration::computeSOL(std::complex<double> s_m, std::complex<double> o_m, std::complex<double> l_m,
|
||||
std::complex<double> &directivity, std::complex<double> &match, std::complex<double> &tracking,
|
||||
std::complex<double> o_c, std::complex<double> s_c, std::complex<double> l_c)
|
||||
{
|
||||
// equations from page 13 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
// solved while taking non ideal o/s/l standards into account
|
||||
auto denom = l_c * o_c * (o_m - l_m) + l_c * s_c * (l_m - s_m) + o_c * s_c * (s_m - o_m);
|
||||
directivity = (l_c * o_m * (s_m * (o_c - s_c) + l_m * s_c) - l_c * o_c * l_m * s_m + o_c * l_m * s_c * (s_m - o_m)) / denom;
|
||||
match = (l_c * (o_m - s_m) + o_c * (s_m - l_m) + s_c * (l_m - o_m)) / denom;
|
||||
auto delta = (l_c * l_m * (o_m - s_m) + o_c * o_m * (s_m - l_m) + s_c * s_m * (l_m - o_m)) / denom;
|
||||
tracking = directivity * match - delta;
|
||||
}
|
||||
|
||||
std::complex<double> Calibration::correctSOL(std::complex<double> measured, std::complex<double> directivity, std::complex<double> match, std::complex<double> tracking)
|
||||
{
|
||||
return (measured - directivity) / (measured * match - directivity * match + tracking);
|
||||
}
|
||||
|
||||
Calkit &Calibration::getCalibrationKit()
|
||||
{
|
||||
return kit;
|
||||
}
|
||||
|
||||
void Calibration::setCalibrationKit(const Calkit &value)
|
||||
{
|
||||
kit = value;
|
||||
}
|
||||
|
||||
Calibration::Type Calibration::getType() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
|
136
Software/PC_Application/Calibration/calibration.h
Normal file
@ -0,0 +1,136 @@
|
||||
#ifndef CALIBRATION_H
|
||||
#define CALIBRATION_H
|
||||
|
||||
#include "Device/device.h"
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include "calkit.h"
|
||||
#include "Traces/tracemodel.h"
|
||||
#include <QDateTime>
|
||||
#include "calkit.h"
|
||||
|
||||
class Calibration
|
||||
{
|
||||
public:
|
||||
Calibration();
|
||||
|
||||
enum class Measurement {
|
||||
Port1Open,
|
||||
Port1Short,
|
||||
Port1Load,
|
||||
Port2Open,
|
||||
Port2Short,
|
||||
Port2Load,
|
||||
Isolation,
|
||||
Through,
|
||||
};
|
||||
void clearMeasurements();
|
||||
void clearMeasurement(Measurement type);
|
||||
void addMeasurement(Measurement type, Protocol::Datapoint &d);
|
||||
|
||||
enum class Type {
|
||||
Port1SOL,
|
||||
Port2SOL,
|
||||
FullSOLT,
|
||||
None,
|
||||
};
|
||||
|
||||
|
||||
bool calculationPossible(Type type);
|
||||
bool constructErrorTerms(Type type);
|
||||
void resetErrorTerms();
|
||||
|
||||
void correctMeasurement(Protocol::Datapoint &d);
|
||||
|
||||
enum class InterpolationType {
|
||||
Unchanged, // Nothing has changed, settings and calibration points match
|
||||
Exact, // Every frequency point in settings has an exact calibration point (but there are more calibration points outside of the sweep)
|
||||
Interpolate, // Every point in the sweep can be interpolated between two calibration points
|
||||
Extrapolate, // At least one point in sweep is outside of the calibration and has to be extrapolated
|
||||
NoCalibration, // No calibration available
|
||||
};
|
||||
|
||||
InterpolationType getInterpolation(Protocol::SweepSettings settings);
|
||||
|
||||
static QString MeasurementToString(Measurement m);
|
||||
static QString TypeToString(Type t);
|
||||
|
||||
class MeasurementInfo {
|
||||
public:
|
||||
QString name, prerequisites;
|
||||
double fmin, fmax;
|
||||
unsigned int points;
|
||||
QDateTime timestamp;
|
||||
};
|
||||
|
||||
static const std::vector<Type> Types();
|
||||
static const std::vector<Measurement> Measurements(Type type = Type::None);
|
||||
MeasurementInfo getMeasurementInfo(Measurement m);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Calibration& c);
|
||||
friend std::istream& operator >> (std::istream &in, Calibration& c);
|
||||
int nPoints() {
|
||||
return points.size();
|
||||
}
|
||||
|
||||
std::vector<Trace*> getErrorTermTraces();
|
||||
|
||||
bool openFromFile(QString filename = QString());
|
||||
bool saveToFile(QString filename = QString());
|
||||
Type getType() const;
|
||||
|
||||
Calkit& getCalibrationKit();
|
||||
void setCalibrationKit(const Calkit &value);
|
||||
|
||||
private:
|
||||
void construct12TermPoints();
|
||||
void constructPort1SOL();
|
||||
void constructPort2SOL();
|
||||
bool SanityCheckSamples(std::vector<Measurement> &requiredMeasurements);
|
||||
class Point
|
||||
{
|
||||
public:
|
||||
double frequency;
|
||||
// Forward error terms
|
||||
std::complex<double> fe00, fe11, fe10e01, fe10e32, fe22, fe30;
|
||||
// Reverse error terms
|
||||
std::complex<double> re33, re11, re23e32, re23e01, re22, re03;
|
||||
};
|
||||
Point getCalibrationPoint(Protocol::Datapoint &d);
|
||||
/*
|
||||
* Constructs directivity, match and tracking correction factors from measurements of three distinct impedances
|
||||
* Normally, an open, short and load are used (with ideal reflection coefficients of 1, -1 and 0 respectively).
|
||||
* The actual reflection coefficients can be passed on as optional arguments to take into account the non-ideal
|
||||
* calibration kit.
|
||||
*/
|
||||
void computeSOL(std::complex<double> s_m,
|
||||
std::complex<double> o_m,
|
||||
std::complex<double> l_m,
|
||||
std::complex<double> &directivity,
|
||||
std::complex<double> &match,
|
||||
std::complex<double> &tracking,
|
||||
std::complex<double> o_c = std::complex<double>(1.0, 0),
|
||||
std::complex<double> s_c = std::complex<double>(-1.0, 0),
|
||||
std::complex<double> l_c = std::complex<double>(0, 0));
|
||||
std::complex<double> correctSOL(std::complex<double> measured,
|
||||
std::complex<double> directivity,
|
||||
std::complex<double> match,
|
||||
std::complex<double> tracking);
|
||||
class MeasurementData {
|
||||
public:
|
||||
QDateTime timestamp;
|
||||
std::vector<Protocol::Datapoint> datapoints;
|
||||
};
|
||||
Type type;
|
||||
|
||||
std::map<Measurement, MeasurementData> measurements;
|
||||
double minFreq, maxFreq;
|
||||
std::vector<Point> points;
|
||||
|
||||
Calkit kit;
|
||||
};
|
||||
|
||||
#endif // CALIBRATION_H
|
@ -0,0 +1,73 @@
|
||||
#include "calibrationtracedialog.h"
|
||||
#include "ui_calibrationtracedialog.h"
|
||||
#include "measurementmodel.h"
|
||||
#include <QStyle>
|
||||
|
||||
CalibrationTraceDialog::CalibrationTraceDialog(Calibration *cal, Calibration::Type type) :
|
||||
QDialog(nullptr),
|
||||
ui(new Ui::CalibrationTraceDialog),
|
||||
cal(cal),
|
||||
requestedType(type)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->bApply->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton));
|
||||
measurements = cal->Measurements(type);
|
||||
if(requestedType == Calibration::Type::None) {
|
||||
ui->bApply->setVisible(false);
|
||||
}
|
||||
model = new MeasurementModel(cal, measurements);
|
||||
ui->tableView->setModel(model);
|
||||
ui->tableView->setColumnWidth(0, 100);
|
||||
ui->tableView->setColumnWidth(1, 350);
|
||||
ui->tableView->setColumnWidth(2, 320);
|
||||
ui->tableView->setColumnWidth(3, 160);
|
||||
UpdateApplyButton();
|
||||
}
|
||||
|
||||
CalibrationTraceDialog::~CalibrationTraceDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void CalibrationTraceDialog::measurementComplete(Calibration::Measurement m)
|
||||
{
|
||||
model->measurementUpdated(m);
|
||||
UpdateApplyButton();
|
||||
}
|
||||
|
||||
void CalibrationTraceDialog::UpdateApplyButton()
|
||||
{
|
||||
ui->bApply->setEnabled(cal->calculationPossible(requestedType));
|
||||
}
|
||||
|
||||
void CalibrationTraceDialog::on_bDelete_clicked()
|
||||
{
|
||||
auto measurement = measurements[ui->tableView->currentIndex().row()];
|
||||
cal->clearMeasurement(measurement);
|
||||
model->measurementUpdated(measurement);
|
||||
UpdateApplyButton();
|
||||
}
|
||||
|
||||
void CalibrationTraceDialog::on_bMeasure_clicked()
|
||||
{
|
||||
auto measurement = measurements[ui->tableView->currentIndex().row()];
|
||||
emit triggerMeasurement(measurement);
|
||||
}
|
||||
|
||||
void CalibrationTraceDialog::on_bApply_clicked()
|
||||
{
|
||||
emit applyCalibration(requestedType);
|
||||
accept();
|
||||
}
|
||||
|
||||
void CalibrationTraceDialog::on_bOpen_clicked()
|
||||
{
|
||||
cal->openFromFile();
|
||||
UpdateApplyButton();
|
||||
emit applyCalibration(cal->getType());
|
||||
}
|
||||
|
||||
void CalibrationTraceDialog::on_bSave_clicked()
|
||||
{
|
||||
cal->saveToFile();
|
||||
}
|
44
Software/PC_Application/Calibration/calibrationtracedialog.h
Normal file
@ -0,0 +1,44 @@
|
||||
#ifndef CALIBRATIONTRACEDIALOG_H
|
||||
#define CALIBRATIONTRACEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "calibration.h"
|
||||
#include "measurementmodel.h"
|
||||
|
||||
namespace Ui {
|
||||
class CalibrationTraceDialog;
|
||||
}
|
||||
|
||||
class CalibrationTraceDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CalibrationTraceDialog(Calibration *cal, Calibration::Type type = Calibration::Type::None);
|
||||
~CalibrationTraceDialog();
|
||||
|
||||
public slots:
|
||||
void measurementComplete(Calibration::Measurement m);
|
||||
signals:
|
||||
void triggerMeasurement(Calibration::Measurement m);
|
||||
void applyCalibration(Calibration::Type type);
|
||||
|
||||
private slots:
|
||||
void on_bDelete_clicked();
|
||||
void on_bMeasure_clicked();
|
||||
void on_bApply_clicked();
|
||||
|
||||
void on_bOpen_clicked();
|
||||
|
||||
void on_bSave_clicked();
|
||||
|
||||
private:
|
||||
void UpdateApplyButton();
|
||||
Ui::CalibrationTraceDialog *ui;
|
||||
Calibration *cal;
|
||||
Calibration::Type requestedType;
|
||||
std::vector<Calibration::Measurement> measurements;
|
||||
MeasurementModel *model;
|
||||
};
|
||||
|
||||
#endif // CALIBRATIONTRACEDIALOG_H
|
116
Software/PC_Application/Calibration/calibrationtracedialog.ui
Normal file
@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalibrationTraceDialog</class>
|
||||
<widget class="QDialog" name="CalibrationTraceDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1066</width>
|
||||
<height>396</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Calibration Traces</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="tableView">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="bMeasure">
|
||||
<property name="text">
|
||||
<string>Measure</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-start">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bDelete">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bOpen">
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-open"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bSave">
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bApply">
|
||||
<property name="text">
|
||||
<string>Apply Calibration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
290
Software/PC_Application/Calibration/calkit.cpp
Normal file
@ -0,0 +1,290 @@
|
||||
#include "calkit.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include "calkitdialog.h"
|
||||
#include <math.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Calkit::Calkit()
|
||||
: ts_open(nullptr),
|
||||
ts_short(nullptr),
|
||||
ts_load(nullptr),
|
||||
ts_through(nullptr),
|
||||
ts_cached(false)
|
||||
{
|
||||
open_Z0 = 50.0;
|
||||
open_delay = 0.0;
|
||||
open_loss = 0.0;
|
||||
open_C0 = 0.0;
|
||||
open_C1 = 0.0;
|
||||
open_C2 = 0.0;
|
||||
open_C3 = 0.0;
|
||||
|
||||
short_Z0 = 50.0;
|
||||
short_delay = 0.0;
|
||||
short_loss = 0.0;
|
||||
short_L0 = 0.0;
|
||||
short_L1 = 0.0;
|
||||
short_L2 = 0.0;
|
||||
short_L3 = 0.0;
|
||||
|
||||
load_Z0 = 50.0;
|
||||
|
||||
through_Z0 = 50.0;
|
||||
through_delay = 0.0;
|
||||
through_loss = 0.0;
|
||||
|
||||
open_measurements = false;
|
||||
short_measurements = false;
|
||||
load_measurements = false;
|
||||
through_measurements = false;
|
||||
|
||||
open_file = "";
|
||||
short_file = "";
|
||||
load_file = "";
|
||||
through_file = "";
|
||||
|
||||
open_Sparam = 0;
|
||||
short_Sparam = 0;
|
||||
load_Sparam = 0;
|
||||
through_Sparam1 = 0;
|
||||
through_Sparam2 = 1;
|
||||
}
|
||||
|
||||
void Calkit::toFile(std::string filename)
|
||||
{
|
||||
ofstream file;
|
||||
file.open(filename);
|
||||
file << std::fixed << std::setprecision(12);
|
||||
file << open_measurements << "\n" << short_measurements << "\n" << load_measurements << "\n" << through_measurements << "\n";
|
||||
file << open_Z0 << "\n" << open_delay << "\n" << open_loss << "\n" << open_C0 << "\n" << open_C1 << "\n" << open_C2 << "\n" << open_C3 << "\n";
|
||||
file << short_Z0 << "\n" << short_delay << "\n" << short_loss << "\n" << short_L0 << "\n" << short_L1 << "\n" << short_L2 << "\n" << short_L3 << "\n";
|
||||
file << load_Z0 << "\n";
|
||||
file << through_Z0 << "\n" << through_delay << "\n" << through_loss << "\n";
|
||||
if(open_measurements) {
|
||||
file << open_file << "\n" << open_Sparam << "\n";
|
||||
}
|
||||
if(short_measurements) {
|
||||
file << short_file << "\n" << short_Sparam << "\n";
|
||||
}
|
||||
if(load_measurements) {
|
||||
file << load_file << "\n" << load_Sparam << "\n";
|
||||
}
|
||||
if(through_measurements) {
|
||||
file << through_file << "\n" << through_Sparam1 << "\n" << through_Sparam2 << "\n";
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
Calkit Calkit::fromFile(std::string filename)
|
||||
{
|
||||
Calkit c;
|
||||
ifstream file;
|
||||
file.open(filename);
|
||||
if(!file.is_open()) {
|
||||
throw runtime_error("Unable to open file");
|
||||
}
|
||||
file >> c.open_measurements;
|
||||
file >> c.short_measurements;
|
||||
file >> c.load_measurements;
|
||||
file >> c.through_measurements;
|
||||
file >> c.open_Z0;
|
||||
file >> c.open_delay;
|
||||
file >> c.open_loss;
|
||||
file >> c.open_C0;
|
||||
file >> c.open_C1;
|
||||
file >> c.open_C2;
|
||||
file >> c.open_C3;
|
||||
file >> c.short_Z0;
|
||||
file >> c.short_delay;
|
||||
file >> c.short_loss;
|
||||
file >> c.short_L0;
|
||||
file >> c.short_L1;
|
||||
file >> c.short_L2;
|
||||
file >> c.short_L3;
|
||||
file >> c.load_Z0;
|
||||
file >> c.through_Z0;
|
||||
file >> c.through_delay;
|
||||
file >> c.through_loss;
|
||||
if(c.open_measurements) {
|
||||
file >> c.open_file;
|
||||
file >> c.open_Sparam;
|
||||
}
|
||||
if(c.short_measurements) {
|
||||
file >> c.short_file;
|
||||
file >> c.short_Sparam;
|
||||
}
|
||||
if(c.load_measurements) {
|
||||
file >> c.load_file;
|
||||
file >> c.load_Sparam;
|
||||
}
|
||||
if(c.through_measurements) {
|
||||
file >> c.through_file;
|
||||
file >> c.through_Sparam1;
|
||||
file >> c.through_Sparam2;
|
||||
}
|
||||
file.close();
|
||||
return c;
|
||||
}
|
||||
|
||||
void Calkit::edit()
|
||||
{
|
||||
auto dialog = new CalkitDialog(*this);
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
Calkit::Reflection Calkit::toReflection(double frequency)
|
||||
{
|
||||
fillTouchstoneCache();
|
||||
Reflection ref;
|
||||
if(load_measurements) {
|
||||
ref.Load = ts_load->interpolate(frequency).S[0];
|
||||
} else {
|
||||
auto imp_load = complex<double>(load_Z0, 0);
|
||||
ref.Load = (imp_load - complex<double>(50.0)) / (imp_load + complex<double>(50.0));
|
||||
}
|
||||
|
||||
if(open_measurements) {
|
||||
ref.Open = ts_open->interpolate(frequency).S[0];
|
||||
} else {
|
||||
// calculate fringing capacitance for open
|
||||
double Cfringing = open_C0 * 1e-15 + open_C1 * 1e-27 * frequency + open_C2 * 1e-36 * pow(frequency, 2) + open_C3 * 1e-45 * pow(frequency, 3);
|
||||
// convert to impedance
|
||||
if (Cfringing == 0) {
|
||||
// special case to avoid issues with infinity
|
||||
ref.Open = complex<double>(1.0, 0);
|
||||
} else {
|
||||
auto imp_open = complex<double>(0, -1.0 / (frequency * 2 * M_PI * Cfringing));
|
||||
ref.Open = (imp_open - complex<double>(50.0)) / (imp_open + complex<double>(50.0));
|
||||
}
|
||||
// transform the delay into a phase shift for the given frequency
|
||||
double open_phaseshift = -2 * M_PI * frequency * open_delay * 1e-12;
|
||||
double open_att_db = open_loss * 1e9 * 4.3429 * open_delay * 1e-12 / open_Z0 * sqrt(frequency / 1e9);
|
||||
double open_att = pow(10.0, -open_att_db / 10.0);
|
||||
auto open_correction = polar<double>(open_att, open_phaseshift);
|
||||
ref.Open *= open_correction;
|
||||
}
|
||||
|
||||
if(short_measurements) {
|
||||
ref.Short = ts_short->interpolate(frequency).S[0];
|
||||
} else {
|
||||
// calculate inductance for short
|
||||
double Lseries = short_L0 * 1e-12 + short_L1 * 1e-24 * frequency + short_L2 * 1e-33 * pow(frequency, 2) + short_L3 * 1e-42 * pow(frequency, 3);
|
||||
// convert to impedance
|
||||
auto imp_short = complex<double>(0, frequency * 2 * M_PI * Lseries);
|
||||
ref.Short = (imp_short - complex<double>(50.0)) / (imp_short + complex<double>(50.0));
|
||||
// transform the delay into a phase shift for the given frequency
|
||||
double short_phaseshift = -2 * M_PI * frequency * short_delay * 1e-12;
|
||||
double short_att_db = short_loss * 1e9 * 4.3429 * short_delay * 1e-12 / short_Z0 * sqrt(frequency / 1e9);;
|
||||
double short_att = pow(10.0, -short_att_db / 10.0);
|
||||
auto short_correction = polar<double>(short_att, short_phaseshift);
|
||||
ref.Short *= short_correction;
|
||||
}
|
||||
|
||||
if(through_measurements) {
|
||||
auto interp = ts_through->interpolate(frequency);
|
||||
ref.ThroughS11 = interp.S[0];
|
||||
ref.ThroughS12 = interp.S[1];
|
||||
ref.ThroughS21 = interp.S[2];
|
||||
ref.ThroughS22 = interp.S[3];
|
||||
} else {
|
||||
// calculate effect of through
|
||||
double through_phaseshift = -2 * M_PI * frequency * through_delay * 1e-12;
|
||||
double through_att_db = through_loss * 1e9 * 4.3429 * through_delay * 1e-12 / through_Z0 * sqrt(frequency / 1e9);;
|
||||
double through_att = pow(10.0, -through_att_db / 10.0);
|
||||
ref.ThroughS12 = polar<double>(through_att, through_phaseshift);
|
||||
// Assume symmetric and perfectly matched through for other parameters
|
||||
ref.ThroughS21 = ref.ThroughS12;
|
||||
ref.ThroughS11 = 0.0;
|
||||
ref.ThroughS22 = 0.0;
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
double Calkit::minFreq()
|
||||
{
|
||||
fillTouchstoneCache();
|
||||
double min = std::numeric_limits<double>::min();
|
||||
array<Touchstone*, 4> ts_list = {ts_open, ts_short, ts_load, ts_through};
|
||||
// find the highest minimum frequency in all measurement files
|
||||
for(auto ts : ts_list) {
|
||||
if(!ts) {
|
||||
// this calibration standard is defined by coefficients, no minimum frequency
|
||||
continue;
|
||||
}
|
||||
if(ts->minFreq() > min) {
|
||||
min = ts->minFreq();
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
double Calkit::maxFreq()
|
||||
{
|
||||
fillTouchstoneCache();
|
||||
double max = std::numeric_limits<double>::max();
|
||||
array<Touchstone*, 4> ts_list = {ts_open, ts_short, ts_load, ts_through};
|
||||
// find the highest minimum frequency in all measurement files
|
||||
for(auto ts : ts_list) {
|
||||
if(!ts) {
|
||||
// this calibration standard is defined by coefficients, no minimum frequency
|
||||
continue;
|
||||
}
|
||||
if(ts->maxFreq() < max) {
|
||||
max = ts->maxFreq();
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
void Calkit::clearTouchstoneCache()
|
||||
{
|
||||
if(ts_open) {
|
||||
delete ts_open;
|
||||
ts_open = nullptr;
|
||||
}
|
||||
if(ts_short) {
|
||||
delete ts_short;
|
||||
ts_short = nullptr;
|
||||
}
|
||||
if(ts_load) {
|
||||
delete ts_load;
|
||||
ts_load = nullptr;
|
||||
}
|
||||
if(ts_through) {
|
||||
delete ts_through;
|
||||
ts_through = nullptr;
|
||||
}
|
||||
ts_cached = false;
|
||||
}
|
||||
|
||||
void Calkit::fillTouchstoneCache()
|
||||
{
|
||||
if(ts_cached) {
|
||||
return;
|
||||
}
|
||||
if(open_measurements) {
|
||||
ts_open = new Touchstone(1);
|
||||
*ts_open = Touchstone::fromFile(open_file);
|
||||
ts_open->reduceTo1Port(open_Sparam);
|
||||
}
|
||||
if(short_measurements) {
|
||||
ts_short = new Touchstone(1);
|
||||
*ts_short = Touchstone::fromFile(short_file);
|
||||
ts_open->reduceTo1Port(short_Sparam);
|
||||
}
|
||||
if(load_measurements) {
|
||||
ts_load = new Touchstone(1);
|
||||
*ts_load = Touchstone::fromFile(load_file);
|
||||
ts_open->reduceTo1Port(load_Sparam);
|
||||
}
|
||||
if(through_measurements) {
|
||||
ts_through = new Touchstone(2);
|
||||
*ts_through = Touchstone::fromFile(through_file);
|
||||
ts_through->reduceTo2Port(through_Sparam1, through_Sparam2);
|
||||
}
|
||||
ts_cached = true;
|
||||
}
|
50
Software/PC_Application/Calibration/calkit.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef CALKIT_H
|
||||
#define CALKIT_H
|
||||
|
||||
#include <string>
|
||||
#include <complex>
|
||||
#include "touchstone.h"
|
||||
|
||||
class Calkit
|
||||
{
|
||||
friend class CalkitDialog;
|
||||
public:
|
||||
Calkit();
|
||||
|
||||
class Reflection {
|
||||
public:
|
||||
std::complex<double> Open;
|
||||
std::complex<double> Short;
|
||||
std::complex<double> Load;
|
||||
std::complex<double> ThroughS11, ThroughS12, ThroughS21, ThroughS22;
|
||||
};
|
||||
|
||||
void toFile(std::string filename);
|
||||
static Calkit fromFile(std::string filename);
|
||||
void edit();
|
||||
Reflection toReflection(double frequency);
|
||||
double minFreq();
|
||||
double maxFreq();
|
||||
private:
|
||||
double open_Z0, open_delay, open_loss, open_C0, open_C1, open_C2, open_C3;
|
||||
double short_Z0, short_delay, short_loss, short_L0, short_L1, short_L2, short_L3;
|
||||
double load_Z0;
|
||||
double through_Z0, through_delay, through_loss;
|
||||
|
||||
// coefficients/measurement file switch
|
||||
bool open_measurements;
|
||||
bool short_measurements;
|
||||
bool load_measurements;
|
||||
bool through_measurements;
|
||||
|
||||
std::string open_file, short_file, load_file, through_file;
|
||||
int open_Sparam, short_Sparam, load_Sparam, through_Sparam1, through_Sparam2;
|
||||
|
||||
Touchstone *ts_open, *ts_short, *ts_load, *ts_through;
|
||||
bool ts_cached;
|
||||
|
||||
void clearTouchstoneCache();
|
||||
void fillTouchstoneCache();
|
||||
};
|
||||
|
||||
#endif // CALKIT_H
|
219
Software/PC_Application/Calibration/calkitdialog.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
#include "calkitdialog.h"
|
||||
#include "ui_calkitdialog.h"
|
||||
#include <QPushButton>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include <fstream>
|
||||
#include <touchstone.h>
|
||||
#include <QtGlobal>
|
||||
|
||||
using namespace std;
|
||||
|
||||
CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::CalkitDialog),
|
||||
open_ok(true),
|
||||
short_ok(true),
|
||||
load_ok(true),
|
||||
through_ok(true),
|
||||
editKit(c)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->OpenType->setId(ui->open_coefficients, 0);
|
||||
ui->OpenType->setId(ui->open_measurement, 1);
|
||||
|
||||
ui->ShortType->setId(ui->short_coefficients, 0);
|
||||
ui->ShortType->setId(ui->short_measurement, 1);
|
||||
|
||||
ui->LoadType->setId(ui->load_coefficients, 0);
|
||||
ui->LoadType->setId(ui->load_measurement, 1);
|
||||
|
||||
ui->ThroughType->setId(ui->through_coefficients, 0);
|
||||
ui->ThroughType->setId(ui->through_measurement, 1);
|
||||
|
||||
ui->open_touchstone->setPorts(1);
|
||||
ui->short_touchstone->setPorts(1);
|
||||
ui->load_touchstone->setPorts(1);
|
||||
ui->through_touchstone->setPorts(2);
|
||||
|
||||
editKit.clearTouchstoneCache();
|
||||
ownKit = editKit;
|
||||
updateEntries();
|
||||
|
||||
auto UpdateStatus = [=]() {
|
||||
bool ok = true;
|
||||
if(ui->open_measurement->isChecked() && !ui->open_touchstone->getStatus()) {
|
||||
ok = false;
|
||||
}
|
||||
if(ui->short_measurement->isChecked() && !ui->short_touchstone->getStatus()) {
|
||||
ok = false;
|
||||
}
|
||||
if(ui->load_measurement->isChecked() && !ui->load_touchstone->getStatus()) {
|
||||
ok = false;
|
||||
}
|
||||
if(ui->through_measurement->isChecked() && !ui->through_touchstone->getStatus()) {
|
||||
ok = false;
|
||||
}
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok);
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(ok);
|
||||
};
|
||||
|
||||
connect(ui->open_touchstone, &TouchstoneImport::statusChanged, UpdateStatus);
|
||||
connect(ui->short_touchstone, &TouchstoneImport::statusChanged, UpdateStatus);
|
||||
connect(ui->load_touchstone, &TouchstoneImport::statusChanged, UpdateStatus);
|
||||
connect(ui->through_touchstone, &TouchstoneImport::statusChanged, UpdateStatus);
|
||||
|
||||
connect(ui->OpenType, qOverload<int>(&QButtonGroup::buttonClicked), [=](int) {
|
||||
UpdateStatus();
|
||||
});
|
||||
connect(ui->ShortType, qOverload<int>(&QButtonGroup::buttonClicked), [=](int) {
|
||||
UpdateStatus();
|
||||
});
|
||||
connect(ui->LoadType, qOverload<int>(&QButtonGroup::buttonClicked), [=](int) {
|
||||
UpdateStatus();
|
||||
});
|
||||
connect(ui->ThroughType, qOverload<int>(&QButtonGroup::buttonClicked), [=](int) {
|
||||
UpdateStatus();
|
||||
});
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [this]() {
|
||||
parseEntries();
|
||||
editKit = ownKit;
|
||||
delete this;
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this]() {
|
||||
delete this;
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){
|
||||
auto filename = QFileDialog::getOpenFileName(this, "Open calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if(filename.length() > 0) {
|
||||
ownKit = Calkit::fromFile(filename.toStdString());
|
||||
updateEntries();
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Save calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if(filename.length() > 0) {
|
||||
parseEntries();
|
||||
ownKit.toFile(filename.toStdString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CalkitDialog::~CalkitDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void CalkitDialog::parseEntries()
|
||||
{
|
||||
|
||||
// type
|
||||
ownKit.open_measurements = ui->open_measurement->isChecked();
|
||||
ownKit.short_measurements = ui->short_measurement->isChecked();
|
||||
ownKit.load_measurements = ui->load_measurement->isChecked();
|
||||
ownKit.through_measurements = ui->through_measurement->isChecked();
|
||||
|
||||
// coefficients
|
||||
ownKit.open_Z0 = ui->open_Z0->text().toDouble();
|
||||
ownKit.open_delay = ui->open_delay->text().toDouble();
|
||||
ownKit.open_loss = ui->open_loss->text().toDouble();
|
||||
ownKit.open_C0 = ui->open_C0->text().toDouble();
|
||||
ownKit.open_C1 = ui->open_C1->text().toDouble();
|
||||
ownKit.open_C2 = ui->open_C2->text().toDouble();
|
||||
ownKit.open_C3 = ui->open_C3->text().toDouble();
|
||||
|
||||
ownKit.short_Z0 = ui->short_Z0->text().toDouble();
|
||||
ownKit.short_delay = ui->short_delay->text().toDouble();
|
||||
ownKit.short_loss = ui->short_loss->text().toDouble();
|
||||
ownKit.short_L0 = ui->short_L0->text().toDouble();
|
||||
ownKit.short_L1 = ui->short_L1->text().toDouble();
|
||||
ownKit.short_L2 = ui->short_L2->text().toDouble();
|
||||
ownKit.short_L3 = ui->short_L3->text().toDouble();
|
||||
|
||||
ownKit.load_Z0 = ui->load_Z0->text().toDouble();
|
||||
|
||||
ownKit.through_Z0 = ui->through_Z0->text().toDouble();
|
||||
ownKit.through_delay = ui->through_delay->text().toDouble();
|
||||
ownKit.through_loss = ui->through_loss->text().toDouble();
|
||||
|
||||
// file
|
||||
ownKit.open_file = ui->open_touchstone->getFilename().toStdString();
|
||||
ownKit.short_file = ui->short_touchstone->getFilename().toStdString();
|
||||
ownKit.load_file = ui->load_touchstone->getFilename().toStdString();
|
||||
ownKit.through_file = ui->through_touchstone->getFilename().toStdString();
|
||||
|
||||
ownKit.open_Sparam = ui->open_touchstone->getPorts()[0];
|
||||
ownKit.short_Sparam = ui->short_touchstone->getPorts()[0];
|
||||
ownKit.load_Sparam = ui->load_touchstone->getPorts()[0];
|
||||
ownKit.through_Sparam1 = ui->through_touchstone->getPorts()[0];
|
||||
ownKit.through_Sparam2 = ui->through_touchstone->getPorts()[1];
|
||||
}
|
||||
|
||||
void CalkitDialog::updateEntries()
|
||||
{
|
||||
// Coefficients
|
||||
ui->open_Z0->setText(QString::number(ownKit.open_Z0));
|
||||
ui->open_delay->setText(QString::number(ownKit.open_delay));
|
||||
ui->open_loss->setText(QString::number(ownKit.open_loss));
|
||||
ui->open_C0->setText(QString::number(ownKit.open_C0));
|
||||
ui->open_C1->setText(QString::number(ownKit.open_C1));
|
||||
ui->open_C2->setText(QString::number(ownKit.open_C2));
|
||||
ui->open_C3->setText(QString::number(ownKit.open_C3));
|
||||
|
||||
ui->short_Z0->setText(QString::number(ownKit.short_Z0));
|
||||
ui->short_delay->setText(QString::number(ownKit.short_delay));
|
||||
ui->short_loss->setText(QString::number(ownKit.short_loss));
|
||||
ui->short_L0->setText(QString::number(ownKit.short_L0));
|
||||
ui->short_L1->setText(QString::number(ownKit.short_L1));
|
||||
ui->short_L2->setText(QString::number(ownKit.short_L2));
|
||||
ui->short_L3->setText(QString::number(ownKit.short_L3));
|
||||
|
||||
ui->load_Z0->setText(QString::number(ownKit.load_Z0));
|
||||
|
||||
ui->through_Z0->setText(QString::number(ownKit.through_Z0));
|
||||
ui->through_delay->setText(QString::number(ownKit.through_delay));
|
||||
ui->through_loss->setText(QString::number(ownKit.through_loss));
|
||||
|
||||
// Measurements
|
||||
ui->open_touchstone->setFile(QString::fromStdString(ownKit.open_file));
|
||||
ui->open_touchstone->selectPort(0, ownKit.open_Sparam);
|
||||
|
||||
ui->short_touchstone->setFile(QString::fromStdString(ownKit.short_file));
|
||||
ui->short_touchstone->selectPort(0, ownKit.short_Sparam);
|
||||
|
||||
ui->load_touchstone->setFile(QString::fromStdString(ownKit.load_file));
|
||||
ui->load_touchstone->selectPort(0, ownKit.load_Sparam);
|
||||
|
||||
ui->through_touchstone->setFile(QString::fromStdString(ownKit.through_file));
|
||||
ui->through_touchstone->selectPort(0, ownKit.through_Sparam1);
|
||||
ui->through_touchstone->selectPort(1, ownKit.through_Sparam2);
|
||||
|
||||
// Type
|
||||
if (ownKit.open_measurements) {
|
||||
ui->open_measurement->click();
|
||||
} else {
|
||||
ui->open_coefficients->click();
|
||||
}
|
||||
|
||||
if (ownKit.short_measurements) {
|
||||
ui->short_measurement->click();
|
||||
} else {
|
||||
ui->short_coefficients->click();
|
||||
}
|
||||
|
||||
if (ownKit.load_measurements) {
|
||||
ui->load_measurement->click();
|
||||
} else {
|
||||
ui->load_coefficients->click();
|
||||
}
|
||||
|
||||
if (ownKit.through_measurements) {
|
||||
ui->through_measurement->click();
|
||||
} else {
|
||||
ui->through_coefficients->click();
|
||||
}
|
||||
}
|
33
Software/PC_Application/Calibration/calkitdialog.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef CALKITDIALOG_H
|
||||
#define CALKITDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QAbstractButton>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include "calkit.h"
|
||||
|
||||
namespace Ui {
|
||||
class CalkitDialog;
|
||||
}
|
||||
|
||||
class CalkitDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CalkitDialog(Calkit &c, QWidget *parent = nullptr);
|
||||
~CalkitDialog();
|
||||
|
||||
private:
|
||||
void parseEntries();
|
||||
void updateEntries();
|
||||
Ui::CalkitDialog *ui;
|
||||
|
||||
bool open_ok, short_ok, load_ok, through_ok;
|
||||
|
||||
Calkit ownKit;
|
||||
Calkit &editKit;
|
||||
};
|
||||
|
||||
#endif // CALKITDIALOG_H
|
601
Software/PC_Application/Calibration/calkitdialog.ui
Normal file
@ -0,0 +1,601 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalkitDialog</class>
|
||||
<widget class="QDialog" name="CalkitDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1121</width>
|
||||
<height>345</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Calibration Kit Coefficients</string>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_17">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="open_coefficients">
|
||||
<property name="text">
|
||||
<string>Coefficients</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">OpenType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="open_measurement">
|
||||
<property name="text">
|
||||
<string>Measurement file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">OpenType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="open_stack">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Offset delay [ps]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="open_delay"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Offset loss [GΩ/s]: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="open_loss"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>C0 [10<sup>-15</sup>F]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="open_C0"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>C1 [10<sup>-27</sup>F/Hz]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="open_C1"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>C2 [10<sup>-36</sup>F/Hz<sup>2</sup>]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="open_C2"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>C0 [10<sup>-45</sup>F/Hz<sup>3</sup>]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="open_C3"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="open_Z0"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Z0 [Ω]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0">
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="open_touchstone" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Short</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="short_coefficients">
|
||||
<property name="text">
|
||||
<string>Coefficients</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">ShortType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="short_measurement">
|
||||
<property name="text">
|
||||
<string>Measurement file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">ShortType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="short_stack">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page_3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Offset delay [ps]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="short_delay"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Offset loss [GΩ/s]: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="short_loss"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>L0 [10<span style=" vertical-align:super;">-12</span>F]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="short_L0"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>L1 [10<span style=" vertical-align:super;">-24</span>F/Hz]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="short_L1"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>L2 [10<span style=" vertical-align:super;">-33</span>F/Hz<span style=" vertical-align:super;">2</span>]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="short_L2"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>L3 [10<span style=" vertical-align:super;">-42</span>F/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="short_L3"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="short_Z0"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Z0 [Ω]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5" stretch="0">
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="short_touchstone" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Load</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="load_coefficients">
|
||||
<property name="text">
|
||||
<string>Coefficients</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">LoadType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="load_measurement">
|
||||
<property name="text">
|
||||
<string>Measurement file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">LoadType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="load_stack">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page_5">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_30">
|
||||
<property name="text">
|
||||
<string>Z0 [Ω]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="load_Z0"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_6">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6" stretch="0">
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="load_touchstone" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Through</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_13">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="through_coefficients">
|
||||
<property name="text">
|
||||
<string>Coefficients</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">ThroughType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="through_measurement">
|
||||
<property name="text">
|
||||
<string>Measurement file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">ThroughType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="through_stack">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page_7">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_14">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_35">
|
||||
<property name="text">
|
||||
<string>Z0 [Ω]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="through_Z0"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Offset delay [ps]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="through_delay"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Offset loss [GΩ/s]: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="through_loss"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_8">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8" stretch="0">
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="through_touchstone" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Open|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>TouchstoneImport</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>CustomWidgets/touchstoneimport.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>open_Z0</tabstop>
|
||||
<tabstop>open_delay</tabstop>
|
||||
<tabstop>open_loss</tabstop>
|
||||
<tabstop>open_C0</tabstop>
|
||||
<tabstop>open_C1</tabstop>
|
||||
<tabstop>open_C2</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>OpenType</sender>
|
||||
<signal>buttonClicked(int)</signal>
|
||||
<receiver>open_stack</receiver>
|
||||
<slot>setCurrentIndex(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>141</x>
|
||||
<y>187</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>ShortType</sender>
|
||||
<signal>buttonClicked(int)</signal>
|
||||
<receiver>short_stack</receiver>
|
||||
<slot>setCurrentIndex(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>420</x>
|
||||
<y>187</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>LoadType</sender>
|
||||
<signal>buttonClicked(int)</signal>
|
||||
<receiver>load_stack</receiver>
|
||||
<slot>setCurrentIndex(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>699</x>
|
||||
<y>187</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>ThroughType</sender>
|
||||
<signal>buttonClicked(int)</signal>
|
||||
<receiver>through_stack</receiver>
|
||||
<slot>setCurrentIndex(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>978</x>
|
||||
<y>187</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="LoadType"/>
|
||||
<buttongroup name="OpenType"/>
|
||||
<buttongroup name="ShortType"/>
|
||||
<buttongroup name="ThroughType"/>
|
||||
</buttongroups>
|
||||
</ui>
|
88
Software/PC_Application/Calibration/measurementmodel.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
#include "measurementmodel.h"
|
||||
#include "../unit.h"
|
||||
#include <algorithm>
|
||||
|
||||
MeasurementModel::MeasurementModel(Calibration *cal, std::vector<Calibration::Measurement> measurements) :
|
||||
QAbstractTableModel(),
|
||||
cal(cal),
|
||||
measurements(measurements)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int MeasurementModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return measurements.size();
|
||||
}
|
||||
|
||||
int MeasurementModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return ColIndexLast;
|
||||
}
|
||||
|
||||
QVariant MeasurementModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
auto info = cal->getMeasurementInfo(measurements[index.row()]);
|
||||
if(role == Qt::DisplayRole) {
|
||||
switch(index.column()) {
|
||||
case ColIndexName:
|
||||
return info.name;
|
||||
break;
|
||||
case ColIndexDescription:
|
||||
return info.prerequisites;
|
||||
break;
|
||||
case ColIndexData:
|
||||
if(info.points > 0) {
|
||||
QString data = QString::number(info.points);
|
||||
data.append(" points from ");
|
||||
data.append(Unit::ToString(info.fmin, "Hz", " kMG"));
|
||||
data.append(" to ");
|
||||
data.append(Unit::ToString(info.fmax, "Hz", " kMG"));
|
||||
return data;
|
||||
} else {
|
||||
return "Not available";
|
||||
}
|
||||
break;
|
||||
case ColIndexDate:
|
||||
return info.timestamp.toString("dd.MM.yyyy hh:mm:ss");
|
||||
break;
|
||||
}
|
||||
} else if(role == Qt::SizeHintRole) {
|
||||
switch(index.column()) {
|
||||
case ColIndexName: return 200; break;
|
||||
case ColIndexDescription: return 500; break;
|
||||
case ColIndexData: return 300; break;
|
||||
case ColIndexDate: return 300; break;
|
||||
case ColIndexStatusSymbol: return 150; break;
|
||||
default: return QVariant(); break;
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant MeasurementModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||
switch(section) {
|
||||
case ColIndexName: return "Type"; break;
|
||||
case ColIndexDescription: return "Prerequisites"; break;
|
||||
case ColIndexData: return "Statistics"; break;
|
||||
case ColIndexDate: return "Timestamp"; break;
|
||||
case ColIndexStatusSymbol: return "Status"; break;
|
||||
default: return QVariant(); break;
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
void MeasurementModel::measurementUpdated(Calibration::Measurement m)
|
||||
{
|
||||
// find correct index in vector
|
||||
auto it = std::find(measurements.begin(), measurements.end(), m);
|
||||
if(it != measurements.end()) {
|
||||
int row = it - measurements.begin();
|
||||
emit dataChanged(index(row, 0), index(row, ColIndexLast - 1));
|
||||
}
|
||||
}
|
36
Software/PC_Application/Calibration/measurementmodel.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef MEASUREMENTMODEL_H
|
||||
#define MEASUREMENTMODEL_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QAbstractTableModel>
|
||||
#include "calibration.h"
|
||||
|
||||
class MeasurementModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MeasurementModel(Calibration *cal, std::vector<Calibration::Measurement> measurements);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
public slots:
|
||||
void measurementUpdated(Calibration::Measurement m);
|
||||
|
||||
private:
|
||||
enum {
|
||||
ColIndexName,
|
||||
ColIndexDescription,
|
||||
ColIndexData,
|
||||
ColIndexDate,
|
||||
ColIndexStatusSymbol,
|
||||
ColIndexLast
|
||||
};
|
||||
Calibration *cal;
|
||||
std::vector<Calibration::Measurement> measurements;
|
||||
};
|
||||
|
||||
#endif // MEASUREMENTMODEL_H
|
79
Software/PC_Application/CustomWidgets/siunitedit.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#include "siunitedit.h"
|
||||
|
||||
#include <QDoubleValidator>
|
||||
#include <unit.h>
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
|
||||
SIUnitEdit::SIUnitEdit(QString unit, QString prefixes, int precision, QWidget *parent)
|
||||
: QLineEdit(parent)
|
||||
{
|
||||
this->unit = unit;
|
||||
this->prefixes = prefixes;
|
||||
this->precision = precision;
|
||||
setAlignment(Qt::AlignCenter);
|
||||
installEventFilter(this);
|
||||
setValidator(new QDoubleValidator);
|
||||
connect(this, &QLineEdit::editingFinished, [this]() {
|
||||
parseNewValue(1.0);
|
||||
});
|
||||
}
|
||||
|
||||
SIUnitEdit::SIUnitEdit(QWidget *parent)
|
||||
: SIUnitEdit("", " ", 4, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SIUnitEdit::setValue(double value)
|
||||
{
|
||||
setValueQuiet(value);
|
||||
emit valueChanged(value);
|
||||
}
|
||||
|
||||
bool SIUnitEdit::eventFilter(QObject *, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
int key = static_cast<QKeyEvent *>(event)->key();
|
||||
if(key == Qt::Key_Escape) {
|
||||
// abort editing process and set old value
|
||||
setValueQuiet(_value);
|
||||
return true;
|
||||
}
|
||||
if(key == Qt::Key_Return) {
|
||||
// use new value without prefix
|
||||
parseNewValue(1.0);
|
||||
return true;
|
||||
}
|
||||
auto mod = static_cast<QKeyEvent *>(event)->modifiers();
|
||||
if (!(mod & Qt::ShiftModifier)) {
|
||||
key = tolower(key);
|
||||
}
|
||||
if(key <= 255 && prefixes.indexOf(key) >= 0) {
|
||||
// a valid prefix key was pressed
|
||||
parseNewValue(Unit::SIPrefixToFactor(key));
|
||||
return true;
|
||||
}
|
||||
} else if(event->type() == QEvent::FocusOut) {
|
||||
if(!text().isEmpty()) {
|
||||
parseNewValue(1.0);
|
||||
} else {
|
||||
setValueQuiet(_value);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SIUnitEdit::setValueQuiet(double value)
|
||||
{
|
||||
_value = value;
|
||||
clear();
|
||||
setPlaceholderText(Unit::ToString(value, unit, prefixes, precision));
|
||||
clearFocus();
|
||||
}
|
||||
|
||||
void SIUnitEdit::parseNewValue(double factor)
|
||||
{
|
||||
double v = text().toDouble() * factor;
|
||||
setValue(v);
|
||||
}
|
31
Software/PC_Application/CustomWidgets/siunitedit.h
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef SIUNITEDIT_H
|
||||
#define SIUNITEDIT_H
|
||||
|
||||
#include <QLineEdit>
|
||||
|
||||
class SIUnitEdit : public QLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SIUnitEdit(QString unit = QString(), QString prefixes = " ", int precision = 0, QWidget *parent = nullptr);
|
||||
SIUnitEdit(QWidget *parent);
|
||||
|
||||
void setUnit(QString unit) { this->unit = unit; setValueQuiet(_value); }
|
||||
void setPrefixes(QString prefixes) { this->prefixes = prefixes; setValueQuiet(_value); }
|
||||
void setPrecision(int precision) { this->precision = precision; setValueQuiet(_value); }
|
||||
double value() { return _value; }
|
||||
public slots:
|
||||
void setValue(double value);
|
||||
void setValueQuiet(double value);
|
||||
signals:
|
||||
void valueChanged(double newvalue);
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
private:
|
||||
void parseNewValue(double factor);
|
||||
QString unit, prefixes;
|
||||
int precision;
|
||||
double _value;
|
||||
};
|
||||
|
||||
#endif // SIUNITEDIT_H
|
141
Software/PC_Application/CustomWidgets/tilewidget.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#include "tilewidget.h"
|
||||
#include "ui_tilewidget.h"
|
||||
#include <QDebug>
|
||||
#include "Traces/tracebodeplot.h"
|
||||
#include "Traces/tracesmithchart.h"
|
||||
|
||||
TileWidget::TileWidget(TraceModel &model, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::TileWidget),
|
||||
splitter(0),
|
||||
isSplit(false),
|
||||
parent(0),
|
||||
child1(0),
|
||||
child2(0),
|
||||
hasContent(false),
|
||||
content(0),
|
||||
model(model)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
auto layout = new QGridLayout;
|
||||
layout->setContentsMargins(0,0,0,0);
|
||||
ui->ContentPage->setLayout(layout);
|
||||
ui->bClose->setVisible(false);
|
||||
}
|
||||
|
||||
TileWidget::~TileWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void TileWidget::splitVertically()
|
||||
{
|
||||
if(isSplit) {
|
||||
return;
|
||||
}
|
||||
isSplit = true;
|
||||
splitter = new QSplitter(Qt::Vertical);
|
||||
split();
|
||||
}
|
||||
|
||||
void TileWidget::splitHorizontally()
|
||||
{
|
||||
if(isSplit) {
|
||||
return;
|
||||
}
|
||||
isSplit = true;
|
||||
splitter = new QSplitter(Qt::Horizontal);
|
||||
split();
|
||||
}
|
||||
|
||||
void TileWidget::closeTile()
|
||||
{
|
||||
if(!parent) {
|
||||
// Unable to close toplevel tile
|
||||
return;
|
||||
}
|
||||
auto pTile = parent;
|
||||
TileWidget *absorbedTile;
|
||||
if(this == parent->child1) {
|
||||
absorbedTile = parent->child2;
|
||||
} else {
|
||||
absorbedTile = parent->child1;
|
||||
}
|
||||
delete this;
|
||||
|
||||
if(absorbedTile->isSplit) {
|
||||
pTile->isSplit = true;
|
||||
pTile->child1 = absorbedTile->child1;
|
||||
pTile->child2 = absorbedTile->child2;
|
||||
pTile->child1->parent = pTile;
|
||||
pTile->child2->parent = pTile;
|
||||
pTile->ui->ContentPage->layout()->addWidget(absorbedTile->splitter);
|
||||
auto oldsplitter = pTile->splitter;
|
||||
pTile->splitter = absorbedTile->splitter;
|
||||
delete absorbedTile;
|
||||
delete oldsplitter;
|
||||
} else if(absorbedTile->hasContent) {
|
||||
pTile->setContent(absorbedTile->content);
|
||||
delete absorbedTile;
|
||||
pTile->isSplit = false;
|
||||
delete pTile->splitter;
|
||||
pTile->splitter = nullptr;
|
||||
} else {
|
||||
delete absorbedTile;
|
||||
pTile->isSplit = false;
|
||||
pTile->hasContent = false;
|
||||
delete pTile->splitter;
|
||||
pTile->ui->stack->setCurrentWidget(pTile->ui->TilePage);
|
||||
}
|
||||
}
|
||||
|
||||
void TileWidget::setPlot(TracePlot *plot)
|
||||
{
|
||||
if(!isSplit && !hasContent) {
|
||||
setContent(plot);
|
||||
}
|
||||
}
|
||||
|
||||
TileWidget::TileWidget(TraceModel &model, TileWidget &parent)
|
||||
: TileWidget(model)
|
||||
{
|
||||
this->parent = &parent;
|
||||
ui->bClose->setVisible(true);
|
||||
}
|
||||
|
||||
void TileWidget::split()
|
||||
{
|
||||
splitter->setHandleWidth(0);
|
||||
child1 = new TileWidget(model, *this);
|
||||
child2 = new TileWidget(model, *this);
|
||||
splitter->addWidget(child1);
|
||||
splitter->addWidget(child2);
|
||||
ui->ContentPage->layout()->addWidget(splitter);
|
||||
ui->stack->setCurrentWidget(ui->ContentPage);
|
||||
}
|
||||
|
||||
void TileWidget::setContent(TracePlot *plot)
|
||||
{
|
||||
content = plot;
|
||||
hasContent = true;
|
||||
ui->ContentPage->layout()->addWidget(plot);
|
||||
ui->stack->setCurrentWidget(ui->ContentPage);
|
||||
connect(content, &TracePlot::deleted, this, &TileWidget::traceDeleted);
|
||||
}
|
||||
|
||||
void TileWidget::on_bSmithchart_clicked()
|
||||
{
|
||||
setContent(new TraceSmithChart(model));
|
||||
}
|
||||
|
||||
void TileWidget::on_bBodeplot_clicked()
|
||||
{
|
||||
setContent(new TraceBodePlot(model));
|
||||
}
|
||||
|
||||
void TileWidget::traceDeleted(TracePlot *)
|
||||
{
|
||||
ui->stack->setCurrentWidget(ui->TilePage);
|
||||
hasContent = false;
|
||||
content = nullptr;
|
||||
}
|
49
Software/PC_Application/CustomWidgets/tilewidget.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef TILEWIDGET_H
|
||||
#define TILEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "Traces/traceplot.h"
|
||||
#include <QSplitter>
|
||||
#include "Traces/tracemodel.h"
|
||||
|
||||
namespace Ui {
|
||||
class TileWidget;
|
||||
}
|
||||
|
||||
class TileWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TileWidget(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TileWidget();
|
||||
|
||||
TileWidget *Child1() { return child1; };
|
||||
TileWidget *Child2() { return child2; };
|
||||
public slots:
|
||||
void splitVertically();
|
||||
void splitHorizontally();
|
||||
void closeTile();
|
||||
void setPlot(TracePlot *plot);
|
||||
|
||||
private slots:
|
||||
void on_bSmithchart_clicked();
|
||||
void on_bBodeplot_clicked();
|
||||
void traceDeleted(TracePlot *t);
|
||||
|
||||
private:
|
||||
TileWidget(TraceModel &model, TileWidget &parent);
|
||||
void split();
|
||||
void setContent(TracePlot *plot);
|
||||
void setChild();
|
||||
Ui::TileWidget *ui;
|
||||
QSplitter *splitter;
|
||||
bool isSplit;
|
||||
TileWidget *parent;
|
||||
TileWidget *child1, *child2;
|
||||
bool hasContent;
|
||||
TracePlot *content;
|
||||
TraceModel &model;
|
||||
};
|
||||
|
||||
#endif // TILEWIDGET_H
|
266
Software/PC_Application/CustomWidgets/tilewidget.ui
Normal file
@ -0,0 +1,266 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TileWidget</class>
|
||||
<widget class="QWidget" name="TileWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>465</width>
|
||||
<height>350</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stack">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="TilePage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Plots</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="bSmithchart">
|
||||
<property name="text">
|
||||
<string>Smithchart</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bBodeplot">
|
||||
<property name="text">
|
||||
<string>Bodeplot</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Split Tile</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="bSplitV">
|
||||
<property name="text">
|
||||
<string>Vertical</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/icons/vertical.svg</normaloff>:/icons/vertical.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bSplitH">
|
||||
<property name="text">
|
||||
<string>Horizontal</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/icons/horizontal.svg</normaloff>:/icons/horizontal.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bClose">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Close Tile</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/icons/close.svg</normaloff>:/icons/close.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="ContentPage"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>bClose</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>TileWidget</receiver>
|
||||
<slot>closeTile()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>199</x>
|
||||
<y>74</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>bSplitH</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>TileWidget</receiver>
|
||||
<slot>splitHorizontally()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>199</x>
|
||||
<y>224</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>bSplitV</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>TileWidget</receiver>
|
||||
<slot>splitVertically()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>splitVertically()</slot>
|
||||
<slot>splitHorizontally()</slot>
|
||||
<slot>closeTile()</slot>
|
||||
</slots>
|
||||
</ui>
|
66
Software/PC_Application/CustomWidgets/toggleswitch.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#include "toggleswitch.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
|
||||
ToggleSwitch::ToggleSwitch(QWidget *parent, bool state) : QAbstractButton(parent),
|
||||
_height(24),
|
||||
_width(128),
|
||||
state(state)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QSize ToggleSwitch::sizeHint() const
|
||||
{
|
||||
return QSize(_width, _height);
|
||||
}
|
||||
|
||||
void ToggleSwitch::toggle()
|
||||
{
|
||||
state = !state;
|
||||
emit toggled(state);
|
||||
}
|
||||
|
||||
void ToggleSwitch::setState(bool state)
|
||||
{
|
||||
if(this->state != state) {
|
||||
this->state = state;
|
||||
emit toggled(state);
|
||||
}
|
||||
}
|
||||
|
||||
void ToggleSwitch::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(Qt::black);
|
||||
p.setOpacity(isEnabled() ? 0.38 : 0.12);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
p.drawRoundedRect(QRect(0, 0, width(), height()), 8.0, 8.0);
|
||||
p.setOpacity(1.0);
|
||||
QRect rect;
|
||||
QString statename;
|
||||
if(state) {
|
||||
p.setBrush(isEnabled() ? Qt::darkGreen : Qt::gray);
|
||||
rect = QRect(width()/2, 0, width()/2, height());
|
||||
statename = "ON";
|
||||
} else {
|
||||
p.setBrush(isEnabled() ? QColor("#AA090E") : Qt::lightGray);
|
||||
rect = QRect(0, 0, width()/2, height());
|
||||
statename = "OFF";
|
||||
}
|
||||
p.drawRoundedRect(rect, 8.0, 8.0);
|
||||
QFont font = p.font();
|
||||
p.setPen(Qt::SolidLine);
|
||||
p.setPen(isEnabled() ? Qt::black : Qt::gray);
|
||||
p.drawText(rect, Qt::AlignCenter, statename);
|
||||
}
|
||||
|
||||
void ToggleSwitch::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
if(e->button() & Qt::LeftButton) {
|
||||
toggle();
|
||||
}
|
||||
QAbstractButton::mouseReleaseEvent(e);
|
||||
}
|
28
Software/PC_Application/CustomWidgets/toggleswitch.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef TOGGLESWITCH_H
|
||||
#define TOGGLESWITCH_H
|
||||
|
||||
#include <QAbstractButton>
|
||||
|
||||
class ToggleSwitch : public QAbstractButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ToggleSwitch(QWidget* parent = nullptr, bool state = false);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
signals:
|
||||
void toggled(bool newstate);
|
||||
public slots:
|
||||
void toggle();
|
||||
void setState(bool state);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
void mouseReleaseEvent(QMouseEvent*) override;
|
||||
|
||||
private:
|
||||
int _height, _width;
|
||||
bool state;
|
||||
};
|
||||
|
||||
#endif // TOGGLESWITCH_H
|
185
Software/PC_Application/CustomWidgets/touchstoneimport.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
#include "touchstoneimport.h"
|
||||
#include "ui_touchstoneimport.h"
|
||||
#include <QFileDialog>
|
||||
#include <QDebug>
|
||||
#include <QtGlobal>
|
||||
|
||||
using namespace std;
|
||||
|
||||
TouchstoneImport::TouchstoneImport(QWidget *parent, int ports) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::TouchstoneImport),
|
||||
touchstone(ports),
|
||||
status(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->browse, &QPushButton::clicked, this, &TouchstoneImport::evaluateFile);
|
||||
ui->port1Group->setId(ui->port1_1, 0);
|
||||
ui->port1Group->setId(ui->port1_2, 1);
|
||||
ui->port1Group->setId(ui->port1_3, 2);
|
||||
ui->port1Group->setId(ui->port1_4, 3);
|
||||
ui->port2Group->setId(ui->port2_1, 0);
|
||||
ui->port2Group->setId(ui->port2_2, 1);
|
||||
ui->port2Group->setId(ui->port2_3, 2);
|
||||
ui->port2Group->setId(ui->port2_4, 3);
|
||||
// prevent selection of same port for port1 and 2
|
||||
connect(ui->port1Group, qOverload<int>(&QButtonGroup::buttonClicked), [=](int id) {
|
||||
preventCollisionWithGroup(ui->port2Group, id);
|
||||
});
|
||||
connect(ui->port2Group, qOverload<int>(&QButtonGroup::buttonClicked), [=](int id) {
|
||||
preventCollisionWithGroup(ui->port1Group, id);
|
||||
});
|
||||
setPorts(ports);
|
||||
}
|
||||
|
||||
TouchstoneImport::~TouchstoneImport()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool TouchstoneImport::getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
Touchstone TouchstoneImport::getTouchstone()
|
||||
{
|
||||
if(required_ports == 1) {
|
||||
auto t1 = touchstone;
|
||||
t1.reduceTo1Port(ui->port1Group->checkedId());
|
||||
return t1;
|
||||
} else if(required_ports == 2) {
|
||||
auto t2 = touchstone;
|
||||
t2.reduceTo2Port(ui->port1Group->checkedId(), ui->port2Group->checkedId());
|
||||
return t2;
|
||||
}
|
||||
|
||||
return touchstone;
|
||||
}
|
||||
|
||||
void TouchstoneImport::setPorts(int ports)
|
||||
{
|
||||
required_ports = ports;
|
||||
ui->port1Widget->setVisible(ports >= 1);
|
||||
ui->port2Widget->setVisible(ports >= 2);
|
||||
}
|
||||
|
||||
QString TouchstoneImport::getFilename()
|
||||
{
|
||||
return ui->file->text();
|
||||
}
|
||||
|
||||
void TouchstoneImport::selectPort(int destination, int source)
|
||||
{
|
||||
switch(destination) {
|
||||
case 0:
|
||||
ui->port1Group->button(source)->setChecked(true);
|
||||
preventCollisionWithGroup(ui->port2Group, source);
|
||||
break;
|
||||
case 1:
|
||||
ui->port2Group->button(source)->setChecked(true);
|
||||
preventCollisionWithGroup(ui->port1Group, source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> TouchstoneImport::getPorts()
|
||||
{
|
||||
vector<int> ret;
|
||||
if(required_ports >= 1) {
|
||||
ret.push_back(ui->port1Group->checkedId());
|
||||
}
|
||||
if(required_ports >= 2) {
|
||||
ret.push_back(ui->port2Group->checkedId());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TouchstoneImport::setFile(QString filename)
|
||||
{
|
||||
ui->file->setText(filename);
|
||||
evaluateFile();
|
||||
}
|
||||
|
||||
void TouchstoneImport::on_browse_clicked()
|
||||
{
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if (filename.length() > 0) {
|
||||
ui->file->setText(filename);
|
||||
evaluateFile();
|
||||
}
|
||||
}
|
||||
|
||||
void TouchstoneImport::evaluateFile()
|
||||
{
|
||||
bool new_status = false;
|
||||
ui->port1_1->setEnabled(false);
|
||||
ui->port1_2->setEnabled(false);
|
||||
ui->port1_3->setEnabled(false);
|
||||
ui->port1_4->setEnabled(false);
|
||||
if (required_ports != 1) {
|
||||
ui->port2_1->setEnabled(false);
|
||||
ui->port2_2->setEnabled(false);
|
||||
ui->port2_3->setEnabled(false);
|
||||
ui->port2_4->setEnabled(false);
|
||||
}
|
||||
ui->points->setText("");
|
||||
ui->lowerFreq->setText("");
|
||||
ui->upperFreq->setText("");
|
||||
ui->status->clear();
|
||||
try {
|
||||
touchstone = Touchstone::fromFile(ui->file->text().toStdString());
|
||||
if (required_ports > 0 && touchstone.ports() < (unsigned int) required_ports) {
|
||||
throw runtime_error("Not enough ports in file");
|
||||
}
|
||||
ui->port1_1->setEnabled(touchstone.ports() >= 1);
|
||||
ui->port1_2->setEnabled(touchstone.ports() >= 2);
|
||||
ui->port1_3->setEnabled(touchstone.ports() >= 3);
|
||||
ui->port1_4->setEnabled(touchstone.ports() >= 4);
|
||||
if (required_ports != 1) {
|
||||
ui->port2_1->setEnabled(touchstone.ports() >= 1);
|
||||
ui->port2_2->setEnabled(touchstone.ports() >= 2);
|
||||
ui->port2_3->setEnabled(touchstone.ports() >= 3);
|
||||
ui->port2_4->setEnabled(touchstone.ports() >= 4);
|
||||
}
|
||||
ui->points->setText(QString::number(touchstone.points()));
|
||||
ui->lowerFreq->setText(QString::number(touchstone.minFreq()));
|
||||
ui->upperFreq->setText(QString::number(touchstone.maxFreq()));
|
||||
if(ui->port1Group->checkedId() == -1 || !ui->port1Group->checkedButton()->isEnabled()) {
|
||||
// no or invalid S parameter selected
|
||||
ui->port1_1->setChecked(true);
|
||||
}
|
||||
if (required_ports != 1) {
|
||||
preventCollisionWithGroup(ui->port2Group, 0);
|
||||
}
|
||||
new_status = true;
|
||||
} catch (const exception &e) {
|
||||
ui->status->setText(e.what());
|
||||
}
|
||||
if (new_status != status) {
|
||||
status = new_status;
|
||||
emit statusChanged(status);
|
||||
}
|
||||
emit filenameChanged(ui->file->text());
|
||||
}
|
||||
|
||||
void TouchstoneImport::preventCollisionWithGroup(QButtonGroup *group, int id)
|
||||
{
|
||||
for(unsigned int i=0;i<touchstone.ports();i++) {
|
||||
group->button(i)->setEnabled(true);
|
||||
}
|
||||
// change selection in second group and mark invalid
|
||||
group->button(id)->setEnabled(false);
|
||||
group->button(id)->setChecked(false);
|
||||
if (group->checkedId() == -1 || group->checkedId() == id) {
|
||||
for(int i=0;i<4;i++) {
|
||||
if(i == id) {
|
||||
continue;
|
||||
}
|
||||
if(group->button(i)->isEnabled()) {
|
||||
group->button(i)->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
Software/PC_Application/CustomWidgets/touchstoneimport.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef TOUCHSTONEIMPORT_H
|
||||
#define TOUCHSTONEIMPORT_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "touchstone.h"
|
||||
#include <QButtonGroup>
|
||||
|
||||
namespace Ui {
|
||||
class TouchstoneImport;
|
||||
}
|
||||
|
||||
class TouchstoneImport : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TouchstoneImport(QWidget *parent = nullptr, int ports = 0);
|
||||
~TouchstoneImport();
|
||||
|
||||
bool getStatus();
|
||||
Touchstone getTouchstone();
|
||||
void setPorts(int ports);
|
||||
QString getFilename();
|
||||
void selectPort(int destination, int source);
|
||||
std::vector<int> getPorts();
|
||||
|
||||
signals:
|
||||
void statusChanged(bool status);
|
||||
void filenameChanged(QString name);
|
||||
|
||||
public slots:
|
||||
void setFile(QString filename);
|
||||
|
||||
private slots:
|
||||
void on_browse_clicked();
|
||||
|
||||
private:
|
||||
void evaluateFile();
|
||||
void preventCollisionWithGroup(QButtonGroup *group, int id);
|
||||
Ui::TouchstoneImport *ui;
|
||||
int required_ports;
|
||||
Touchstone touchstone;
|
||||
bool status;
|
||||
};
|
||||
|
||||
#endif // TOUCHSTONEIMPORT_H
|
312
Software/PC_Application/CustomWidgets/touchstoneimport.ui
Normal file
@ -0,0 +1,312 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TouchstoneImport</class>
|
||||
<widget class="QWidget" name="TouchstoneImport">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>228</width>
|
||||
<height>227</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="file">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browse">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="status">
|
||||
<property name="palette">
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>239</red>
|
||||
<green>41</green>
|
||||
<blue>41</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>239</red>
|
||||
<green>41</green>
|
||||
<blue>41</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>190</red>
|
||||
<green>190</green>
|
||||
<blue>190</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="port1Widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Port 1:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="port1_1">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">port1Group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="port1_2">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">port1Group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="port1_3">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>3</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">port1Group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="port1_4">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">port1Group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="port2Widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="port2Selector">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Port 2:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="port2_1">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">port2Group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="port2_2">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">port2Group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="port2_3">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>3</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">port2Group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="port2_4">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">port2Group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Points:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="points">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Lower Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lowerFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Upper Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="upperFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="port2Group"/>
|
||||
<buttongroup name="port1Group"/>
|
||||
</buttongroups>
|
||||
</ui>
|
408
Software/PC_Application/Device/device.cpp
Normal file
@ -0,0 +1,408 @@
|
||||
#include "device.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <QMessageBox>
|
||||
#include <mutex>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Device::Device(QString serial)
|
||||
{
|
||||
qDebug() << "Starting device connection...";
|
||||
|
||||
m_handle = nullptr;
|
||||
libusb_init(&m_context);
|
||||
|
||||
SearchDevices([=](libusb_device_handle *handle, QString found_serial) -> bool {
|
||||
if(serial.isEmpty() || serial == found_serial) {
|
||||
// accept connection to this device
|
||||
m_serial = found_serial;
|
||||
m_handle = handle;
|
||||
// abort device search
|
||||
return false;
|
||||
} else {
|
||||
// not the requested device, continue search
|
||||
return true;
|
||||
}
|
||||
}, m_context);
|
||||
|
||||
if(!m_handle) {
|
||||
QString message = "No device found";
|
||||
auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message);
|
||||
msg->exec();
|
||||
libusb_exit(m_context);
|
||||
throw std::runtime_error(message.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Found the correct device, now connect
|
||||
/* claim the interfaces */
|
||||
for (int if_num = 0; if_num < 1; if_num++) {
|
||||
int ret = libusb_claim_interface(m_handle, if_num);
|
||||
if (ret < 0) {
|
||||
libusb_close(m_handle);
|
||||
/* Failed to open */
|
||||
QString message = "Failed to claim interface: \"";
|
||||
message.append(libusb_strerror((libusb_error) ret));
|
||||
message.append("\" Maybe you are already connected to this device?");
|
||||
qWarning() << message;
|
||||
auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message);
|
||||
msg->exec();
|
||||
libusb_exit(m_context);
|
||||
throw std::runtime_error(message.toStdString());
|
||||
}
|
||||
}
|
||||
qInfo() << "USB connection established" << flush;
|
||||
m_connected = true;
|
||||
m_receiveThread = new std::thread(&Device::USBHandleThread, this);
|
||||
dataBuffer = new USBInBuffer(m_handle, EP_Data_In_Addr, 2048);
|
||||
logBuffer = new USBInBuffer(m_handle, EP_Log_In_Addr, 2048);
|
||||
connect(dataBuffer, &USBInBuffer::DataReceived, this, &Device::ReceivedData, Qt::DirectConnection);
|
||||
connect(dataBuffer, &USBInBuffer::TransferError, this, &Device::ConnectionLost);
|
||||
connect(logBuffer, &USBInBuffer::DataReceived, this, &Device::ReceivedLog, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
if(m_connected) {
|
||||
delete dataBuffer;
|
||||
delete logBuffer;
|
||||
m_connected = false;
|
||||
for (int if_num = 0; if_num < 1; if_num++) {
|
||||
int ret = libusb_release_interface(m_handle, if_num);
|
||||
if (ret < 0) {
|
||||
qCritical() << "Error releasing interface" << libusb_error_name(ret);
|
||||
}
|
||||
}
|
||||
libusb_close(m_handle);
|
||||
m_receiveThread->join();
|
||||
libusb_exit(m_context);
|
||||
}
|
||||
}
|
||||
|
||||
bool Device::Configure(Protocol::SweepSettings settings)
|
||||
{
|
||||
if(m_connected) {
|
||||
unsigned char buffer[128];
|
||||
Protocol::PacketInfo p;
|
||||
p.type = Protocol::PacketType::SweepSettings;
|
||||
p.settings = settings;
|
||||
unsigned int length = Protocol::EncodePacket(p, buffer, sizeof(buffer));
|
||||
if(!length) {
|
||||
qCritical() << "Failed to encode packet";
|
||||
return false;
|
||||
}
|
||||
int actual_length;
|
||||
auto ret = libusb_bulk_transfer(m_handle, EP_Data_Out_Addr, buffer, length, &actual_length, 0);
|
||||
if(ret < 0) {
|
||||
qCritical() << "Error sending data: "
|
||||
<< libusb_strerror((libusb_error) ret);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Device::SetManual(Protocol::ManualControl manual)
|
||||
{
|
||||
if(m_connected) {
|
||||
unsigned char buffer[128];
|
||||
Protocol::PacketInfo p;
|
||||
p.type = Protocol::PacketType::ManualControl;
|
||||
p.manual = manual;
|
||||
unsigned int length = Protocol::EncodePacket(p, buffer, sizeof(buffer));
|
||||
if(!length) {
|
||||
qCritical() << "Failed to encode packet";
|
||||
return false;
|
||||
}
|
||||
int actual_length;
|
||||
auto ret = libusb_bulk_transfer(m_handle, EP_Data_Out_Addr, buffer, length, &actual_length, 0);
|
||||
if(ret < 0) {
|
||||
qCritical() << "Error sending data: "
|
||||
<< libusb_strerror((libusb_error) ret);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Device::SendFirmwareChunk(Protocol::FirmwarePacket &fw)
|
||||
{
|
||||
if(m_connected) {
|
||||
unsigned char buffer[Protocol::FirmwareChunkSize + 4 + 8];
|
||||
Protocol::PacketInfo p;
|
||||
p.type = Protocol::PacketType::FirmwarePacket;
|
||||
p.firmware = fw;
|
||||
unsigned int length = Protocol::EncodePacket(p, buffer, sizeof(buffer));
|
||||
if(!length) {
|
||||
qCritical() << "Failed to encode packet";
|
||||
return false;
|
||||
}
|
||||
int actual_length;
|
||||
auto ret = libusb_bulk_transfer(m_handle, EP_Data_Out_Addr, buffer, length, &actual_length, 0);
|
||||
if(ret < 0) {
|
||||
qCritical() << "Error sending data: "
|
||||
<< libusb_strerror((libusb_error) ret);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<QString> Device::GetDevices()
|
||||
{
|
||||
std::vector<QString> serials;
|
||||
|
||||
libusb_context *ctx;
|
||||
libusb_init(&ctx);
|
||||
|
||||
SearchDevices([&serials](libusb_device_handle *, QString serial) -> bool {
|
||||
serials.push_back(serial);
|
||||
return true;
|
||||
}, ctx);
|
||||
|
||||
libusb_exit(ctx);
|
||||
|
||||
return serials;
|
||||
}
|
||||
|
||||
void Device::USBHandleThread()
|
||||
{
|
||||
qInfo() << "Receive thread started" << flush;
|
||||
while (m_connected) {
|
||||
libusb_handle_events(m_context);
|
||||
}
|
||||
qDebug() << "Disconnected, receive thread exiting";
|
||||
}
|
||||
|
||||
void Device::SearchDevices(std::function<bool (libusb_device_handle *, QString)> foundCallback, libusb_context *context)
|
||||
{
|
||||
libusb_device **devList;
|
||||
auto ndevices = libusb_get_device_list(context, &devList);
|
||||
|
||||
for (ssize_t idx = 0; idx < ndevices; idx++) {
|
||||
int ret;
|
||||
libusb_device *device = devList[idx];
|
||||
libusb_device_descriptor desc = {};
|
||||
|
||||
ret = libusb_get_device_descriptor(device, &desc);
|
||||
if (ret) {
|
||||
/* some error occured */
|
||||
qCritical() << "Failed to get device descriptor: "
|
||||
<< libusb_strerror((libusb_error) ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desc.idVendor != VID || desc.idProduct != PID) {
|
||||
/* Not the correct IDs */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to open the device */
|
||||
libusb_device_handle *handle = nullptr;
|
||||
ret = libusb_open(device, &handle);
|
||||
if (ret) {
|
||||
/* Failed to open */
|
||||
QString message = "Found potential device but failed to open usb connection: \"";
|
||||
message.append(libusb_strerror((libusb_error) ret));
|
||||
message.append("\" On Linux this is most likely caused by a missing udev rule. On Windows it could be a missing driver. Try installing the WinUSB driver using Zadig (https://zadig.akeo.ie/)");
|
||||
qWarning() << message;
|
||||
auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message);
|
||||
msg->exec();
|
||||
continue;
|
||||
}
|
||||
|
||||
char c_product[256];
|
||||
char c_serial[256];
|
||||
libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber,
|
||||
(unsigned char*) c_serial, sizeof(c_serial));
|
||||
ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct,
|
||||
(unsigned char*) c_product, sizeof(c_product));
|
||||
if (ret > 0) {
|
||||
/* managed to read the product string */
|
||||
QString product(c_product);
|
||||
qDebug() << "Opened device: " << product;
|
||||
if (product == "VNA") {
|
||||
// this is a match
|
||||
if(!foundCallback(handle, QString(c_serial))) {
|
||||
// abort search
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to get product descriptor: "
|
||||
<< libusb_strerror((libusb_error) ret);
|
||||
}
|
||||
libusb_close(handle);
|
||||
}
|
||||
libusb_free_device_list(devList, 1);
|
||||
}
|
||||
|
||||
Protocol::DeviceInfo Device::getLastInfo() const
|
||||
{
|
||||
return lastInfo;
|
||||
}
|
||||
|
||||
QString Device::getLastDeviceInfoString()
|
||||
{
|
||||
QString ret;
|
||||
if(!lastInfoValid) {
|
||||
ret.append("No device information available yet");
|
||||
} else {
|
||||
ret.append("HW Rev.");
|
||||
ret.append(lastInfo.HW_Revision);
|
||||
ret.append(" FW "+QString::number(lastInfo.FW_major)+"."+QString::number(lastInfo.FW_minor).rightJustified(2, '0'));
|
||||
ret.append(" Temps: "+QString::number(lastInfo.temperatures.source)+"°C/"+QString::number(lastInfo.temperatures.LO1)+"°C/"+QString::number(lastInfo.temperatures.MCU)+"°C");
|
||||
ret.append(" Reference:");
|
||||
if(lastInfo.extRefInUse) {
|
||||
ret.append("External");
|
||||
} else {
|
||||
ret.append("Internal");
|
||||
if(lastInfo.extRefAvailable) {
|
||||
ret.append(" (External available)");
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Device::ReceivedData()
|
||||
{
|
||||
Protocol::PacketInfo packet;
|
||||
uint16_t handled_len;
|
||||
do {
|
||||
handled_len = Protocol::DecodeBuffer(dataBuffer->getBuffer(), dataBuffer->getReceived(), &packet);
|
||||
dataBuffer->removeBytes(handled_len);
|
||||
if(packet.type == Protocol::PacketType::Datapoint) {
|
||||
emit DatapointReceived(packet.datapoint);
|
||||
} else if(packet.type == Protocol::PacketType::Status) {
|
||||
qDebug() << "Got status";
|
||||
emit ManualStatusReceived(packet.status);
|
||||
} else if(packet.type == Protocol::PacketType::DeviceInfo) {
|
||||
lastInfo = packet.info;
|
||||
lastInfoValid = true;
|
||||
emit DeviceInfoUpdated();
|
||||
} else if(packet.type == Protocol::PacketType::Ack) {
|
||||
emit AckReceived();
|
||||
}
|
||||
} while (handled_len > 0);
|
||||
}
|
||||
|
||||
void Device::ReceivedLog()
|
||||
{
|
||||
uint16_t handled_len;
|
||||
do {
|
||||
handled_len = 0;
|
||||
auto firstLinebreak = (uint8_t*) memchr(logBuffer->getBuffer(), '\n', logBuffer->getReceived());
|
||||
if(firstLinebreak) {
|
||||
handled_len = firstLinebreak - logBuffer->getBuffer();
|
||||
auto line = QString::fromLatin1((const char*) logBuffer->getBuffer(), handled_len - 1);
|
||||
emit LogLineReceived(line);
|
||||
logBuffer->removeBytes(handled_len + 1);
|
||||
}
|
||||
} while(handled_len > 0);
|
||||
}
|
||||
|
||||
QString Device::serial() const
|
||||
{
|
||||
return m_serial;
|
||||
}
|
||||
|
||||
USBInBuffer::USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size) :
|
||||
buffer_size(buffer_size),
|
||||
received_size(0),
|
||||
inCallback(false)
|
||||
{
|
||||
buffer = new unsigned char[buffer_size];
|
||||
transfer = libusb_alloc_transfer(0);
|
||||
libusb_fill_bulk_transfer(transfer, handle, endpoint, buffer, 64, CallbackTrampoline, this, 100);
|
||||
libusb_submit_transfer(transfer);
|
||||
}
|
||||
|
||||
USBInBuffer::~USBInBuffer()
|
||||
{
|
||||
if(transfer) {
|
||||
qDebug() << "Start cancellation";
|
||||
libusb_cancel_transfer(transfer);
|
||||
// wait for cancellation to complete
|
||||
mutex mtx;
|
||||
unique_lock<mutex> lck(mtx);
|
||||
cv.wait(lck);
|
||||
qDebug() << "Cancellation complete";
|
||||
}
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
void USBInBuffer::removeBytes(int handled_bytes)
|
||||
{
|
||||
if(!inCallback) {
|
||||
throw runtime_error("Removing of bytes is only allowed from within receive callback");
|
||||
}
|
||||
if(handled_bytes >= received_size) {
|
||||
received_size = 0;
|
||||
} else {
|
||||
// not removing all bytes, have to move remaining data to the beginning of the buffer
|
||||
memmove(buffer, &buffer[handled_bytes], received_size - handled_bytes);
|
||||
received_size -= handled_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
int USBInBuffer::getReceived() const
|
||||
{
|
||||
return received_size;
|
||||
}
|
||||
|
||||
void USBInBuffer::Callback(libusb_transfer *transfer)
|
||||
{
|
||||
switch(transfer->status) {
|
||||
case LIBUSB_TRANSFER_COMPLETED:
|
||||
received_size += transfer->actual_length;
|
||||
inCallback = true;
|
||||
emit DataReceived();
|
||||
inCallback = false;
|
||||
break;
|
||||
case LIBUSB_TRANSFER_ERROR:
|
||||
case LIBUSB_TRANSFER_NO_DEVICE:
|
||||
case LIBUSB_TRANSFER_OVERFLOW:
|
||||
case LIBUSB_TRANSFER_STALL:
|
||||
qCritical() << "LIBUSB_TRANSFER_ERROR";
|
||||
libusb_free_transfer(transfer);
|
||||
this->transfer = nullptr;
|
||||
emit TransferError();
|
||||
return;
|
||||
break;
|
||||
case LIBUSB_TRANSFER_TIMED_OUT:
|
||||
// nothing to do
|
||||
break;
|
||||
case LIBUSB_TRANSFER_CANCELLED:
|
||||
// destructor called, do not resubmit
|
||||
libusb_free_transfer(transfer);
|
||||
this->transfer = nullptr;
|
||||
cv.notify_all();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
// Resubmit the transfer
|
||||
transfer->buffer = &buffer[received_size];
|
||||
libusb_submit_transfer(transfer);
|
||||
}
|
||||
|
||||
void USBInBuffer::CallbackTrampoline(libusb_transfer *transfer)
|
||||
{
|
||||
auto usb = (USBInBuffer*) transfer->user_data;
|
||||
usb->Callback(transfer);
|
||||
}
|
||||
|
||||
uint8_t *USBInBuffer::getBuffer() const
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
|
93
Software/PC_Application/Device/device.h
Normal file
@ -0,0 +1,93 @@
|
||||
#ifndef DEVICE_H
|
||||
#define DEVICE_H
|
||||
|
||||
#include "../VNA_embedded/Application/Communication/Protocol.hpp"
|
||||
#include <functional>
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include <thread>
|
||||
#include <QObject>
|
||||
#include <condition_variable>
|
||||
|
||||
Q_DECLARE_METATYPE(Protocol::Datapoint);
|
||||
Q_DECLARE_METATYPE(Protocol::ManualStatus);
|
||||
Q_DECLARE_METATYPE(Protocol::DeviceInfo);
|
||||
|
||||
class USBInBuffer : public QObject {
|
||||
Q_OBJECT;
|
||||
public:
|
||||
USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size);
|
||||
~USBInBuffer();
|
||||
|
||||
void removeBytes(int handled_bytes);
|
||||
int getReceived() const;
|
||||
uint8_t *getBuffer() const;
|
||||
|
||||
signals:
|
||||
void DataReceived();
|
||||
void TransferError();
|
||||
|
||||
private:
|
||||
void Callback(libusb_transfer *transfer);
|
||||
static void LIBUSB_CALL CallbackTrampoline(libusb_transfer *transfer);
|
||||
libusb_transfer *transfer;
|
||||
unsigned char *buffer;
|
||||
int buffer_size;
|
||||
int received_size;
|
||||
bool inCallback;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
|
||||
class Device : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// connect to a VNA device. If serial is specified only connecting to this device, otherwise to the first one found
|
||||
Device(QString serial = QString());
|
||||
~Device();
|
||||
bool Configure(Protocol::SweepSettings settings);
|
||||
bool SetManual(Protocol::ManualControl manual);
|
||||
bool SendFirmwareChunk(Protocol::FirmwarePacket &fw);
|
||||
// Returns serial numbers of all connected devices
|
||||
static std::vector<QString> GetDevices();
|
||||
QString serial() const;
|
||||
Protocol::DeviceInfo getLastInfo() const;
|
||||
QString getLastDeviceInfoString();
|
||||
|
||||
signals:
|
||||
void DatapointReceived(Protocol::Datapoint);
|
||||
void ManualStatusReceived(Protocol::ManualStatus);
|
||||
void DeviceInfoUpdated();
|
||||
void ConnectionLost();
|
||||
void AckReceived();
|
||||
void LogLineReceived(QString line);
|
||||
private slots:
|
||||
void ReceivedData();
|
||||
void ReceivedLog();
|
||||
|
||||
private:
|
||||
static constexpr int VID = 0x0483;
|
||||
static constexpr int PID = 0x564e;
|
||||
static constexpr int EP_Data_Out_Addr = 0x01;
|
||||
static constexpr int EP_Data_In_Addr = 0x81;
|
||||
static constexpr int EP_Log_In_Addr = 0x82;
|
||||
|
||||
void USBHandleThread();
|
||||
// foundCallback is called for every device that is found. If it returns true the search continues, otherwise it is aborted.
|
||||
// When the search is aborted the last found device is still opened
|
||||
static void SearchDevices(std::function<bool(libusb_device_handle *handle, QString serial)> foundCallback, libusb_context *context);
|
||||
|
||||
libusb_device_handle *m_handle;
|
||||
libusb_context *m_context;
|
||||
USBInBuffer *dataBuffer;
|
||||
USBInBuffer *logBuffer;
|
||||
|
||||
|
||||
QString m_serial;
|
||||
bool m_connected;
|
||||
std::thread *m_receiveThread;
|
||||
Protocol::DeviceInfo lastInfo;
|
||||
bool lastInfoValid;
|
||||
};
|
||||
|
||||
#endif // DEVICE_H
|
61
Software/PC_Application/Device/devicelog.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "devicelog.h"
|
||||
#include "ui_devicelog.h"
|
||||
#include <QScrollBar>
|
||||
#include <QFileDialog>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
DeviceLog::DeviceLog(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::DeviceLog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->bClear, &QPushButton::clicked, this, &DeviceLog::clear);
|
||||
}
|
||||
|
||||
DeviceLog::~DeviceLog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void DeviceLog::addLine(QString line)
|
||||
{
|
||||
// Set color depending on log level
|
||||
QColor color = Qt::black;
|
||||
if(line.contains(",CRT]")) {
|
||||
color = Qt::red;
|
||||
} else if(line.contains(",ERR]")) {
|
||||
color = QColor("orange");
|
||||
} else if(line.contains(",WRN]")) {
|
||||
color = Qt::darkYellow;
|
||||
} else if(line.contains(",DBG")) {
|
||||
color = Qt::gray;
|
||||
}
|
||||
QTextCharFormat tf;
|
||||
tf = ui->text->currentCharFormat();
|
||||
tf.setForeground(QBrush(color));
|
||||
ui->text->setCurrentCharFormat(tf);
|
||||
ui->text->appendPlainText(line);
|
||||
if(ui->cbAutoscroll->isChecked()) {
|
||||
QScrollBar *sb = ui->text->verticalScrollBar();
|
||||
sb->setValue(sb->maximum());
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceLog::clear()
|
||||
{
|
||||
ui->text->clear();
|
||||
}
|
||||
|
||||
void DeviceLog::on_bToFile_clicked()
|
||||
{
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Select file for device log", "", "", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if(filename.length() > 0) {
|
||||
// create file
|
||||
ofstream file;
|
||||
file.open(filename.toStdString());
|
||||
file << ui->text->toPlainText().toStdString();
|
||||
file.close();
|
||||
}
|
||||
}
|
29
Software/PC_Application/Device/devicelog.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef DEVICELOG_H
|
||||
#define DEVICELOG_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class DeviceLog;
|
||||
}
|
||||
|
||||
class DeviceLog : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeviceLog(QWidget *parent = nullptr);
|
||||
~DeviceLog();
|
||||
|
||||
public slots:
|
||||
void addLine(QString line);
|
||||
void clear();
|
||||
|
||||
private slots:
|
||||
void on_bToFile_clicked();
|
||||
|
||||
private:
|
||||
Ui::DeviceLog *ui;
|
||||
};
|
||||
|
||||
#endif // DEVICELOG_H
|
72
Software/PC_Application/Device/devicelog.ui
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DeviceLog</class>
|
||||
<widget class="QWidget" name="DeviceLog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>810</width>
|
||||
<height>211</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="text">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="bToFile">
|
||||
<property name="text">
|
||||
<string>To File</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bClear">
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-clear"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="cbAutoscroll">
|
||||
<property name="text">
|
||||
<string>Autoscroll</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
127
Software/PC_Application/Device/firmwareupdatedialog.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
#include "firmwareupdatedialog.h"
|
||||
#include "ui_firmwareupdatedialog.h"
|
||||
#include <QFileDialog>
|
||||
|
||||
FirmwareUpdateDialog::FirmwareUpdateDialog(Device &dev, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::FirmwareUpdateDialog),
|
||||
dev(dev),
|
||||
file(),
|
||||
timer(),
|
||||
state(State::Idle)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->bFile->setIcon(this->style()->standardPixmap(QStyle::SP_FileDialogStart));
|
||||
ui->bStart->setIcon(this->style()->standardPixmap(QStyle::SP_MediaPlay));
|
||||
timer.setSingleShot(true);
|
||||
connect(&timer, &QTimer::timeout, [=](){
|
||||
abortWithError("Response timed out");
|
||||
});
|
||||
}
|
||||
|
||||
FirmwareUpdateDialog::~FirmwareUpdateDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void FirmwareUpdateDialog::on_bFile_clicked()
|
||||
{
|
||||
ui->bStart->setEnabled(false);
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open firmware file", "", "Firmware file (*.vnafw)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if (filename.length() > 0) {
|
||||
ui->lFile->setText(filename);
|
||||
if(file) {
|
||||
delete file;
|
||||
}
|
||||
file = new QFile(filename);
|
||||
ui->bStart->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareUpdateDialog::on_bStart_clicked()
|
||||
{
|
||||
ui->status->clear();
|
||||
ui->bStart->setEnabled(false);
|
||||
if(!file->isOpen()) {
|
||||
if(!file->open(QIODevice::ReadOnly)) {
|
||||
abortWithError("Unable to open file");
|
||||
return;
|
||||
}
|
||||
}
|
||||
file->seek(0);
|
||||
addStatus("Evaluating file...");
|
||||
if(file->size() % Protocol::FirmwareChunkSize != 0) {
|
||||
abortWithError("Invalid file size");
|
||||
return;
|
||||
}
|
||||
char header[24];
|
||||
file->read(header, sizeof(header));
|
||||
if(strncmp(header, "VNA!", 4)) {
|
||||
abortWithError("Invalid magic header constant");
|
||||
return;
|
||||
}
|
||||
state = State::ErasingFLASH;
|
||||
addStatus("Erasing device memory...");
|
||||
// TODO issue write command
|
||||
timer.start(10000);
|
||||
}
|
||||
|
||||
void FirmwareUpdateDialog::addStatus(QString line)
|
||||
{
|
||||
ui->status->appendPlainText(line);
|
||||
}
|
||||
|
||||
void FirmwareUpdateDialog::abortWithError(QString error)
|
||||
{
|
||||
QTextCharFormat tf;
|
||||
tf = ui->status->currentCharFormat();
|
||||
tf.setForeground(QBrush(Qt::red));
|
||||
ui->status->setCurrentCharFormat(tf);
|
||||
ui->status->appendPlainText(error);
|
||||
tf.setForeground(QBrush(Qt::black));
|
||||
ui->status->setCurrentCharFormat(tf);
|
||||
ui->bStart->setEnabled(true);
|
||||
state = State::Idle;
|
||||
}
|
||||
|
||||
void FirmwareUpdateDialog::receivedAck()
|
||||
{
|
||||
switch(state) {
|
||||
case State::Idle:
|
||||
// no firmware update in progress, ignore
|
||||
break;
|
||||
case State::ErasingFLASH:
|
||||
// FLASH erased, begin transferring firmware
|
||||
state = State::TransferringData;
|
||||
transferredBytes = 0;
|
||||
addStatus("Transferring firmware...");
|
||||
sendNextFirmwareChunk();
|
||||
timer.start(1000);
|
||||
break;
|
||||
case State::TransferringData:
|
||||
transferredBytes += Protocol::FirmwareChunkSize;
|
||||
ui->progress->setValue(100 * transferredBytes / file->size());
|
||||
if(transferredBytes >= file->size()) {
|
||||
// complete file transferred
|
||||
addStatus("Triggering device update...");
|
||||
state = State::TriggeringUpdate;
|
||||
// TODO trigger update
|
||||
timer.start(5000);
|
||||
}
|
||||
sendNextFirmwareChunk();
|
||||
timer.start(1000);
|
||||
break;
|
||||
case State::TriggeringUpdate:
|
||||
addStatus("Rebooting device...");
|
||||
// TODO listen for detected device
|
||||
state = State::Idle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareUpdateDialog::sendNextFirmwareChunk()
|
||||
{
|
||||
Protocol::FirmwarePacket fw;
|
||||
fw.address = transferredBytes;
|
||||
file->read((char*) &fw.data, Protocol::FirmwareChunkSize);
|
||||
}
|
46
Software/PC_Application/Device/firmwareupdatedialog.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef FIRMWAREUPDATEDIALOG_H
|
||||
#define FIRMWAREUPDATEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "device.h"
|
||||
#include <QFile>
|
||||
#include <QTimer>
|
||||
|
||||
namespace Ui {
|
||||
class FirmwareUpdateDialog;
|
||||
}
|
||||
|
||||
class FirmwareUpdateDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FirmwareUpdateDialog(Device &dev, QWidget *parent = nullptr);
|
||||
~FirmwareUpdateDialog();
|
||||
|
||||
private slots:
|
||||
void on_bFile_clicked();
|
||||
|
||||
void on_bStart_clicked();
|
||||
|
||||
private:
|
||||
void addStatus(QString line);
|
||||
void abortWithError(QString error);
|
||||
void receivedAck();
|
||||
void sendNextFirmwareChunk();
|
||||
Ui::FirmwareUpdateDialog *ui;
|
||||
Device &dev;
|
||||
QFile *file;
|
||||
QTimer timer;
|
||||
|
||||
enum class State {
|
||||
Idle,
|
||||
ErasingFLASH,
|
||||
TransferringData,
|
||||
TriggeringUpdate,
|
||||
};
|
||||
State state;
|
||||
unsigned int transferredBytes;
|
||||
};
|
||||
|
||||
#endif // FIRMWAREUPDATEDIALOG_H
|
105
Software/PC_Application/Device/firmwareupdatedialog.ui
Normal file
@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FirmwareUpdateDialog</class>
|
||||
<widget class="QDialog" name="FirmwareUpdateDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>520</width>
|
||||
<height>327</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Firmware Update</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>File:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lFile">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bFile">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="status">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progress">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bStart">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
254
Software/PC_Application/Device/manualcontroldialog.cpp
Normal file
@ -0,0 +1,254 @@
|
||||
#include "manualcontroldialog.h"
|
||||
#include "ui_manualcontroldialog.h"
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QButtonGroup>
|
||||
#include <complex>
|
||||
|
||||
using namespace std;
|
||||
|
||||
ManualControlDialog::ManualControlDialog(Device &dev, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::ManualControlDialog),
|
||||
dev(dev)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->SourceLowFrequency->setUnit("Hz");
|
||||
ui->SourceLowFrequency->setPrefixes(" kM");
|
||||
ui->SourceLowFrequency->setPrecision(6);
|
||||
ui->SourceLowFrequency->setValueQuiet(1000000);
|
||||
|
||||
ui->SourceHighFrequency->setUnit("Hz");
|
||||
ui->SourceHighFrequency->setPrefixes(" kMG");
|
||||
ui->SourceHighFrequency->setPrecision(6);
|
||||
ui->SourceHighFrequency->setValueQuiet(1000000000);
|
||||
|
||||
ui->IF1->setUnit("Hz");
|
||||
ui->IF1->setPrefixes(" kM");
|
||||
ui->IF1->setPrecision(6);
|
||||
|
||||
ui->LO1Frequency->setUnit("Hz");
|
||||
ui->LO1Frequency->setPrefixes(" kMG");
|
||||
ui->LO1Frequency->setPrecision(6);
|
||||
|
||||
ui->IF2->setUnit("Hz");
|
||||
ui->IF2->setPrefixes(" kM");
|
||||
ui->IF2->setPrecision(6);
|
||||
|
||||
ui->LO2Frequency->setUnit("Hz");
|
||||
ui->LO2Frequency->setPrefixes(" kM");
|
||||
ui->LO2Frequency->setPrecision(6);
|
||||
|
||||
auto UpdateLO1 = [=]() {
|
||||
double sourceFreq;
|
||||
if (ui->SwitchLowband->isChecked()) {
|
||||
sourceFreq = ui->SourceLowFrequency->value();
|
||||
} else {
|
||||
sourceFreq = ui->SourceHighFrequency->value();
|
||||
}
|
||||
if (ui->LO1FreqType->currentIndex() == 0) {
|
||||
// fixed IF mode
|
||||
ui->LO1Frequency->setValueQuiet(sourceFreq + ui->IF1->value());
|
||||
} else {
|
||||
// Manual Frequency mode
|
||||
ui->IF1->setValueQuiet(ui->LO1Frequency->value() - sourceFreq);
|
||||
}
|
||||
};
|
||||
auto UpdateLO2 = [=]() {
|
||||
double IF1 = ui->IF1->value();
|
||||
if (ui->LO2FreqType->currentIndex() == 0) {
|
||||
// fixed IF mode
|
||||
ui->LO2Frequency->setValueQuiet(IF1 + ui->IF2->value());
|
||||
} else {
|
||||
// Manual Frequency mode
|
||||
ui->IF2->setValueQuiet(ui->LO2Frequency->value() - IF1);
|
||||
}
|
||||
};
|
||||
|
||||
connect(ui->IF1, &SIUnitEdit::valueChanged, [=](double) {
|
||||
UpdateLO1();
|
||||
UpdateLO2();
|
||||
});
|
||||
connect(ui->LO1Frequency, &SIUnitEdit::valueChanged, [=](double) {
|
||||
UpdateLO1();
|
||||
UpdateLO2();
|
||||
});
|
||||
connect(ui->IF2, &SIUnitEdit::valueChanged, [=](double) {
|
||||
UpdateLO2();
|
||||
});
|
||||
connect(ui->LO2Frequency, &SIUnitEdit::valueChanged, [=](double) {
|
||||
UpdateLO2();
|
||||
});
|
||||
connect(ui->SourceSwitchGroup, qOverload<int, bool>(&QButtonGroup::buttonToggled), [=](int, bool) {
|
||||
UpdateLO1();
|
||||
UpdateLO2();
|
||||
});
|
||||
connect(ui->SourceLowFrequency, &SIUnitEdit::valueChanged, [=](double) {
|
||||
UpdateLO1();
|
||||
UpdateLO2();
|
||||
});
|
||||
connect(ui->SourceHighFrequency, &SIUnitEdit::valueChanged, [=](double) {
|
||||
UpdateLO1();
|
||||
UpdateLO2();
|
||||
});
|
||||
|
||||
ui->IF1->setValue(60000000);
|
||||
ui->IF2->setValue(250000);
|
||||
|
||||
// LO1/LO2 mode switch connections
|
||||
connect(ui->LO1FreqType, qOverload<int>(&QComboBox::activated), [=](int index) {
|
||||
switch(index) {
|
||||
case 0:
|
||||
ui->LO1Frequency->setEnabled(false);
|
||||
ui->IF1->setEnabled(true);
|
||||
break;
|
||||
case 1:
|
||||
ui->LO1Frequency->setEnabled(true);
|
||||
ui->IF1->setEnabled(false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
connect(ui->LO2FreqType, qOverload<int>(&QComboBox::activated), [=](int index) {
|
||||
switch(index) {
|
||||
case 0:
|
||||
ui->LO2Frequency->setEnabled(false);
|
||||
ui->IF2->setEnabled(true);
|
||||
break;
|
||||
case 1:
|
||||
ui->LO2Frequency->setEnabled(true);
|
||||
ui->IF2->setEnabled(false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Readonly widgets
|
||||
auto MakeReadOnly = [](QWidget* w) {
|
||||
w->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
w->setFocusPolicy(Qt::NoFocus);
|
||||
};
|
||||
MakeReadOnly(ui->SourceLocked);
|
||||
MakeReadOnly(ui->LO1locked);
|
||||
MakeReadOnly(ui->port1min);
|
||||
MakeReadOnly(ui->port1max);
|
||||
MakeReadOnly(ui->port1mag);
|
||||
MakeReadOnly(ui->port1phase);
|
||||
MakeReadOnly(ui->port1referenced);
|
||||
MakeReadOnly(ui->port2min);
|
||||
MakeReadOnly(ui->port2max);
|
||||
MakeReadOnly(ui->port2mag);
|
||||
MakeReadOnly(ui->port2phase);
|
||||
MakeReadOnly(ui->port2referenced);
|
||||
MakeReadOnly(ui->refmin);
|
||||
MakeReadOnly(ui->refmax);
|
||||
MakeReadOnly(ui->refmag);
|
||||
MakeReadOnly(ui->refphase);
|
||||
|
||||
qRegisterMetaType<Protocol::ManualStatus>("Status");
|
||||
connect(&dev, &Device::ManualStatusReceived, this, &ManualControlDialog::NewStatus);
|
||||
|
||||
connect(ui->SourceCE, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->SourceRFEN, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->LO1CE, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->LO1RFEN, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->SourceLowEnable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->AmplifierEnable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->LO2EN, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->Port1Enable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->Port2Enable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
connect(ui->RefEnable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
|
||||
|
||||
connect(ui->SourceHighPower, qOverload<int>(&QComboBox::activated), [=](int) { UpdateDevice(); });
|
||||
connect(ui->SourceLowpass, qOverload<int>(&QComboBox::activated), [=](int) { UpdateDevice(); });
|
||||
connect(ui->SourceLowPower, qOverload<int>(&QComboBox::activated), [=](int) { UpdateDevice(); });
|
||||
|
||||
connect(ui->SourceHighFrequency, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
|
||||
connect(ui->SourceLowFrequency, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
|
||||
connect(ui->LO1Frequency, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
|
||||
connect(ui->IF1, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
|
||||
connect(ui->LO2Frequency, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
|
||||
connect(ui->IF2, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
|
||||
|
||||
connect(ui->PortSwitchGroup, qOverload<int, bool>(&QButtonGroup::buttonToggled), [=](int, bool) { UpdateDevice(); });
|
||||
connect(ui->SourceSwitchGroup, qOverload<int, bool>(&QButtonGroup::buttonToggled), [=](int, bool) { UpdateDevice(); });
|
||||
|
||||
connect(ui->Attenuator, qOverload<double>(&QDoubleSpinBox::valueChanged), [=](double) { UpdateDevice(); });
|
||||
connect(ui->Samples, qOverload<int>(&QSpinBox::valueChanged), [=](double) { UpdateDevice(); });
|
||||
|
||||
UpdateDevice();
|
||||
}
|
||||
|
||||
ManualControlDialog::~ManualControlDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ManualControlDialog::NewStatus(Protocol::ManualStatus status)
|
||||
{
|
||||
// ADC values
|
||||
ui->port1min->setText(QString::number(status.port1min));
|
||||
ui->port1max->setText(QString::number(status.port1max));
|
||||
auto port1 = complex<double>(status.port1real, status.port1imag);
|
||||
ui->port1mag->setText(QString::number(abs(port1)));
|
||||
ui->port1phase->setText(QString::number(arg(port1)*180/M_PI));
|
||||
|
||||
ui->port2min->setText(QString::number(status.port2min));
|
||||
ui->port2max->setText(QString::number(status.port2max));
|
||||
auto port2 = complex<double>(status.port2real, status.port2imag);
|
||||
ui->port2mag->setText(QString::number(abs(port2)));
|
||||
ui->port2phase->setText(QString::number(arg(port2)*180/M_PI));
|
||||
|
||||
ui->refmin->setText(QString::number(status.refmin));
|
||||
ui->refmax->setText(QString::number(status.refmax));
|
||||
auto ref = complex<double>(status.refreal, status.refimag);
|
||||
ui->refmag->setText(QString::number(abs(ref)));
|
||||
ui->refphase->setText(QString::number(arg(ref)*180/M_PI));
|
||||
|
||||
auto port1referenced = port1 / ref;
|
||||
auto port2referenced = port2 / ref;
|
||||
auto port1db = 20*log10(abs(port1referenced));
|
||||
auto port2db = 20*log10(abs(port2referenced));
|
||||
|
||||
ui->port1referenced->setText(QString::number(port1db, 'f', 1) + "db@" + QString::number(arg(port1referenced)*180/M_PI, 'f', 0) + "°");
|
||||
ui->port2referenced->setText(QString::number(port2db, 'f', 1) + "db@" + QString::number(arg(port2referenced)*180/M_PI, 'f', 0) + "°");
|
||||
|
||||
// PLL state
|
||||
ui->SourceLocked->setChecked(status.source_locked);
|
||||
ui->LO1locked->setChecked(status.LO_locked);
|
||||
}
|
||||
|
||||
void ManualControlDialog::UpdateDevice()
|
||||
{
|
||||
Protocol::ManualControl m;
|
||||
// Source highband
|
||||
m.SourceHighCE = ui->SourceCE->isChecked();
|
||||
m.SourceHighRFEN = ui->SourceRFEN->isChecked();
|
||||
m.SourceHighPower = ui->SourceHighPower->currentIndex();
|
||||
m.SourceHighFrequency = ui->SourceHighFrequency->value();
|
||||
m.SourceHighLowpass = ui->SourceLowpass->currentIndex();
|
||||
// Source lowband
|
||||
m.SourceLowEN = ui->SourceLowEnable->isChecked();
|
||||
m.SourceLowPower = ui->SourceLowPower->currentIndex();
|
||||
m.SourceLowFrequency = ui->SourceLowFrequency->value();
|
||||
// Source signal path
|
||||
m.SourceHighband = ui->SwitchHighband->isChecked();
|
||||
m.AmplifierEN = ui->AmplifierEnable->isChecked();
|
||||
m.PortSwitch = ui->Port2Switch->isChecked();
|
||||
m.attenuator = -ui->Attenuator->value() / 0.25;
|
||||
// LO1
|
||||
m.LO1CE = ui->LO1CE->isChecked();
|
||||
m.LO1RFEN = ui->LO1RFEN->isChecked();
|
||||
m.LO1Frequency = ui->LO1Frequency->value();
|
||||
// LO2
|
||||
m.LO2EN = ui->LO2EN->isChecked();
|
||||
m.LO2Frequency = ui->LO2Frequency->value();
|
||||
// Acquisition
|
||||
m.Port1EN = ui->Port1Enable->isChecked();
|
||||
m.Port2EN = ui->Port2Enable->isChecked();
|
||||
m.RefEN = ui->RefEnable->isChecked();
|
||||
m.Samples = ui->Samples->value();
|
||||
|
||||
qDebug() << "Updating manual control state";
|
||||
|
||||
dev.SetManual(m);
|
||||
}
|
28
Software/PC_Application/Device/manualcontroldialog.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef MANUALCONTROLDIALOG_H
|
||||
#define MANUALCONTROLDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "device.h"
|
||||
|
||||
namespace Ui {
|
||||
class ManualControlDialog;
|
||||
}
|
||||
|
||||
class ManualControlDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManualControlDialog(Device &dev, QWidget *parent = nullptr);
|
||||
~ManualControlDialog();
|
||||
|
||||
public slots:
|
||||
void NewStatus(Protocol::ManualStatus status);
|
||||
|
||||
private:
|
||||
void UpdateDevice();
|
||||
Ui::ManualControlDialog *ui;
|
||||
Device &dev;
|
||||
};
|
||||
|
||||
#endif // MANUALCONTROLDIALOG_H
|
751
Software/PC_Application/Device/manualcontroldialog.ui
Normal file
@ -0,0 +1,751 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ManualControlDialog</class>
|
||||
<widget class="QDialog" name="ManualControlDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1023</width>
|
||||
<height>628</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Manual System Control</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_15">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_10">
|
||||
<property name="title">
|
||||
<string>Signal Generation</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_14">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Highband Source</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="SourceCE">
|
||||
<property name="text">
|
||||
<string>Chip Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="SourceRFEN">
|
||||
<property name="text">
|
||||
<string>RF Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="SourceLocked">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Locked</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Power:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="SourceHighPower">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>-4dbm</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>-1dbm</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>+2dbm</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>+5dbm</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="SourceHighFrequency"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Lowpass:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="SourceLowpass">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>947MHz</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1880MHz</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>3500MHz</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Lowband Source</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="SourceLowEnable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Power:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="SourceLowPower">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2mA</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4mA</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>6mA</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>8mA</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="SourceLowFrequency"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Source Switch</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="SwitchLowband">
|
||||
<property name="text">
|
||||
<string>Lowband</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">SourceSwitchGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="SwitchHighband">
|
||||
<property name="text">
|
||||
<string>Highband</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">SourceSwitchGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Attenuator</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="Attenuator">
|
||||
<property name="suffix">
|
||||
<string>db</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-31.750000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.250000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Amplifier</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_11">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="AmplifierEnable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_6">
|
||||
<property name="title">
|
||||
<string>Port Switch</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="Port1Switch">
|
||||
<property name="text">
|
||||
<string>Port 1</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">PortSwitchGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="Port2Switch">
|
||||
<property name="text">
|
||||
<string>Port 2</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">PortSwitchGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_11">
|
||||
<property name="title">
|
||||
<string>Signal Analysis</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_13">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_7">
|
||||
<property name="title">
|
||||
<string>LO1</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="LO1CE">
|
||||
<property name="text">
|
||||
<string>Chip Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="LO1RFEN">
|
||||
<property name="text">
|
||||
<string>RF Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="LO1locked">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Locked</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Freq. Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="LO1FreqType">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>IF1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Absolute</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="LO1Frequency">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>IF1:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="IF1"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_8">
|
||||
<property name="title">
|
||||
<string>LO2</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="LO2EN">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Freq. Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="LO2FreqType">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>IF2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Absolute</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="LO2Frequency">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>IF2:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="IF2"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_9">
|
||||
<property name="title">
|
||||
<string>Aquisition</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="Port1Enable">
|
||||
<property name="text">
|
||||
<string>Port 1 Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="Port2Enable">
|
||||
<property name="text">
|
||||
<string>Port 2 Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="RefEnable">
|
||||
<property name="text">
|
||||
<string>Reference Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Samples:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="Samples">
|
||||
<property name="minimum">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>131072</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>131072</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_12">
|
||||
<property name="title">
|
||||
<string>Measurements</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_16">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_16">
|
||||
<property name="title">
|
||||
<string>Port 1</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_19">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_9">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>ADC min:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="port1min"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>ADC max:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="port1max"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_27">
|
||||
<property name="text">
|
||||
<string>Magnitude:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="port1mag"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_28">
|
||||
<property name="text">
|
||||
<string>Phase:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="port1phase"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Referenced:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="port1referenced"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_14">
|
||||
<property name="title">
|
||||
<string>Port 2</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_17">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_7">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>ADC min:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="port2min"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>ADC max:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="port2max"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>Magnitude:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="port2mag"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Phase:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="port2phase"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Referenced:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="port2referenced"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_15">
|
||||
<property name="title">
|
||||
<string>Reference</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_18">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_8">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>ADC min:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="refmin"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>ADC max:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="refmax"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>Magnitude:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="refmag"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Phase:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="refphase"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="PortSwitchGroup"/>
|
||||
<buttongroup name="SourceSwitchGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
84
Software/PC_Application/Menu/menu.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include "menu.h"
|
||||
#include <QKeyEvent>
|
||||
#include "menuaction.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Menu::Menu(QStackedLayout &layout, QString name)
|
||||
: name(name),
|
||||
m_containingLayout(layout)
|
||||
{
|
||||
m_layout = new QVBoxLayout;
|
||||
setLayout(m_layout);
|
||||
m_widgetCount = 0;
|
||||
setFixedSize(180, 800);
|
||||
parent = nullptr;
|
||||
layout.addWidget(this);
|
||||
if(name.length() > 0) {
|
||||
auto back = new MenuAction(name, MenuAction::ArrowType::Left);
|
||||
back->setStyleSheet("background-color:lightblue;");
|
||||
connect(back, &MenuAction::triggered, this, &Menu::leave);
|
||||
addItem(back);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::addItem(MenuItem *i)
|
||||
{
|
||||
if(m_widgetCount >= maxWidgets) {
|
||||
throw runtime_error("Menu already at maximum capacity");
|
||||
}
|
||||
m_layout->addWidget(i, 1);
|
||||
items.push_back(i);
|
||||
m_widgetCount++;
|
||||
}
|
||||
|
||||
void Menu::addMenu(Menu *m)
|
||||
{
|
||||
auto menuLabel = new MenuAction(m->name, MenuAction::ArrowType::Right);
|
||||
submenus.push_back(SubmenuEntry(menuLabel, m, m_widgetCount));
|
||||
connect(menuLabel, &MenuAction::triggered, [=]() {
|
||||
m->m_containingLayout.setCurrentWidget(m);
|
||||
});
|
||||
addItem(menuLabel);
|
||||
m->parent = this;
|
||||
}
|
||||
|
||||
void Menu::finalize()
|
||||
{
|
||||
m_layout->addStretch(maxWidgets - m_widgetCount);
|
||||
}
|
||||
|
||||
void Menu::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
// check if softkey pressed
|
||||
int index = -1;
|
||||
switch(event->key()) {
|
||||
case Qt::Key_F1: index = 0; break;
|
||||
case Qt::Key_F2: index = 1; break;
|
||||
case Qt::Key_F3: index = 2; break;
|
||||
case Qt::Key_F4: index = 3; break;
|
||||
case Qt::Key_F5: index = 4; break;
|
||||
case Qt::Key_F6: index = 5; break;
|
||||
case Qt::Key_F7: index = 6; break;
|
||||
case Qt::Key_F8: index = 7; break;
|
||||
}
|
||||
if(index >= 0) {
|
||||
auto w = m_layout->itemAt(index);
|
||||
w->widget()->setFocus();
|
||||
items[index]->userSelected();
|
||||
event->accept();
|
||||
} else if(event->key() == Qt::Key_Escape) {
|
||||
leave();
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::leave()
|
||||
{
|
||||
if(parent) {
|
||||
// got a parent menu
|
||||
parent->m_containingLayout.setCurrentWidget(parent);
|
||||
}
|
||||
}
|
||||
|
41
Software/PC_Application/Menu/menu.h
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef MENU_H
|
||||
#define MENU_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QStackedLayout>
|
||||
#include "menuitem.h"
|
||||
#include "menuaction.h"
|
||||
|
||||
class Menu : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Menu(QStackedLayout &layout, QString name = QString());
|
||||
void addItem(MenuItem *i);
|
||||
void addMenu(Menu *m);
|
||||
void finalize();
|
||||
signals:
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
private:
|
||||
void leave();
|
||||
class SubmenuEntry {
|
||||
public:
|
||||
SubmenuEntry(MenuAction *label, Menu *menu, int index) :
|
||||
label(label), menu(menu), index(index){};
|
||||
MenuAction *label;
|
||||
Menu *menu;
|
||||
int index;
|
||||
};
|
||||
static constexpr int maxWidgets = 12;
|
||||
QVBoxLayout *m_layout;
|
||||
Menu *parent;
|
||||
const QString name;
|
||||
QStackedLayout &m_containingLayout;
|
||||
std::vector<SubmenuEntry> submenus;
|
||||
std::vector<MenuItem*> items;
|
||||
int m_widgetCount;
|
||||
};
|
||||
|
||||
#endif // MENU_H
|
65
Software/PC_Application/Menu/menuaction.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include "menuaction.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QStyle>
|
||||
#include <QSizePolicy>
|
||||
|
||||
MenuAction::MenuAction(const QString &l, MenuAction::ArrowType arrow)
|
||||
{
|
||||
subline = nullptr;
|
||||
auto label = new QLabel(l, this);
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
auto labelLayout = new QHBoxLayout();
|
||||
if(arrow == ArrowType::Left) {
|
||||
auto lIcon = new QLabel(this);
|
||||
lIcon->setPixmap(style()->standardIcon(QStyle::SP_ArrowLeft).pixmap(16));
|
||||
lIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
labelLayout->addWidget(lIcon);
|
||||
}
|
||||
labelLayout->addWidget(label);
|
||||
if(arrow == ArrowType::Right) {
|
||||
auto lIcon = new QLabel(this);
|
||||
lIcon->setPixmap(style()->standardIcon(QStyle::SP_ArrowRight).pixmap(16));
|
||||
lIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
labelLayout->addWidget(lIcon);
|
||||
}
|
||||
layout.addLayout(labelLayout);
|
||||
setLayout(&layout);
|
||||
}
|
||||
|
||||
void MenuAction::AddSubline(const QString &l)
|
||||
{
|
||||
if(!subline) {
|
||||
subline = new QLabel(this);
|
||||
subline->setAlignment(Qt::AlignCenter);
|
||||
layout.addWidget(subline);
|
||||
}
|
||||
QFont f( "Arial", 8);
|
||||
subline->setFont( f);
|
||||
subline->setText(l);
|
||||
}
|
||||
|
||||
void MenuAction::RemoveSubline()
|
||||
{
|
||||
if(subline) {
|
||||
layout.removeWidget(subline);
|
||||
delete subline;
|
||||
subline = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuAction::userSelected()
|
||||
{
|
||||
emit triggered();
|
||||
}
|
||||
|
||||
void MenuAction::mouseReleaseEvent(QMouseEvent *me)
|
||||
{
|
||||
setFrameStyle(QFrame::Raised | QFrame::Panel);
|
||||
MenuItem::mouseReleaseEvent(me);
|
||||
}
|
||||
|
||||
void MenuAction::mousePressEvent(QMouseEvent *)
|
||||
{
|
||||
setFrameStyle(QFrame::Sunken | QFrame::Panel);
|
||||
}
|
33
Software/PC_Application/Menu/menuaction.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef MENULABEL_H
|
||||
#define MENULABEL_H
|
||||
|
||||
#include "menuitem.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
class MenuAction : public MenuItem
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class ArrowType {
|
||||
None,
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
|
||||
MenuAction(const QString &l, ArrowType arrow = ArrowType::None);
|
||||
void AddSubline(const QString &l);
|
||||
void RemoveSubline();
|
||||
signals:
|
||||
void triggered();
|
||||
public slots:
|
||||
void userSelected() override;
|
||||
private:
|
||||
QVBoxLayout layout;
|
||||
QLabel *subline;
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent *me) override;
|
||||
void mousePressEvent(QMouseEvent *me) override;
|
||||
};
|
||||
|
||||
#endif // MENULABEL_H
|
36
Software/PC_Application/Menu/menubool.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "menubool.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
MenuBool::MenuBool(QString name, bool defaultValue)
|
||||
{
|
||||
auto label = new QLabel(name, this);
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
layout.addWidget(label);
|
||||
sw = new ToggleSwitch(this, defaultValue);
|
||||
layout.addWidget(sw);
|
||||
setLayout(&layout);
|
||||
connect(sw, &ToggleSwitch::toggled, this, &MenuBool::valueChanged);
|
||||
sw->setFocusPolicy(Qt::NoFocus);
|
||||
}
|
||||
|
||||
void MenuBool::setValue(bool value)
|
||||
{
|
||||
sw->setState(value);
|
||||
}
|
||||
|
||||
void MenuBool::userSelected()
|
||||
{
|
||||
sw->toggle();
|
||||
}
|
||||
|
||||
void MenuBool::mouseReleaseEvent(QMouseEvent *me)
|
||||
{
|
||||
setFrameStyle(QFrame::Raised | QFrame::Panel);
|
||||
MenuItem::mouseReleaseEvent(me);
|
||||
}
|
||||
|
||||
void MenuBool::mousePressEvent(QMouseEvent *)
|
||||
{
|
||||
setFrameStyle(QFrame::Sunken | QFrame::Panel);
|
||||
}
|
28
Software/PC_Application/Menu/menubool.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef MENUBOOL_H
|
||||
#define MENUBOOL_H
|
||||
|
||||
#include <QObject>
|
||||
#include "menuitem.h"
|
||||
#include <QVBoxLayout>
|
||||
#include "CustomWidgets/toggleswitch.h"
|
||||
|
||||
class MenuBool : public MenuItem
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MenuBool(QString name, bool defaultValue = false);
|
||||
|
||||
signals:
|
||||
void valueChanged(bool value);
|
||||
public slots:
|
||||
void setValue(bool value);
|
||||
void userSelected() override;
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent *me) override;
|
||||
void mousePressEvent(QMouseEvent *me) override;
|
||||
private:
|
||||
QVBoxLayout layout;
|
||||
ToggleSwitch *sw;
|
||||
};
|
||||
|
||||
#endif // MENUBOOL_H
|
17
Software/PC_Application/Menu/menuitem.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "menuitem.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
||||
MenuItem::MenuItem() : QFrame()
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
//setStyleSheet("*:focus {background: lightblue}");
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setFrameStyle(QFrame::Raised | QFrame::Panel);
|
||||
}
|
||||
|
||||
void MenuItem::mouseReleaseEvent(QMouseEvent *me)
|
||||
{
|
||||
userSelected();
|
||||
me->accept();
|
||||
}
|
20
Software/PC_Application/Menu/menuitem.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef MENUITEM_H
|
||||
#define MENUITEM_H
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
class MenuItem : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MenuItem();
|
||||
|
||||
public slots:
|
||||
virtual void userSelected(){};
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent *me) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // MENUITEM_H
|
46
Software/PC_Application/Menu/menuvalue.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include "menuvalue.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <math.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include "valueinput.h"
|
||||
#include <QMouseEvent>
|
||||
#include "unit.h"
|
||||
#include <QDoubleValidator>
|
||||
|
||||
using namespace std;
|
||||
|
||||
MenuValue::MenuValue(QString name, double defaultValue, QString unit, QString prefixes, int precision)
|
||||
: name(name)
|
||||
{
|
||||
if(prefixes.indexOf(' ') < 0) {
|
||||
throw runtime_error("Prefix string must contain space");
|
||||
}
|
||||
auto layout = new QVBoxLayout;
|
||||
auto label = new QLabel(name, this);
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
layout->addWidget(label);
|
||||
lvalue = new SIUnitEdit(unit, prefixes, precision);
|
||||
// pass on signal
|
||||
connect(lvalue, &SIUnitEdit::valueChanged, this, &MenuValue::valueChanged);
|
||||
layout->addWidget(lvalue);
|
||||
setValue(defaultValue);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void MenuValue::setValue(double value)
|
||||
{
|
||||
lvalue->setValue(value);
|
||||
}
|
||||
|
||||
void MenuValue::setValueQuiet(double value)
|
||||
{
|
||||
lvalue->setValueQuiet(value);
|
||||
}
|
||||
|
||||
void MenuValue::userSelected()
|
||||
{
|
||||
lvalue->setFocus();
|
||||
//startInputDialog();
|
||||
}
|
26
Software/PC_Application/Menu/menuvalue.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef MENUVALUE_H
|
||||
#define MENUVALUE_H
|
||||
|
||||
#include "menuitem.h"
|
||||
#include <QLabel>
|
||||
#include <CustomWidgets/siunitedit.h>
|
||||
|
||||
class MenuValue : public MenuItem
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MenuValue(QString name, double defaultValue = 0.0, QString unit = QString(), QString prefixes = " ", int precision = 0);
|
||||
|
||||
signals:
|
||||
void valueChanged(double value);
|
||||
public slots:
|
||||
void setValue(double value);
|
||||
// same as setValue, except that no valueChanged signal is emitted
|
||||
void setValueQuiet(double value);
|
||||
void userSelected() override;
|
||||
private:
|
||||
SIUnitEdit *lvalue;
|
||||
const QString name;
|
||||
};
|
||||
|
||||
#endif // MENUVALUE_H
|
60
Software/PC_Application/Tools/eseries.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include "eseries.h"
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
|
||||
static const std::vector<double> E6 = {
|
||||
1.0, 1.5, 2.2, 3.3, 4.7, 6.8
|
||||
};
|
||||
static const std::vector<double> E12 = {
|
||||
1.0, 1.2, 1.5, 1.8, 2.2, 2.7, 3.3, 3.9, 4.7, 5.6, 6.8, 8.2
|
||||
};
|
||||
static const std::vector<double> E24 = {
|
||||
1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1
|
||||
};
|
||||
static const std::vector<double> E48 = {
|
||||
1.00, 1.05, 1.10, 1.15, 1.21, 1.27, 1.33, 1.40, 1.47, 1.54, 1.62, 1.69, 1.78, 1.87, 1.96, 2.05, 2.15, 2.26, 2.37, 2.49, 2.61, 2.74, 2.87, 3.01, 3.16, 3.32, 3.48, 3.65, 3.83, 4.02, 4.22, 4.42, 4.64, 4.87, 5.11, 5.36, 5.62, 5.90, 6.19, 6.49, 6.81, 7.15, 7.50, 7.87, 8.25, 8.66, 9.09, 9.53
|
||||
};
|
||||
static const std::vector<double> E96 = {
|
||||
1.00, 1.02, 1.05, 1.07, 1.10, 1.13, 1.15, 1.18, 1.21, 1.24, 1.27, 1.30, 1.33, 1.37, 1.40, 1.43, 1.47, 1.50, 1.54, 1.58, 1.62, 1.65, 1.69, 1.74, 1.78, 1.82, 1.87, 1.91, 1.96, 2.00, 2.05, 2.10, 2.15, 2.21, 2.26, 2.32, 2.37, 2.43, 2.49, 2.55, 2.61, 2.67, 2.74, 2.80, 2.87, 2.94, 3.01, 3.09, 3.16, 3.24, 3.32, 3.40, 3.48, 3.57, 3.65, 3.74, 3.83, 3.92, 4.02, 4.12, 4.22, 4.32, 4.42, 4.53, 4.64, 4.75, 4.87, 4.99, 5.11, 5.23, 5.36, 5.49, 5.62, 5.76, 5.90, 6.04, 6.19, 6.34, 6.49, 6.65, 6.81, 6.98, 7.15, 7.32, 7.50, 7.68, 7.87, 8.06, 8.25, 8.45, 8.66, 8.87, 9.09, 9.31, 9.53, 9.76
|
||||
};
|
||||
|
||||
double ESeries::ToESeries(double value, ESeries::Series s, ESeries::Type t)
|
||||
{
|
||||
if(s == Series::Ideal) {
|
||||
// nothing to do
|
||||
return value;
|
||||
}
|
||||
// bring value into [1.0, 10.0) interval
|
||||
int shift10 = floor(log10(value));
|
||||
value *= pow(10.0, -shift10);
|
||||
std::vector<double> series;
|
||||
switch(s) {
|
||||
case Series::E96: series = E96; break;
|
||||
case Series::E48: series = E48; break;
|
||||
case Series::E24: series = E24; break;
|
||||
case Series::E12: series = E12; break;
|
||||
case Series::E6: series = E6; break;
|
||||
case Series::Ideal: /* already handled */ break;
|
||||
}
|
||||
unsigned int index = 1;
|
||||
while(index < 96 && series[index] <= value) {
|
||||
index++;
|
||||
}
|
||||
auto lower = series[index - 1];
|
||||
double higher = 10.0;
|
||||
if(index < series.size()) {
|
||||
higher = series[index];
|
||||
}
|
||||
double approximation;
|
||||
switch(t) {
|
||||
case Type::Lower: approximation = lower; break;
|
||||
case Type::Higher: approximation = higher; break;
|
||||
case Type::BestMatch:
|
||||
if(fabs(value - lower) < fabs(value - higher)) {
|
||||
approximation = lower;
|
||||
} else {
|
||||
approximation = higher;
|
||||
}
|
||||
}
|
||||
return approximation * pow(10, shift10);
|
||||
}
|
25
Software/PC_Application/Tools/eseries.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef ESERIES_H
|
||||
#define ESERIES_H
|
||||
|
||||
|
||||
class ESeries
|
||||
{
|
||||
public:
|
||||
enum class Series {
|
||||
Ideal = 0,
|
||||
E6 = 1,
|
||||
E12 = 2,
|
||||
E24 = 3,
|
||||
E48 = 4,
|
||||
E96 = 5,
|
||||
};
|
||||
enum class Type {
|
||||
BestMatch = 0,
|
||||
Lower = 1,
|
||||
Higher = 2,
|
||||
};
|
||||
|
||||
static double ToESeries(double value, Series s, Type t = Type::BestMatch);
|
||||
};
|
||||
|
||||
#endif // ESERIES_H
|
275
Software/PC_Application/Tools/impedancematchdialog.cpp
Normal file
@ -0,0 +1,275 @@
|
||||
#include "impedancematchdialog.h"
|
||||
#include "ui_impedancematchdialog.h"
|
||||
#include "Tools/eseries.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
constexpr double ImpedanceMatchDialog::Z0;
|
||||
|
||||
ImpedanceMatchDialog::ImpedanceMatchDialog(TraceMarkerModel &model, TraceMarker *marker, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::ImpedanceMatchDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// set SI units and prefixes
|
||||
ui->zReal->setUnit("Ohm");
|
||||
ui->zImag->setUnit("Ohm");
|
||||
ui->zFreq->setUnit("Hz");
|
||||
ui->zFreq->setPrefixes(" kMG");
|
||||
|
||||
ui->mImag->setUnit("Ohm");
|
||||
ui->mReal->setUnit("Ohm");
|
||||
ui->mLoss->setUnit("db");
|
||||
|
||||
ui->lValue->setUnit("H");
|
||||
ui->lValue->setPrefixes("pnum ");
|
||||
ui->cValue->setUnit("F");
|
||||
ui->cValue->setPrefixes("pnum ");
|
||||
|
||||
connect(ui->zFreq, &SIUnitEdit::valueChanged, this, &ImpedanceMatchDialog::calculateMatch);
|
||||
connect(ui->zImag, &SIUnitEdit::valueChanged, this, &ImpedanceMatchDialog::calculateMatch);
|
||||
connect(ui->zReal, &SIUnitEdit::valueChanged, this, &ImpedanceMatchDialog::calculateMatch);
|
||||
connect(ui->zGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImpedanceMatchDialog::calculateMatch);
|
||||
connect(ui->cMatchType, qOverload<int>(&QComboBox::currentIndexChanged), this, &ImpedanceMatchDialog::calculateMatch);
|
||||
connect(ui->lGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImpedanceMatchDialog::calculateMatch);
|
||||
connect(ui->cGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImpedanceMatchDialog::calculateMatch);
|
||||
connect(ui->zGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImpedanceMatchDialog::calculateMatch);
|
||||
|
||||
// populate marker options
|
||||
auto markers = model.getMarker();
|
||||
for(auto m : markers) {
|
||||
if(!m->trace()->isReflection()) {
|
||||
// matching only possible for reflections
|
||||
continue;
|
||||
}
|
||||
ui->cSource->addItem("From Marker "+QString::number(m->getNumber()), QVariant::fromValue<TraceMarker*>(m));
|
||||
if(m == marker) {
|
||||
// select the last index, e.i. the just created marker
|
||||
ui->cSource->setCurrentIndex(ui->cSource->count()-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImpedanceMatchDialog::~ImpedanceMatchDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ImpedanceMatchDialog::on_cSource_currentIndexChanged(int index)
|
||||
{
|
||||
ui->rbSeries->setEnabled(index == 0);
|
||||
ui->rbParallel->setEnabled(index == 0);
|
||||
ui->zReal->setEnabled(index == 0);
|
||||
ui->zImag->setEnabled(index == 0);
|
||||
ui->zFreq->setEnabled(index == 0);
|
||||
if(index > 0) {
|
||||
auto m = qvariant_cast<TraceMarker*>(ui->cSource->itemData(index));
|
||||
ui->rbSeries->setChecked(true);
|
||||
auto data = m->getData();
|
||||
auto reflection = Z0 * (1.0 + data) / (1.0 - data);
|
||||
ui->zReal->setValue(reflection.real());
|
||||
ui->zImag->setValue(reflection.imag());
|
||||
ui->zFreq->setValue(m->getFrequency());
|
||||
}
|
||||
}
|
||||
|
||||
void ImpedanceMatchDialog::calculateMatch()
|
||||
{
|
||||
try {
|
||||
double freq = ui->zFreq->value();
|
||||
complex<double> Z;
|
||||
if(ui->rbSeries->isChecked()) {
|
||||
Z.real(ui->zReal->value());
|
||||
Z.imag(ui->zImag->value());
|
||||
} else {
|
||||
auto real = complex<double>(ui->zReal->value(), 0.0);
|
||||
auto imag = complex<double>(0.0, ui->zImag->value());
|
||||
// calculate parallel impedance
|
||||
Z = real * imag / (real + imag);
|
||||
}
|
||||
bool seriesC = ui->cMatchType->currentIndex() == 0 ? true : false;
|
||||
// equations taken from http://www.ittc.ku.edu/~jstiles/723/handouts/section_5_1_Matching_with_Lumped_Elements_package.pdf
|
||||
double B, X;
|
||||
if(Z.real() > Z0) {
|
||||
B = sqrt(Z.real()/Z0)*sqrt(norm(Z)-Z0*Z.real());
|
||||
if (seriesC) {
|
||||
B = -B;
|
||||
}
|
||||
B += Z.imag();
|
||||
B /= norm(Z);
|
||||
X = 1/B + Z.imag()*Z0/Z.real()-Z0/(B*Z.real());
|
||||
} else {
|
||||
B = sqrt((Z0-Z.real())/Z.real())/Z0;
|
||||
X = sqrt(Z.real()*(Z0-Z.real()));
|
||||
if (seriesC) {
|
||||
B = -B;
|
||||
X = -X;
|
||||
}
|
||||
X -= Z.imag();
|
||||
}
|
||||
// convert X and B to inductor and capacitor
|
||||
bool twoCs = false;
|
||||
bool twoLs = false;
|
||||
double L, C, C2, L2;
|
||||
if(X >= 0) {
|
||||
L = X/(2*M_PI*freq);
|
||||
if(B > 0) {
|
||||
C = B/(2*M_PI*freq);
|
||||
} else {
|
||||
L2 = X/(2*M_PI*freq);
|
||||
L = -1/(B*2*M_PI*freq);
|
||||
twoLs = true;
|
||||
}
|
||||
} else {
|
||||
C = -1/(X*2*M_PI*freq);
|
||||
if(B < 0) {
|
||||
L = -1/(B*2*M_PI*freq);
|
||||
} else {
|
||||
C2 = B/(2*M_PI*freq);
|
||||
twoCs = true;
|
||||
}
|
||||
}
|
||||
|
||||
ESeries::Series Lseries;
|
||||
if(ui->lE6->isChecked()) {
|
||||
Lseries = ESeries::Series::E6;
|
||||
} else if(ui->lE12->isChecked()) {
|
||||
Lseries = ESeries::Series::E12;
|
||||
} else if(ui->lE24->isChecked()) {
|
||||
Lseries = ESeries::Series::E24;
|
||||
} else if(ui->lE48->isChecked()) {
|
||||
Lseries = ESeries::Series::E48;
|
||||
} else if(ui->lE96->isChecked()) {
|
||||
Lseries = ESeries::Series::E96;
|
||||
} else {
|
||||
Lseries = ESeries::Series::Ideal;
|
||||
}
|
||||
ESeries::Series Cseries;
|
||||
if(ui->cE6->isChecked()) {
|
||||
Cseries = ESeries::Series::E6;
|
||||
} else if(ui->cE12->isChecked()) {
|
||||
Cseries = ESeries::Series::E12;
|
||||
} else if(ui->cE24->isChecked()) {
|
||||
Cseries = ESeries::Series::E24;
|
||||
} else if(ui->cE48->isChecked()) {
|
||||
Cseries = ESeries::Series::E48;
|
||||
} else if(ui->cE96->isChecked()) {
|
||||
Cseries = ESeries::Series::E96;
|
||||
} else {
|
||||
Cseries = ESeries::Series::Ideal;
|
||||
}
|
||||
|
||||
L = ESeries::ToESeries(L, Lseries);
|
||||
C = ESeries::ToESeries(C, Cseries);
|
||||
L2 = ESeries::ToESeries(L2, Lseries);
|
||||
C2 = ESeries::ToESeries(C2, Cseries);
|
||||
|
||||
if(twoCs) {
|
||||
for(auto b : ui->lGroup->buttons()) {
|
||||
b->setEnabled(false);
|
||||
}
|
||||
for(auto b : ui->cGroup->buttons()) {
|
||||
b->setEnabled(true);
|
||||
}
|
||||
ui->lL->setText("C1:");
|
||||
ui->lC->setText("C2:");
|
||||
ui->lValue->setUnit("F");
|
||||
ui->cValue->setUnit("F");
|
||||
ui->lValue->setValue(C2);
|
||||
ui->cValue->setValue(C);
|
||||
} else if(twoLs) {
|
||||
for(auto b : ui->cGroup->buttons()) {
|
||||
b->setEnabled(false);
|
||||
}
|
||||
for(auto b : ui->lGroup->buttons()) {
|
||||
b->setEnabled(true);
|
||||
}
|
||||
ui->lC->setText("L2:");
|
||||
ui->lL->setText("L1:");
|
||||
ui->cValue->setUnit("H");
|
||||
ui->lValue->setUnit("H");
|
||||
ui->cValue->setValue(L2);
|
||||
ui->lValue->setValue(L);
|
||||
} else {
|
||||
for(auto b : ui->cGroup->buttons()) {
|
||||
b->setEnabled(true);
|
||||
}
|
||||
for(auto b : ui->lGroup->buttons()) {
|
||||
b->setEnabled(true);
|
||||
}
|
||||
ui->lC->setText("C:");
|
||||
ui->lL->setText("L:");
|
||||
ui->lValue->setUnit("H");
|
||||
ui->cValue->setUnit("F");
|
||||
ui->lValue->setValue(L);
|
||||
ui->cValue->setValue(C);
|
||||
}
|
||||
// calculate actual matched impedance
|
||||
complex<double> Zmatched;
|
||||
complex<double> Zp, Zs;
|
||||
if(seriesC) {
|
||||
if(twoLs) {
|
||||
Zs = complex<double>(0, 2*M_PI*freq*L2);
|
||||
Zp = complex<double>(0, 2*M_PI*freq*L);
|
||||
} else if(twoCs) {
|
||||
Zs = complex<double>(0, -1/(2*M_PI*freq*C2));
|
||||
Zp = complex<double>(0, -1/(2*M_PI*freq*C));
|
||||
} else {
|
||||
Zs = complex<double>(0, -1/(2*M_PI*freq*C));
|
||||
Zp = complex<double>(0, 2*M_PI*freq*L);
|
||||
}
|
||||
} else {
|
||||
if(twoCs) {
|
||||
Zs = complex<double>(0, -1/(2*M_PI*freq*C));
|
||||
Zp = complex<double>(0, -1/(2*M_PI*freq*C2));
|
||||
} else if(twoLs){
|
||||
Zs = complex<double>(0, 2*M_PI*freq*L);
|
||||
Zp = complex<double>(0, 2*M_PI*freq*L2);
|
||||
} else {
|
||||
Zs = complex<double>(0, 2*M_PI*freq*L);
|
||||
Zp = complex<double>(0, -1/(2*M_PI*freq*C));
|
||||
}
|
||||
}
|
||||
if(Z.real() > Z0) {
|
||||
Zmatched = Z*Zp/(Z+Zp) + Zs;
|
||||
} else {
|
||||
Zmatched = Zp*(Z+Zs)/(Zp+Z+Zs);
|
||||
}
|
||||
ui->mReal->setValue(Zmatched.real());
|
||||
ui->mImag->setValue(Zmatched.imag());
|
||||
double reflection = abs((Zmatched-Z0)/(Zmatched+Z0));
|
||||
auto loss = 20.0*log10(reflection);
|
||||
ui->mLoss->setValue(loss);
|
||||
|
||||
// set correct image
|
||||
if(Z.real() > Z0) {
|
||||
if(X >= 0 && B >= 0) {
|
||||
ui->Image->setPixmap(QPixmap(":/icons/sLpC_small.png"));
|
||||
} else if(X < 0 && B < 0) {
|
||||
ui->Image->setPixmap(QPixmap(":/icons/sCpL_small.png"));
|
||||
} else if(X >= 0 && B < 0) {
|
||||
ui->Image->setPixmap(QPixmap(":/icons/sCpC_small.png")); // TODO check
|
||||
} else {
|
||||
ui->Image->setPixmap(QPixmap(":/icons/sLpL_small.png")); // TODO check
|
||||
}
|
||||
} else {
|
||||
if(X >= 0 && B >= 0) {
|
||||
ui->Image->setPixmap(QPixmap(":/icons/pCsL_small.png"));
|
||||
} else if(X < 0 && B < 0) {
|
||||
ui->Image->setPixmap(QPixmap(":/icons/pLsC_small.png"));
|
||||
} else if(X >= 0 && B < 0) {
|
||||
ui->Image->setPixmap(QPixmap(":/icons/pLsL_small.png"));
|
||||
} else {
|
||||
ui->Image->setPixmap(QPixmap(":/icons/pCsC_small.png"));
|
||||
}
|
||||
}
|
||||
} catch (exception e){
|
||||
// something went wrong, probably caused by (intermediate) invalid input, such as f=0Hz
|
||||
ui->lValue->setValue(nan(""));
|
||||
ui->cValue->setValue(nan(""));
|
||||
ui->mReal->setValue(nan(""));
|
||||
ui->mImag->setValue(nan(""));
|
||||
ui->mLoss->setValue(nan(""));
|
||||
}
|
||||
}
|
28
Software/PC_Application/Tools/impedancematchdialog.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef IMPEDANCEMATCHDIALOG_H
|
||||
#define IMPEDANCEMATCHDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "Traces/tracemarkermodel.h"
|
||||
|
||||
namespace Ui {
|
||||
class ImpedanceMatchDialog;
|
||||
}
|
||||
|
||||
class ImpedanceMatchDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ImpedanceMatchDialog(TraceMarkerModel &model, TraceMarker *marker = nullptr, QWidget *parent = nullptr);
|
||||
~ImpedanceMatchDialog();
|
||||
|
||||
private slots:
|
||||
void on_cSource_currentIndexChanged(int index);
|
||||
void calculateMatch();
|
||||
|
||||
private:
|
||||
static constexpr double Z0 = 50.0;
|
||||
Ui::ImpedanceMatchDialog *ui;
|
||||
};
|
||||
|
||||
#endif // IMPEDANCEMATCHDIALOG_H
|
457
Software/PC_Application/Tools/impedancematchdialog.ui
Normal file
@ -0,0 +1,457 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ImpedanceMatchDialog</class>
|
||||
<widget class="QDialog" name="ImpedanceMatchDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>664</width>
|
||||
<height>497</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Impedance Matching</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Unmatched Impedance Z</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QComboBox" name="cSource">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Custom</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbSeries">
|
||||
<property name="text">
|
||||
<string>Series</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">zGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbParallel">
|
||||
<property name="text">
|
||||
<string>Parallel</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">zGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Real:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="SIUnitEdit" name="zReal"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Imag:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="zImag"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="frequencyLabel">
|
||||
<property name="text">
|
||||
<string>Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="zFreq"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Matched Impedance</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Real:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="SIUnitEdit" name="mReal">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Imag:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="mImag">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Return loss:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="mLoss">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QComboBox" name="cMatchType">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Series C - Parallel L</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Parallel C - Series L</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Inductor</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="lIdeal">
|
||||
<property name="text">
|
||||
<string>Ideal</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">lGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QRadioButton" name="lE24">
|
||||
<property name="text">
|
||||
<string>E24</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">lGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="lE6">
|
||||
<property name="text">
|
||||
<string>E6</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">lGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QRadioButton" name="lE48">
|
||||
<property name="text">
|
||||
<string>E48</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">lGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="lE12">
|
||||
<property name="text">
|
||||
<string>E12</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">lGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QRadioButton" name="lE96">
|
||||
<property name="text">
|
||||
<string>E96</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">lGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="lL">
|
||||
<property name="text">
|
||||
<string>L:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SIUnitEdit" name="lValue">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Capacitor</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="cE6">
|
||||
<property name="text">
|
||||
<string>E6</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">cGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="cE12">
|
||||
<property name="text">
|
||||
<string>E12</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">cGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="cIdeal">
|
||||
<property name="text">
|
||||
<string>Ideal</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">cGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QRadioButton" name="cE24">
|
||||
<property name="text">
|
||||
<string>E24</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">cGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QRadioButton" name="cE48">
|
||||
<property name="text">
|
||||
<string>E48</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">cGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QRadioButton" name="cE96">
|
||||
<property name="text">
|
||||
<string>E96</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">cGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="lC">
|
||||
<property name="text">
|
||||
<string>C:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SIUnitEdit" name="cValue">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="Image">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ImpedanceMatchDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ImpedanceMatchDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="zGroup"/>
|
||||
<buttongroup name="cGroup"/>
|
||||
<buttongroup name="lGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
118
Software/PC_Application/Traces/bodeplotaxisdialog.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include "bodeplotaxisdialog.h"
|
||||
#include "ui_bodeplotaxisdialog.h"
|
||||
|
||||
BodeplotAxisDialog::BodeplotAxisDialog(TraceBodePlot *plot) :
|
||||
QDialog(nullptr),
|
||||
ui(new Ui::BodeplotAxisDialog),
|
||||
plot(plot)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// Setup GUI connections
|
||||
connect(ui->Y1type, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
ui->Y1log->setEnabled(index != 0);
|
||||
ui->Y1linear->setEnabled(index != 0);
|
||||
ui->Y1auto->setEnabled(index != 0);
|
||||
bool autoRange = ui->Y1auto->isChecked();
|
||||
ui->Y1min->setEnabled(index != 0 && !autoRange);
|
||||
ui->Y1max->setEnabled(index != 0 && !autoRange);
|
||||
ui->Y1divs->setEnabled(index != 0 && !autoRange);
|
||||
auto type = (TraceBodePlot::YAxisType) index;
|
||||
QString unit;
|
||||
switch(type) {
|
||||
case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break;
|
||||
case TraceBodePlot::YAxisType::Phase: unit = "°"; break;
|
||||
case TraceBodePlot::YAxisType::VSWR: unit = ""; break;
|
||||
default: unit = ""; break;
|
||||
}
|
||||
ui->Y1min->setUnit(unit);
|
||||
ui->Y1max->setUnit(unit);
|
||||
ui->Y1divs->setUnit(unit);
|
||||
});
|
||||
connect(ui->Y1auto, &QCheckBox::toggled, [this](bool checked) {
|
||||
ui->Y1min->setEnabled(!checked);
|
||||
ui->Y1max->setEnabled(!checked);
|
||||
ui->Y1divs->setEnabled(!checked);
|
||||
});
|
||||
|
||||
connect(ui->Y2type, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
ui->Y2log->setEnabled(index != 0);
|
||||
ui->Y2linear->setEnabled(index != 0);
|
||||
ui->Y2auto->setEnabled(index != 0);
|
||||
bool autoRange = ui->Y2auto->isChecked();
|
||||
ui->Y2min->setEnabled(index != 0 && !autoRange);
|
||||
ui->Y2max->setEnabled(index != 0 && !autoRange);
|
||||
ui->Y2divs->setEnabled(index != 0 && !autoRange);
|
||||
auto type = (TraceBodePlot::YAxisType) index;
|
||||
QString unit;
|
||||
switch(type) {
|
||||
case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break;
|
||||
case TraceBodePlot::YAxisType::Phase: unit = "°"; break;
|
||||
case TraceBodePlot::YAxisType::VSWR: unit = ""; break;
|
||||
default: unit = ""; break;
|
||||
}
|
||||
ui->Y2min->setUnit(unit);
|
||||
ui->Y2max->setUnit(unit);
|
||||
ui->Y2divs->setUnit(unit);
|
||||
});
|
||||
connect(ui->Y2auto, &QCheckBox::toggled, [this](bool checked) {
|
||||
ui->Y2min->setEnabled(!checked);
|
||||
ui->Y2max->setEnabled(!checked);
|
||||
ui->Y2divs->setEnabled(!checked);
|
||||
});
|
||||
|
||||
connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) {
|
||||
ui->Xmin->setEnabled(!checked);
|
||||
ui->Xmax->setEnabled(!checked);
|
||||
ui->Xdivs->setEnabled(!checked);
|
||||
});
|
||||
|
||||
ui->Xmin->setUnit("Hz");
|
||||
ui->Xmax->setUnit("Hz");
|
||||
ui->Xdivs->setUnit("Hz");
|
||||
ui->Xmin->setPrefixes(" kMG");
|
||||
ui->Xmax->setPrefixes(" kMG");
|
||||
ui->Xdivs->setPrefixes(" kMG");
|
||||
|
||||
// Fill initial values
|
||||
// assume same order in YAxisType enum as in ComboBox items
|
||||
ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type);
|
||||
if(plot->YAxis[0].log) {
|
||||
ui->Y1log->setChecked(true);
|
||||
} else {
|
||||
ui->Y1linear->setChecked(true);
|
||||
}
|
||||
ui->Y1auto->setChecked(plot->YAxis[0].autorange);
|
||||
ui->Y1min->setValueQuiet(plot->YAxis[0].rangeMin);
|
||||
ui->Y1max->setValueQuiet(plot->YAxis[0].rangeMax);
|
||||
ui->Y1divs->setValueQuiet(plot->YAxis[0].rangeDiv);
|
||||
|
||||
ui->Y2type->setCurrentIndex((int) plot->YAxis[1].type);
|
||||
if(plot->YAxis[1].log) {
|
||||
ui->Y2log->setChecked(true);
|
||||
} else {
|
||||
ui->Y2linear->setChecked(true);
|
||||
}
|
||||
ui->Y2auto->setChecked(plot->YAxis[1].autorange);
|
||||
ui->Y2min->setValueQuiet(plot->YAxis[1].rangeMin);
|
||||
ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax);
|
||||
ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv);
|
||||
|
||||
ui->Xauto->setChecked(plot->XAxis.autorange);
|
||||
ui->Xmin->setValueQuiet(plot->XAxis.rangeMin);
|
||||
ui->Xmax->setValueQuiet(plot->XAxis.rangeMax);
|
||||
ui->Xdivs->setValueQuiet(plot->XAxis.rangeDiv);
|
||||
}
|
||||
|
||||
BodeplotAxisDialog::~BodeplotAxisDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void BodeplotAxisDialog::on_buttonBox_accepted()
|
||||
{
|
||||
// set plot values to the ones selected in the dialog
|
||||
plot->setYAxis(0, (TraceBodePlot::YAxisType) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1divs->value());
|
||||
plot->setYAxis(1, (TraceBodePlot::YAxisType) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2divs->value());
|
||||
plot->setXAxis(ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value());
|
||||
}
|
27
Software/PC_Application/Traces/bodeplotaxisdialog.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef BODEPLOTAXISDIALOG_H
|
||||
#define BODEPLOTAXISDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "tracebodeplot.h"
|
||||
|
||||
namespace Ui {
|
||||
class BodeplotAxisDialog;
|
||||
}
|
||||
|
||||
class BodeplotAxisDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BodeplotAxisDialog(TraceBodePlot *plot);
|
||||
~BodeplotAxisDialog();
|
||||
|
||||
private slots:
|
||||
void on_buttonBox_accepted();
|
||||
|
||||
private:
|
||||
Ui::BodeplotAxisDialog *ui;
|
||||
TraceBodePlot *plot;
|
||||
};
|
||||
|
||||
#endif // BODEPLOTAXISDIALOG_H
|
462
Software/PC_Application/Traces/bodeplotaxisdialog.ui
Normal file
@ -0,0 +1,462 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BodeplotAxisDialog</class>
|
||||
<widget class="QDialog" name="BodeplotAxisDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>715</width>
|
||||
<height>282</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Axis Setup</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>15</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Primary Y axis</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="Y1type">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Magnitude</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Phase</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>VSWR</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="Y1linear">
|
||||
<property name="text">
|
||||
<string>Linear</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">Y1group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="Y1log">
|
||||
<property name="text">
|
||||
<string>Log</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">Y1group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Range:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="Y1auto">
|
||||
<property name="text">
|
||||
<string>Auto</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Maximum:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="Y1max"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Minimum:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="Y1min"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Divisions:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="Y1divs"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>15</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Secondary Y axis</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="Y2type">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Magnitude</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Phase</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>VSWR</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="Y2linear">
|
||||
<property name="text">
|
||||
<string>Linear</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">Y2group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="Y2log">
|
||||
<property name="text">
|
||||
<string>Log</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">Y2group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Range:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="Y2auto">
|
||||
<property name="text">
|
||||
<string>Auto</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Maximum:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="Y2max"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Minimum:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="Y2min"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Divisions:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="Y2divs"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>15</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>X axis</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_6">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Range:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="Xauto">
|
||||
<property name="text">
|
||||
<string>Auto</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Maximum:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="Xmax"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Minimum:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="Xmin"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Divisions:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="Xdivs"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>BodeplotAxisDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>BodeplotAxisDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="Y1group"/>
|
||||
<buttongroup name="Y2group"/>
|
||||
</buttongroups>
|
||||
</ui>
|
41
Software/PC_Application/Traces/markerwidget.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include "markerwidget.h"
|
||||
#include "ui_markerwidget.h"
|
||||
|
||||
MarkerWidget::MarkerWidget(TraceMarkerModel &model, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::MarkerWidget),
|
||||
model(model)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tableView->setModel(&model);
|
||||
ui->tableView->setItemDelegateForColumn(1, new TraceChooserDelegate);
|
||||
|
||||
connect(&model.getModel(), &TraceModel::traceAdded, this, &MarkerWidget::updatePersistentEditors);
|
||||
connect(&model.getModel(), &TraceModel::traceRemoved, this, &MarkerWidget::updatePersistentEditors);
|
||||
}
|
||||
|
||||
MarkerWidget::~MarkerWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MarkerWidget::on_bDelete_clicked()
|
||||
{
|
||||
model.removeMarker(ui->tableView->currentIndex().row());
|
||||
}
|
||||
|
||||
void MarkerWidget::on_bAdd_clicked()
|
||||
{
|
||||
auto marker = model.createDefaultMarker();
|
||||
model.addMarker(marker);
|
||||
updatePersistentEditors();
|
||||
}
|
||||
|
||||
void MarkerWidget::updatePersistentEditors(Trace *)
|
||||
{
|
||||
for(int i=0;i<model.rowCount();i++) {
|
||||
auto index = model.index(i, TraceMarkerModel::ColIndexTrace);
|
||||
ui->tableView->closePersistentEditor(index);
|
||||
ui->tableView->openPersistentEditor(index);
|
||||
}
|
||||
}
|
29
Software/PC_Application/Traces/markerwidget.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef MARKERWIDGET_H
|
||||
#define MARKERWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "tracemarkermodel.h"
|
||||
|
||||
namespace Ui {
|
||||
class MarkerWidget;
|
||||
}
|
||||
|
||||
class MarkerWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MarkerWidget(TraceMarkerModel &model, QWidget *parent = nullptr);
|
||||
~MarkerWidget();
|
||||
|
||||
private slots:
|
||||
void on_bDelete_clicked();
|
||||
void on_bAdd_clicked();
|
||||
void updatePersistentEditors(Trace *dummy = nullptr);
|
||||
|
||||
private:
|
||||
Ui::MarkerWidget *ui;
|
||||
TraceMarkerModel &model;
|
||||
};
|
||||
|
||||
#endif // MARKERWIDGET_H
|
89
Software/PC_Application/Traces/markerwidget.ui
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MarkerWidget</class>
|
||||
<widget class="QWidget" name="MarkerWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>729</width>
|
||||
<height>195</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="tableView">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="bAdd">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bDelete">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
238
Software/PC_Application/Traces/trace.cpp
Normal file
@ -0,0 +1,238 @@
|
||||
#include "trace.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Trace::Trace(QString name, QColor color)
|
||||
: _name(name),
|
||||
_color(color),
|
||||
_liveType(LivedataType::Overwrite),
|
||||
reflection(true),
|
||||
visible(true),
|
||||
paused(false),
|
||||
touchstone(false),
|
||||
calibration(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Trace::~Trace()
|
||||
{
|
||||
emit deleted(this);
|
||||
}
|
||||
|
||||
void Trace::clear() {
|
||||
if(paused) {
|
||||
return;
|
||||
}
|
||||
_data.clear();
|
||||
emit cleared(this);
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
void Trace::addData(Trace::Data d) {
|
||||
// add or replace data in vector while keeping it sorted with increasing frequency
|
||||
auto lower = lower_bound(_data.begin(), _data.end(), d, [](const Data &lhs, const Data &rhs) -> bool {
|
||||
return lhs.frequency < rhs.frequency;
|
||||
});
|
||||
if(lower == _data.end()) {
|
||||
// highest frequency yet, add to vector
|
||||
_data.push_back(d);
|
||||
} else if(lower->frequency == d.frequency) {
|
||||
switch(_liveType) {
|
||||
case LivedataType::Overwrite:
|
||||
// replace this data element
|
||||
*lower = d;
|
||||
break;
|
||||
case LivedataType::MaxHold:
|
||||
// replace this data element
|
||||
if(abs(d.S) > abs(lower->S)) {
|
||||
*lower = d;
|
||||
}
|
||||
break;
|
||||
case LivedataType::MinHold:
|
||||
// replace this data element
|
||||
if(abs(d.S) < abs(lower->S)) {
|
||||
*lower = d;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
// insert at this position
|
||||
_data.insert(lower, d);
|
||||
}
|
||||
emit dataAdded(this, d);
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
void Trace::setName(QString name) {
|
||||
_name = name;
|
||||
emit nameChanged();
|
||||
}
|
||||
|
||||
void Trace::fillFromTouchstone(Touchstone &t, unsigned int parameter, QString filename)
|
||||
{
|
||||
if(parameter >= t.ports()*t.ports()) {
|
||||
throw runtime_error("Parameter for touchstone out of range");
|
||||
}
|
||||
clear();
|
||||
setTouchstoneParameter(parameter);
|
||||
setTouchstoneFilename(filename);
|
||||
for(unsigned int i=0;i<t.points();i++) {
|
||||
auto tData = t.point(i);
|
||||
Data d;
|
||||
d.frequency = tData.frequency;
|
||||
d.S = t.point(i).S[parameter];
|
||||
addData(d);
|
||||
}
|
||||
// check if parameter is square (e.i. S11/S22/S33/...)
|
||||
parameter++;
|
||||
bool isSquare = false;
|
||||
for (unsigned int i = 1; i * i <= parameter; i++) {
|
||||
|
||||
// If (i * i = n)
|
||||
if ((parameter % i == 0) && (parameter / i == i)) {
|
||||
isSquare = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isSquare == 1) {
|
||||
reflection = true;
|
||||
} else {
|
||||
reflection = false;
|
||||
}
|
||||
touchstone = true;
|
||||
emit typeChanged(this);
|
||||
}
|
||||
|
||||
void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param)
|
||||
{
|
||||
touchstone = false;
|
||||
_liveType = type;
|
||||
_liveParam = param;
|
||||
if(param == LiveParameter::S11 || param == LiveParameter::S22) {
|
||||
reflection = true;
|
||||
} else {
|
||||
reflection = false;
|
||||
}
|
||||
emit typeChanged(this);
|
||||
}
|
||||
|
||||
void Trace::setColor(QColor color) {
|
||||
if(_color != color) {
|
||||
_color = color;
|
||||
emit colorChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Trace::addMarker(TraceMarker *m)
|
||||
{
|
||||
markers.insert(m);
|
||||
emit markerAdded(m);
|
||||
}
|
||||
|
||||
void Trace::removeMarker(TraceMarker *m)
|
||||
{
|
||||
markers.erase(m);
|
||||
emit markerRemoved(m);
|
||||
}
|
||||
|
||||
void Trace::setReflection(bool value)
|
||||
{
|
||||
reflection = value;
|
||||
}
|
||||
|
||||
void Trace::setCalibration(bool value)
|
||||
{
|
||||
calibration = value;
|
||||
}
|
||||
|
||||
std::set<TraceMarker *> Trace::getMarkers() const
|
||||
{
|
||||
return markers;
|
||||
}
|
||||
|
||||
void Trace::setVisible(bool visible)
|
||||
{
|
||||
if(visible != this->visible) {
|
||||
this->visible = visible;
|
||||
emit visibilityChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool Trace::isVisible()
|
||||
{
|
||||
return visible;
|
||||
}
|
||||
|
||||
void Trace::pause()
|
||||
{
|
||||
paused = true;
|
||||
}
|
||||
|
||||
void Trace::resume()
|
||||
{
|
||||
paused = false;
|
||||
}
|
||||
|
||||
bool Trace::isPaused()
|
||||
{
|
||||
return paused;
|
||||
}
|
||||
|
||||
bool Trace::isTouchstone()
|
||||
{
|
||||
return touchstone;
|
||||
}
|
||||
|
||||
bool Trace::isCalibration()
|
||||
{
|
||||
return calibration;
|
||||
}
|
||||
|
||||
bool Trace::isLive()
|
||||
{
|
||||
return !isCalibration() && !isTouchstone() && !isPaused();
|
||||
}
|
||||
|
||||
bool Trace::isReflection()
|
||||
{
|
||||
return reflection;
|
||||
}
|
||||
|
||||
QString Trace::getTouchstoneFilename() const
|
||||
{
|
||||
return touchstoneFilename;
|
||||
}
|
||||
|
||||
void Trace::setTouchstoneFilename(const QString &value)
|
||||
{
|
||||
touchstoneFilename = value;
|
||||
}
|
||||
|
||||
unsigned int Trace::getTouchstoneParameter() const
|
||||
{
|
||||
return touchstoneParameter;
|
||||
}
|
||||
|
||||
std::complex<double> Trace::getData(double frequency)
|
||||
{
|
||||
if(_data.size() == 0 || frequency < minFreq() || frequency > maxFreq()) {
|
||||
return std::numeric_limits<std::complex<double>>::quiet_NaN();
|
||||
}
|
||||
|
||||
return sample(index(frequency)).S;
|
||||
}
|
||||
|
||||
int Trace::index(double frequency)
|
||||
{
|
||||
auto lower = lower_bound(_data.begin(), _data.end(), frequency, [](const Data &lhs, const double freq) -> bool {
|
||||
return lhs.frequency < freq;
|
||||
});
|
||||
return lower - _data.begin();
|
||||
}
|
||||
|
||||
void Trace::setTouchstoneParameter(int value)
|
||||
{
|
||||
touchstoneParameter = value;
|
||||
}
|
105
Software/PC_Application/Traces/trace.h
Normal file
@ -0,0 +1,105 @@
|
||||
#ifndef TRACE_H
|
||||
#define TRACE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <complex>
|
||||
#include <map>
|
||||
#include <QColor>
|
||||
#include <set>
|
||||
#include "touchstone.h"
|
||||
|
||||
class TraceMarker;
|
||||
|
||||
class Trace : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
class Data {
|
||||
public:
|
||||
double frequency;
|
||||
std::complex<double> S;
|
||||
};
|
||||
|
||||
Trace(QString name = QString(), QColor color = Qt::darkYellow);
|
||||
~Trace();
|
||||
|
||||
enum class LivedataType {
|
||||
Overwrite,
|
||||
MaxHold,
|
||||
MinHold,
|
||||
};
|
||||
enum class LiveParameter {
|
||||
S11,
|
||||
S12,
|
||||
S21,
|
||||
S22,
|
||||
};
|
||||
|
||||
void clear();
|
||||
void addData(Data d);
|
||||
void setName(QString name);
|
||||
void fillFromTouchstone(Touchstone &t, unsigned int parameter, QString filename = QString());
|
||||
void fromLivedata(LivedataType type, LiveParameter param);
|
||||
QString name() { return _name; };
|
||||
QColor color() { return _color; };
|
||||
bool isVisible();
|
||||
void pause();
|
||||
void resume();
|
||||
bool isPaused();
|
||||
bool isTouchstone();
|
||||
bool isCalibration();
|
||||
bool isLive();
|
||||
bool isReflection();
|
||||
LiveParameter liveParameter() { return _liveParam; }
|
||||
LivedataType liveType() { return _liveType; }
|
||||
unsigned int size() { return _data.size(); }
|
||||
double minFreq() { return _data.front().frequency; };
|
||||
double maxFreq() { return _data.back().frequency; };
|
||||
Data sample(unsigned int index) { return _data.at(index); }
|
||||
QString getTouchstoneFilename() const;
|
||||
unsigned int getTouchstoneParameter() const;
|
||||
std::complex<double> getData(double frequency);
|
||||
int index(double frequency);
|
||||
std::set<TraceMarker *> getMarkers() const;
|
||||
void setCalibration(bool value);
|
||||
void setReflection(bool value);
|
||||
|
||||
public slots:
|
||||
void setTouchstoneParameter(int value);
|
||||
void setTouchstoneFilename(const QString &value);
|
||||
void setVisible(bool visible);
|
||||
void setColor(QColor color);
|
||||
void addMarker(TraceMarker *m);
|
||||
void removeMarker(TraceMarker *m);
|
||||
|
||||
private:
|
||||
signals:
|
||||
void cleared(Trace *t);
|
||||
void typeChanged(Trace *t);
|
||||
void dataAdded(Trace *t, Data d);
|
||||
void deleted(Trace *t);
|
||||
void visibilityChanged(Trace *t);
|
||||
void dataChanged();
|
||||
void nameChanged();
|
||||
void colorChanged(Trace *t);
|
||||
void markerAdded(TraceMarker *m);
|
||||
void markerRemoved(TraceMarker *m);
|
||||
|
||||
private:
|
||||
std::vector<Data> _data;
|
||||
QString _name;
|
||||
QColor _color;
|
||||
LivedataType _liveType;
|
||||
LiveParameter _liveParam;
|
||||
bool reflection;
|
||||
bool visible;
|
||||
bool paused;
|
||||
bool touchstone;
|
||||
bool calibration;
|
||||
QString touchstoneFilename;
|
||||
unsigned int touchstoneParameter;
|
||||
std::set<TraceMarker*> markers;
|
||||
};
|
||||
|
||||
#endif // TRACE_H
|
489
Software/PC_Application/Traces/tracebodeplot.cpp
Normal file
@ -0,0 +1,489 @@
|
||||
#include "tracebodeplot.h"
|
||||
#include <QGridLayout>
|
||||
#include <qwt_plot_grid.h>
|
||||
#include "qwtplotpiecewisecurve.h"
|
||||
#include "qwt_series_data.h"
|
||||
#include "trace.h"
|
||||
#include <cmath>
|
||||
#include <QFrame>
|
||||
#include <qwt_plot_canvas.h>
|
||||
#include <qwt_scale_div.h>
|
||||
#include <qwt_plot_layout.h>
|
||||
#include "tracemarker.h"
|
||||
#include <qwt_symbol.h>
|
||||
#include <qwt_plot_picker.h>
|
||||
#include <qwt_picker_machine.h>
|
||||
#include "bodeplotaxisdialog.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static double AxisTransformation(TraceBodePlot::YAxisType type, complex<double> data) {
|
||||
switch(type) {
|
||||
case TraceBodePlot::YAxisType::Magnitude: return 20*log10(abs(data)); break;
|
||||
case TraceBodePlot::YAxisType::Phase: return arg(data) * 180.0 / M_PI; break;
|
||||
case TraceBodePlot::YAxisType::VSWR:
|
||||
if(abs(data) < 1.0) {
|
||||
return (1+abs(data)) / (1-abs(data));
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
template<TraceBodePlot::YAxisType E> class QwtTraceSeries : public QwtSeriesData<QPointF> {
|
||||
public:
|
||||
QwtTraceSeries(Trace &t)
|
||||
: QwtSeriesData<QPointF>(),
|
||||
t(t){};
|
||||
size_t size() const override {
|
||||
return t.size();
|
||||
}
|
||||
QPointF sample(size_t i) const override {
|
||||
Trace::Data d = t.sample(i);
|
||||
QPointF p;
|
||||
p.setX(d.frequency);
|
||||
p.setY(AxisTransformation(E, d.S));
|
||||
return p;
|
||||
}
|
||||
QRectF boundingRect() const override {
|
||||
return qwtBoundingRect(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
Trace &t;
|
||||
};
|
||||
|
||||
// Derived plotpicker, exposing transformation functions
|
||||
class BodeplotPicker : public QwtPlotPicker {
|
||||
public:
|
||||
BodeplotPicker(int xAxis, int yAxis, RubberBand rubberBand, DisplayMode trackerMode, QWidget *w)
|
||||
: QwtPlotPicker(xAxis, yAxis, rubberBand, trackerMode, w) {};
|
||||
QPoint plotToPixel(const QPointF &pos) {
|
||||
return transform(pos);
|
||||
}
|
||||
QPointF pixelToPlot(const QPoint &pos) {
|
||||
return invTransform(pos);
|
||||
}
|
||||
};
|
||||
|
||||
TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent)
|
||||
: TracePlot(parent),
|
||||
selectedMarker(nullptr)
|
||||
{
|
||||
plot = new QwtPlot(this);
|
||||
plot->setCanvasBackground(Background);
|
||||
auto pal = plot->palette();
|
||||
pal.setColor(QPalette::Window, Background);
|
||||
pal.setColor(QPalette::WindowText, Border);
|
||||
pal.setColor(QPalette::Text, Border);
|
||||
auto canvas = new QwtPlotCanvas(plot);
|
||||
canvas->setFrameStyle(QFrame::Plain);
|
||||
plot->setCanvas(canvas);
|
||||
plot->setPalette(pal);
|
||||
plot->setAutoFillBackground(true);
|
||||
|
||||
auto selectPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
|
||||
selectPicker->setStateMachine(new QwtPickerClickPointMachine);
|
||||
|
||||
auto drawPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
|
||||
drawPicker->setStateMachine(new QwtPickerDragPointMachine);
|
||||
drawPicker->setTrackerPen(QPen(Qt::white));
|
||||
|
||||
// Marker selection
|
||||
connect(selectPicker, qOverload<const QPointF&>(&QwtPlotPicker::selected), [=](const QPointF pos) {
|
||||
auto clickPoint = drawPicker->plotToPixel(pos);
|
||||
unsigned int closestDistance = numeric_limits<unsigned int>::max();
|
||||
TraceMarker *closestMarker = nullptr;
|
||||
for(auto m : markers) {
|
||||
auto markerPoint = drawPicker->plotToPixel(m.second->value());
|
||||
auto yDiff = abs(markerPoint.y() - clickPoint.y());
|
||||
auto xDiff = abs(markerPoint.x() - clickPoint.x());
|
||||
unsigned int distance = xDiff * xDiff + yDiff * yDiff;
|
||||
if(distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestMarker = m.first;
|
||||
}
|
||||
}
|
||||
if(closestDistance <= 400) {
|
||||
selectedMarker = closestMarker;
|
||||
selectedCurve = curves[0][selectedMarker->trace()].curve;
|
||||
} else {
|
||||
selectedMarker = nullptr;
|
||||
selectedCurve = nullptr;
|
||||
}
|
||||
});
|
||||
// Marker movement
|
||||
connect(drawPicker, qOverload<const QPointF&>(&QwtPlotPicker::moved), [=](const QPointF pos) {
|
||||
if(!selectedMarker || !selectedCurve) {
|
||||
return;
|
||||
}
|
||||
// int index = selectedCurve->closestPoint(pos.toPoint());
|
||||
// qDebug() << index;
|
||||
// if(index < 0) {
|
||||
// // unable to find closest point
|
||||
// return;
|
||||
// }
|
||||
// selectedMarker->setFrequency(selectedCurve->sample(index).x());
|
||||
selectedMarker->setFrequency(pos.x());
|
||||
});
|
||||
|
||||
QwtPlotGrid *grid = new QwtPlotGrid();
|
||||
grid->setMajorPen(QPen(Divisions, 1.0, Qt::DotLine));
|
||||
grid->attach(plot);
|
||||
auto layout = new QGridLayout;
|
||||
layout->addWidget(plot);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
plot->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
// plot->plotLayout()->setAlignCanvasToScales(true);
|
||||
initializeTraceInfo(model);
|
||||
setAutoFillBackground(true);
|
||||
|
||||
// Setup default axis
|
||||
setYAxis(0, YAxisType::Magnitude, false, false, -120, 20, 10);
|
||||
setYAxis(1, YAxisType::Phase, false, false, -180, 180, 30);
|
||||
// enable autoscaling and set for full span (no information about actual span available yet)
|
||||
setXAxis(0, 6000000000);
|
||||
setXAxis(true, 0, 6000000000, 600000000);
|
||||
}
|
||||
|
||||
TraceBodePlot::~TraceBodePlot()
|
||||
{
|
||||
for(int axis = 0;axis < 2;axis++) {
|
||||
for(auto pd : curves[axis]) {
|
||||
delete pd.second.curve;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceBodePlot::setXAxis(double min, double max)
|
||||
{
|
||||
sweep_fmin = min;
|
||||
sweep_fmax = max;
|
||||
updateXAxis();
|
||||
}
|
||||
|
||||
void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log, bool autorange, double min, double max, double div)
|
||||
{
|
||||
if(YAxis[axis].type != type) {
|
||||
YAxis[axis].type = type;
|
||||
// remove traces that are active but not supported with the new axis type
|
||||
bool erased = false;
|
||||
do {
|
||||
erased = false;
|
||||
for(auto t : tracesAxis[axis]) {
|
||||
if(!supported(t, type)) {
|
||||
enableTraceAxis(t, axis, false);
|
||||
erased = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(erased);
|
||||
|
||||
for(auto t : tracesAxis[axis]) {
|
||||
// supported but needs an adjusted QwtSeriesData
|
||||
auto td = curves[axis][t];
|
||||
td.data = createQwtSeriesData(*t, axis);
|
||||
// call to setSamples deletes old QwtSeriesData
|
||||
td.curve->setSamples(td.data);
|
||||
if(axis == 0) {
|
||||
// update marker data
|
||||
auto marker = t->getMarkers();
|
||||
for(auto m : marker) {
|
||||
markerDataChanged(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
YAxis[axis].log = log;
|
||||
YAxis[axis].autorange = autorange;
|
||||
YAxis[axis].rangeMin = min;
|
||||
YAxis[axis].rangeMax = max;
|
||||
YAxis[axis].rangeDiv = div;
|
||||
// enable/disable y axis
|
||||
auto qwtaxis = axis == 0 ? QwtPlot::yLeft : QwtPlot::yRight;
|
||||
plot->enableAxis(qwtaxis, type != YAxisType::Disabled);
|
||||
if(autorange) {
|
||||
plot->setAxisAutoScale(qwtaxis, true);
|
||||
} else {
|
||||
plot->setAxisScale(qwtaxis, min, max, div);
|
||||
}
|
||||
updateContextMenu();
|
||||
replot();
|
||||
}
|
||||
|
||||
void TraceBodePlot::setXAxis(bool autorange, double min, double max, double div)
|
||||
{
|
||||
XAxis.autorange = autorange;
|
||||
XAxis.rangeMin = min;
|
||||
XAxis.rangeMax = max;
|
||||
XAxis.rangeDiv = div;
|
||||
updateXAxis();
|
||||
}
|
||||
|
||||
void TraceBodePlot::enableTrace(Trace *t, bool enabled)
|
||||
{
|
||||
for(int axis = 0;axis < 2;axis++) {
|
||||
if(supported(t, YAxis[axis].type)) {
|
||||
enableTraceAxis(t, axis, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceBodePlot::updateContextMenu()
|
||||
{
|
||||
contextmenu->clear();
|
||||
// for(int axis = 0;axis < 2;axis++) {
|
||||
// QMenu *axisMenu;
|
||||
// if(axis == 0) {
|
||||
// axisMenu = contextmenu->addMenu("Primary Axis");
|
||||
// } else {
|
||||
// axisMenu = contextmenu->addMenu("Secondary Axis");
|
||||
// }
|
||||
// auto group = new QActionGroup(this);
|
||||
// for(int i=0;i<(int) YAxisType::Last;i++) {
|
||||
// auto action = new QAction(AxisTypeToName((YAxisType) i));
|
||||
// action->setCheckable(true);
|
||||
// group->addAction(action);
|
||||
// if(YAxis[axis].type == (YAxisType) i) {
|
||||
// action->setChecked(true);
|
||||
// }
|
||||
// connect(action, &QAction::triggered, [=](bool active) {
|
||||
// if(active) {
|
||||
// setYAxisType(axis, (YAxisType) i);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// axisMenu->addActions(group->actions());
|
||||
// }
|
||||
auto setup = new QAction("Axis setup...");
|
||||
connect(setup, &QAction::triggered, [this]() {
|
||||
auto setup = new BodeplotAxisDialog(this);
|
||||
setup->show();
|
||||
});
|
||||
contextmenu->addAction(setup);
|
||||
for(int axis = 0;axis < 2;axis++) {
|
||||
if(YAxis[axis].type == YAxisType::Disabled) {
|
||||
continue;
|
||||
}
|
||||
if(axis == 0) {
|
||||
contextmenu->addSection("Primary Traces");
|
||||
} else {
|
||||
contextmenu->addSection("Secondary Traces");
|
||||
}
|
||||
for(auto t : traces) {
|
||||
// Skip traces that are not applicable for the selected axis type
|
||||
if(!supported(t.first, YAxis[axis].type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto action = new QAction(t.first->name());
|
||||
action->setCheckable(true);
|
||||
if(tracesAxis[axis].find(t.first) != tracesAxis[axis].end()) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
connect(action, &QAction::toggled, [=](bool active) {
|
||||
enableTraceAxis(t.first, axis, active);
|
||||
});
|
||||
contextmenu->addAction(action);
|
||||
}
|
||||
}
|
||||
contextmenu->addSeparator();
|
||||
auto close = new QAction("Close");
|
||||
contextmenu->addAction(close);
|
||||
connect(close, &QAction::triggered, [=]() {
|
||||
markedForDeletion = true;
|
||||
});
|
||||
}
|
||||
|
||||
bool TraceBodePlot::supported(Trace *)
|
||||
{
|
||||
// potentially possible to add every kind of trace (depends on axis)
|
||||
return true;
|
||||
}
|
||||
|
||||
void TraceBodePlot::replot()
|
||||
{
|
||||
plot->replot();
|
||||
}
|
||||
|
||||
QString TraceBodePlot::AxisTypeToName(TraceBodePlot::YAxisType type)
|
||||
{
|
||||
switch(type) {
|
||||
case YAxisType::Disabled: return "Disabled"; break;
|
||||
case YAxisType::Magnitude: return "Magnitude"; break;
|
||||
case YAxisType::Phase: return "Phase"; break;
|
||||
case YAxisType::VSWR: return "VSWR"; break;
|
||||
default: return "Unknown"; break;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled)
|
||||
{
|
||||
bool alreadyEnabled = tracesAxis[axis].find(t) != tracesAxis[axis].end();
|
||||
if(alreadyEnabled != enabled) {
|
||||
if(enabled) {
|
||||
tracesAxis[axis].insert(t);
|
||||
CurveData cd;
|
||||
cd.data = createQwtSeriesData(*t, axis);
|
||||
cd.curve = new QwtPlotPiecewiseCurve();
|
||||
cd.curve->attach(plot);
|
||||
cd.curve->setYAxis(axis == 0 ? QwtPlot::yLeft : QwtPlot::yRight);
|
||||
cd.curve->setSamples(cd.data);
|
||||
curves[axis][t] = cd;
|
||||
// connect signals
|
||||
connect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot);
|
||||
connect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged);
|
||||
connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged);
|
||||
connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot);
|
||||
if(axis == 0) {
|
||||
connect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded);
|
||||
connect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved);
|
||||
auto tracemarkers = t->getMarkers();
|
||||
for(auto m : tracemarkers) {
|
||||
markerAdded(m);
|
||||
}
|
||||
}
|
||||
traceColorChanged(t);
|
||||
} else {
|
||||
tracesAxis[axis].erase(t);
|
||||
// clean up and delete
|
||||
if(curves[axis].find(t) != curves[axis].end()) {
|
||||
if(curves[axis][t].curve) {
|
||||
delete curves[axis][t].curve;
|
||||
}
|
||||
curves[axis].erase(t);
|
||||
}
|
||||
int otherAxis = axis == 0 ? 1 : 0;
|
||||
if(curves[otherAxis].find(t) == curves[otherAxis].end()) {
|
||||
// this trace is not used anymore, disconnect from notifications
|
||||
disconnect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot);
|
||||
disconnect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged);
|
||||
disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged);
|
||||
disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot);
|
||||
}
|
||||
if(axis == 0) {
|
||||
disconnect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded);
|
||||
disconnect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved);
|
||||
auto tracemarkers = t->getMarkers();
|
||||
for(auto m : tracemarkers) {
|
||||
markerRemoved(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateContextMenu();
|
||||
replot();
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceBodePlot::supported(Trace *t, TraceBodePlot::YAxisType type)
|
||||
{
|
||||
switch(type) {
|
||||
case YAxisType::Disabled:
|
||||
return false;
|
||||
case YAxisType::VSWR:
|
||||
if(!t->isReflection()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TraceBodePlot::updateXAxis()
|
||||
{
|
||||
if(XAxis.autorange) {
|
||||
QList<double> tickList;
|
||||
for(double tick = sweep_fmin;tick <= sweep_fmax;tick+= (sweep_fmax-sweep_fmin)/10) {
|
||||
tickList.append(tick);
|
||||
}
|
||||
QwtScaleDiv scalediv(sweep_fmin, sweep_fmax, QList<double>(), QList<double>(), tickList);
|
||||
plot->setAxisScaleDiv(QwtPlot::xBottom, scalediv);
|
||||
} else {
|
||||
plot->setAxisScale(QwtPlot::xBottom, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv);
|
||||
}
|
||||
triggerReplot();
|
||||
}
|
||||
|
||||
QwtSeriesData<QPointF> *TraceBodePlot::createQwtSeriesData(Trace &t, int axis)
|
||||
{
|
||||
switch(YAxis[axis].type) {
|
||||
case YAxisType::Magnitude:
|
||||
return new QwtTraceSeries<YAxisType::Magnitude>(t);
|
||||
case YAxisType::Phase:
|
||||
return new QwtTraceSeries<YAxisType::Phase>(t);
|
||||
case YAxisType::VSWR:
|
||||
return new QwtTraceSeries<YAxisType::VSWR>(t);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceBodePlot::traceColorChanged(Trace *t)
|
||||
{
|
||||
for(int axis = 0;axis < 2;axis++) {
|
||||
if(curves[axis].find(t) != curves[axis].end()) {
|
||||
// trace active, change the pen color
|
||||
if(t->isVisible()) {
|
||||
if(axis == 0) {
|
||||
curves[axis][t].curve->setPen(t->color());
|
||||
} else {
|
||||
curves[axis][t].curve->setPen(t->color(), 1.0, Qt::DashLine);
|
||||
}
|
||||
for(auto m : t->getMarkers()) {
|
||||
if(markers.count(m)) {
|
||||
markers[m]->attach(plot);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
curves[axis][t].curve->setPen(t->color(), 0.0, Qt::NoPen);
|
||||
for(auto m : t->getMarkers()) {
|
||||
if(markers.count(m)) {
|
||||
markers[m]->detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceBodePlot::markerAdded(TraceMarker *m)
|
||||
{
|
||||
if(markers.count(m)) {
|
||||
return;
|
||||
}
|
||||
QwtSymbol *sym=new QwtSymbol;
|
||||
sym->setPixmap(m->getSymbol());
|
||||
sym->setPinPoint(QPointF(m->getSymbol().width()/2, m->getSymbol().height()));
|
||||
auto qwtMarker = new QwtPlotMarker;
|
||||
qwtMarker->setSymbol(sym);
|
||||
connect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged);
|
||||
markers[m] = qwtMarker;
|
||||
markerDataChanged(m);
|
||||
qwtMarker->attach(plot);
|
||||
triggerReplot();
|
||||
}
|
||||
|
||||
void TraceBodePlot::markerRemoved(TraceMarker *m)
|
||||
{
|
||||
disconnect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged);
|
||||
if(markers.count(m)) {
|
||||
markers[m]->detach();
|
||||
delete markers[m];
|
||||
markers.erase(m);
|
||||
}
|
||||
triggerReplot();
|
||||
}
|
||||
|
||||
void TraceBodePlot::markerDataChanged(TraceMarker *m)
|
||||
{
|
||||
auto qwtMarker = markers[m];
|
||||
qwtMarker->setXValue(m->getFrequency());
|
||||
qwtMarker->setYValue(AxisTransformation(YAxis[0].type, m->getData()));
|
||||
triggerReplot();
|
||||
}
|
||||
|
76
Software/PC_Application/Traces/tracebodeplot.h
Normal file
@ -0,0 +1,76 @@
|
||||
#ifndef TRACEBODEPLOT_H
|
||||
#define TRACEBODEPLOT_H
|
||||
|
||||
#include "traceplot.h"
|
||||
#include <set>
|
||||
#include <qwt_plot.h>
|
||||
#include <qwt_plot_curve.h>
|
||||
#include <qwt_series_data.h>
|
||||
#include <qwt_plot_marker.h>
|
||||
|
||||
class TraceBodePlot : public TracePlot
|
||||
{
|
||||
friend class BodeplotAxisDialog;
|
||||
Q_OBJECT
|
||||
public:
|
||||
TraceBodePlot(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TraceBodePlot();
|
||||
|
||||
enum class YAxisType {
|
||||
Disabled = 0,
|
||||
Magnitude = 1,
|
||||
Phase = 2,
|
||||
VSWR = 3,
|
||||
Last,
|
||||
};
|
||||
|
||||
virtual void setXAxis(double min, double max) override;
|
||||
void setYAxis(int axis, YAxisType type, bool log, bool autorange, double min, double max, double div);
|
||||
void setXAxis(bool autorange, double min, double max, double div);
|
||||
void enableTrace(Trace *t, bool enabled) override;
|
||||
|
||||
protected:
|
||||
virtual void updateContextMenu();
|
||||
virtual bool supported(Trace *t);
|
||||
void replot() override;
|
||||
|
||||
private slots:
|
||||
void traceColorChanged(Trace *t);
|
||||
void markerAdded(TraceMarker *m) override;
|
||||
void markerRemoved(TraceMarker *m) override;
|
||||
void markerDataChanged(TraceMarker *m);
|
||||
private:
|
||||
QString AxisTypeToName(YAxisType type);
|
||||
void enableTraceAxis(Trace *t, int axis, bool enabled);
|
||||
bool supported(Trace *t, YAxisType type);
|
||||
void updateXAxis();
|
||||
QwtSeriesData<QPointF> *createQwtSeriesData(Trace &t, int axis);
|
||||
|
||||
std::set<Trace*> tracesAxis[2];
|
||||
|
||||
class Axis {
|
||||
public:
|
||||
YAxisType type;
|
||||
bool log;
|
||||
bool autorange;
|
||||
double rangeMin;
|
||||
double rangeMax;
|
||||
double rangeDiv;
|
||||
};
|
||||
Axis YAxis[2];
|
||||
Axis XAxis;
|
||||
double sweep_fmin, sweep_fmax;
|
||||
|
||||
using CurveData = struct {
|
||||
QwtPlotCurve *curve;
|
||||
QwtSeriesData<QPointF> *data;
|
||||
};
|
||||
|
||||
std::map<Trace*, CurveData> curves[2];
|
||||
std::map<TraceMarker*, QwtPlotMarker*> markers;
|
||||
QwtPlot *plot;
|
||||
TraceMarker *selectedMarker;
|
||||
QwtPlotCurve *selectedCurve;
|
||||
};
|
||||
|
||||
#endif // TRACEBODEPLOT_H
|
123
Software/PC_Application/Traces/traceeditdialog.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#include "traceeditdialog.h"
|
||||
#include "ui_traceeditdialog.h"
|
||||
#include <QColorDialog>
|
||||
#include <QFileDialog>
|
||||
|
||||
TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::TraceEditDialog),
|
||||
trace(t)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->name->setText(t.name());
|
||||
setColor(trace.color());
|
||||
|
||||
ui->GSource->setId(ui->bLive, 0);
|
||||
ui->GSource->setId(ui->bFile, 1);
|
||||
|
||||
if(t.isCalibration()) {
|
||||
// prevent editing imported calibration traces
|
||||
ui->bLive->setEnabled(false);
|
||||
ui->bFile->setEnabled(false);
|
||||
ui->CLiveType->setEnabled(false);
|
||||
ui->CLiveParam->setEnabled(false);
|
||||
}
|
||||
|
||||
if(t.isTouchstone()) {
|
||||
ui->bFile->click();
|
||||
ui->touchstoneImport->setFile(t.getTouchstoneFilename());
|
||||
}
|
||||
|
||||
auto updateFileStatus = [this]() {
|
||||
// remove all options from paramater combo box
|
||||
while(ui->CParameter->count() > 0) {
|
||||
ui->CParameter->removeItem(0);
|
||||
}
|
||||
if (ui->bFile->isChecked() && !ui->touchstoneImport->getStatus()) {
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
} else {
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||
auto touchstone = ui->touchstoneImport->getTouchstone();
|
||||
for(unsigned int i=0;i<touchstone.ports();i++) {
|
||||
for(unsigned int j=0;j<touchstone.ports();j++) {
|
||||
QString name = "S"+QString::number(i+1)+QString::number(j+1);
|
||||
ui->CParameter->addItem(name);
|
||||
}
|
||||
}
|
||||
if(trace.getTouchstoneParameter() < touchstone.ports()*touchstone.ports()) {
|
||||
ui->CParameter->setCurrentIndex(trace.getTouchstoneParameter());
|
||||
} else {
|
||||
ui->CParameter->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switch(t.liveType()) {
|
||||
case Trace::LivedataType::Overwrite: ui->CLiveType->setCurrentIndex(0); break;
|
||||
case Trace::LivedataType::MaxHold: ui->CLiveType->setCurrentIndex(1); break;
|
||||
case Trace::LivedataType::MinHold: ui->CLiveType->setCurrentIndex(2); break;
|
||||
}
|
||||
|
||||
switch(t.liveParameter()) {
|
||||
case Trace::LiveParameter::S11: ui->CLiveParam->setCurrentIndex(0); break;
|
||||
case Trace::LiveParameter::S12: ui->CLiveParam->setCurrentIndex(1); break;
|
||||
case Trace::LiveParameter::S21: ui->CLiveParam->setCurrentIndex(2); break;
|
||||
case Trace::LiveParameter::S22: ui->CLiveParam->setCurrentIndex(3); break;
|
||||
}
|
||||
|
||||
connect(ui->GSource, qOverload<int>(&QButtonGroup::buttonClicked), updateFileStatus);
|
||||
connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, updateFileStatus);
|
||||
connect(ui->touchstoneImport, &TouchstoneImport::filenameChanged, updateFileStatus);
|
||||
|
||||
updateFileStatus();
|
||||
}
|
||||
|
||||
TraceEditDialog::~TraceEditDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void TraceEditDialog::on_color_clicked()
|
||||
{
|
||||
auto color = QColorDialog::getColor(trace.color(), this, "Select color", QColorDialog::DontUseNativeDialog);
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
|
||||
void TraceEditDialog::on_buttonBox_accepted()
|
||||
{
|
||||
trace.setName(ui->name->text());
|
||||
if(!trace.isCalibration()) {
|
||||
// only apply changes if it is not a calibration trace
|
||||
if (ui->bFile->isChecked()) {
|
||||
auto t = ui->touchstoneImport->getTouchstone();
|
||||
trace.fillFromTouchstone(t, ui->CParameter->currentIndex(), ui->touchstoneImport->getFilename());
|
||||
} else {
|
||||
Trace::LivedataType type;
|
||||
Trace::LiveParameter param;
|
||||
switch(ui->CLiveType->currentIndex()) {
|
||||
case 0: type = Trace::LivedataType::Overwrite; break;
|
||||
case 1: type = Trace::LivedataType::MaxHold; break;
|
||||
case 2: type = Trace::LivedataType::MinHold; break;
|
||||
}
|
||||
switch(ui->CLiveParam->currentIndex()) {
|
||||
case 0: param = Trace::LiveParameter::S11; break;
|
||||
case 1: param = Trace::LiveParameter::S12; break;
|
||||
case 2: param = Trace::LiveParameter::S21; break;
|
||||
case 3: param = Trace::LiveParameter::S22; break;
|
||||
}
|
||||
trace.fromLivedata(type, param);
|
||||
}
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
|
||||
void TraceEditDialog::setColor(QColor c)
|
||||
{
|
||||
QPalette pal = ui->color->palette();
|
||||
pal.setColor(QPalette::Button, c);
|
||||
ui->color->setAutoFillBackground(true);
|
||||
ui->color->setPalette(pal);
|
||||
ui->color->update();
|
||||
trace.setColor(c);
|
||||
}
|
29
Software/PC_Application/Traces/traceeditdialog.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef TRACEEDITDIALOG_H
|
||||
#define TRACEEDITDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "trace.h"
|
||||
|
||||
namespace Ui {
|
||||
class TraceEditDialog;
|
||||
}
|
||||
|
||||
class TraceEditDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TraceEditDialog(Trace &t, QWidget *parent = nullptr);
|
||||
~TraceEditDialog();
|
||||
|
||||
private slots:
|
||||
void on_color_clicked();
|
||||
void on_buttonBox_accepted();
|
||||
|
||||
private:
|
||||
void setColor(QColor c);
|
||||
Ui::TraceEditDialog *ui;
|
||||
Trace &trace;
|
||||
};
|
||||
|
||||
#endif // TRACEEDITDIALOG_H
|
209
Software/PC_Application/Traces/traceeditdialog.ui
Normal file
@ -0,0 +1,209 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceEditDialog</class>
|
||||
<widget class="QDialog" name="TraceEditDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>282</width>
|
||||
<height>355</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Trace</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Color:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="color">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="bLive">
|
||||
<property name="text">
|
||||
<string>Live Capture</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">GSource</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="bFile">
|
||||
<property name="text">
|
||||
<string>From File</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">GSource</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stack">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="CLiveType">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Overwrite</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Max hold</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Min hold</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Parameter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="CLiveParam">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>S11</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>S12</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>S21</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>S22</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="touchstoneImport" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Parameter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="CParameter"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>TouchstoneImport</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>CustomWidgets/touchstoneimport.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>GSource</sender>
|
||||
<signal>buttonClicked(int)</signal>
|
||||
<receiver>stack</receiver>
|
||||
<slot>setCurrentIndex(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>146</x>
|
||||
<y>216</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="GSource"/>
|
||||
</buttongroups>
|
||||
</ui>
|
186
Software/PC_Application/Traces/traceexportdialog.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include "traceexportdialog.h"
|
||||
#include "ui_traceexportdialog.h"
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include "touchstone.h"
|
||||
#include <QPushButton>
|
||||
|
||||
TraceExportDialog::TraceExportDialog(TraceModel &model, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::TraceExportDialog),
|
||||
model(model),
|
||||
freqsSet(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
|
||||
ui->gbTraces->setLayout(new QGridLayout);
|
||||
on_sbPorts_valueChanged(ui->sbPorts->value());
|
||||
}
|
||||
|
||||
TraceExportDialog::~TraceExportDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool TraceExportDialog::setTrace(int portFrom, int portTo, Trace *t)
|
||||
{
|
||||
if(portFrom < 0 || portFrom >= ui->sbPorts->value() || portTo < 0 || portTo >= ui->sbPorts->value()) {
|
||||
// invalid port selection
|
||||
return false;
|
||||
}
|
||||
auto c = cTraces[portTo][portFrom];
|
||||
if(t) {
|
||||
for(int i=1;i<c->count();i++) {
|
||||
if(t == qvariant_cast<Trace*>(c->itemData(i))) {
|
||||
// select this trace
|
||||
c->setCurrentIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// requested trace is not an option
|
||||
return false;
|
||||
} else {
|
||||
// select 'none' option
|
||||
c->setCurrentIndex(0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceExportDialog::setPortNum(int ports)
|
||||
{
|
||||
if(ports < 1 || ports > 4) {
|
||||
return false;
|
||||
}
|
||||
ui->sbPorts->setValue(ports);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TraceExportDialog::on_buttonBox_accepted()
|
||||
{
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if(filename.length() > 0) {
|
||||
auto ports = ui->sbPorts->value();
|
||||
auto t = Touchstone(ports);
|
||||
// add trace points to touchstone
|
||||
for(unsigned int s=0;s<points;s++) {
|
||||
Touchstone::Datapoint tData;
|
||||
for(int i=0;i<ports;i++) {
|
||||
for(int j=0;j<ports;j++) {
|
||||
if(cTraces[i][j]->currentIndex() == 0) {
|
||||
// missing trace, set to 0
|
||||
tData.S.push_back(0.0);
|
||||
} else {
|
||||
Trace *t = qvariant_cast<Trace*>(cTraces[i][j]->itemData(cTraces[i][j]->currentIndex()));
|
||||
// extract frequency (will overwrite for each trace but all traces have the same frequency points anyway)
|
||||
tData.frequency = t->sample(s).frequency;
|
||||
// add S parameter from trace to touchstone
|
||||
tData.S.push_back(t->sample(s).S);
|
||||
}
|
||||
}
|
||||
}
|
||||
t.AddDatapoint(tData);
|
||||
}
|
||||
Touchstone::Unit unit;
|
||||
switch(ui->cUnit->currentIndex()) {
|
||||
case 0: unit = Touchstone::Unit::Hz; break;
|
||||
case 1: unit = Touchstone::Unit::kHz; break;
|
||||
case 2: unit = Touchstone::Unit::MHz; break;
|
||||
case 3: unit = Touchstone::Unit::GHz; break;
|
||||
}
|
||||
Touchstone::Format format;
|
||||
switch(ui->cFormat->currentIndex()) {
|
||||
case 0: format = Touchstone::Format::DBAngle; break;
|
||||
case 1: format = Touchstone::Format::MagnitudeAngle; break;
|
||||
case 2: format = Touchstone::Format::RealImaginary; break;
|
||||
}
|
||||
|
||||
t.toFile(filename.toStdString(), unit, format);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceExportDialog::on_sbPorts_valueChanged(int ports)
|
||||
{
|
||||
// remove the previous widgets
|
||||
QGridLayout *layout = static_cast<QGridLayout*>(ui->gbTraces->layout());
|
||||
QLayoutItem *child;
|
||||
while ((child = layout->takeAt(0)) != 0) {
|
||||
delete child->widget();
|
||||
delete child;
|
||||
}
|
||||
cTraces.clear();
|
||||
auto availableTraces = model.getTraces();
|
||||
for(int i=0;i<ports;i++) {
|
||||
cTraces.push_back(std::vector<QComboBox*>());
|
||||
for(int j=0;j<ports;j++) {
|
||||
auto l = new QLabel("S"+QString::number(i+1)+QString::number(j+1)+":");
|
||||
auto c = new QComboBox();
|
||||
// create possible trace selections
|
||||
c->addItem("None");
|
||||
for(auto t : availableTraces) {
|
||||
if(i == j && !t->isReflection()) {
|
||||
// can not add through measurement at reflection port
|
||||
continue;
|
||||
} else if(i != j && t->isReflection()) {
|
||||
// can not add reflection measurement at through port
|
||||
continue;
|
||||
}
|
||||
c->addItem(t->name(), QVariant::fromValue<Trace*>(t));
|
||||
}
|
||||
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [=](int) {
|
||||
selectionChanged(c);
|
||||
});
|
||||
cTraces[i].push_back(c);
|
||||
layout->addWidget(l, i, j*2);
|
||||
layout->addWidget(c, i, j*2 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceExportDialog::selectionChanged(QComboBox *w)
|
||||
{
|
||||
if(w->currentIndex() != 0 && !freqsSet) {
|
||||
// the first trace has been selected, extract frequency info
|
||||
Trace *t = qvariant_cast<Trace*>(w->itemData(w->currentIndex()));
|
||||
points = t->size();
|
||||
ui->points->setText(QString::number(points));
|
||||
if(points > 0) {
|
||||
lowerFreq = t->minFreq();
|
||||
upperFreq = t->maxFreq();
|
||||
ui->lowerFreq->setText(QString::number(lowerFreq));
|
||||
ui->upperFreq->setText(QString::number(upperFreq));
|
||||
}
|
||||
freqsSet = true;
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);
|
||||
// remove all trace options with incompatible frequencies
|
||||
for(auto v1 : cTraces) {
|
||||
for(auto c : v1) {
|
||||
for(int i=1;i<c->count();i++) {
|
||||
Trace *t = qvariant_cast<Trace*>(c->itemData(i));
|
||||
if(t->size() != points || (points > 0 && (t->minFreq() != lowerFreq || t->maxFreq() != upperFreq))) {
|
||||
// this trace is not available anymore
|
||||
c->removeItem(i);
|
||||
// decrement to check the next index in the next loop iteration
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(w->currentIndex() == 0 && freqsSet) {
|
||||
// Check if all trace selections are set for none
|
||||
for(auto v1 : cTraces) {
|
||||
for(auto c : v1) {
|
||||
if(c->currentIndex() != 0) {
|
||||
// some trace is still selected, abort
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// all traces set for none
|
||||
freqsSet = false;
|
||||
ui->points->clear();
|
||||
ui->lowerFreq->clear();
|
||||
ui->upperFreq->clear();
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
|
||||
}
|
||||
}
|
38
Software/PC_Application/Traces/traceexportdialog.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef TRACEEXPORTDIALOG_H
|
||||
#define TRACEEXPORTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QComboBox>
|
||||
#include "tracemodel.h"
|
||||
#include <QSignalMapper>
|
||||
|
||||
namespace Ui {
|
||||
class TraceExportDialog;
|
||||
}
|
||||
|
||||
class TraceExportDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TraceExportDialog(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TraceExportDialog();
|
||||
bool setTrace(int portFrom, int portTo, Trace *t);
|
||||
bool setPortNum(int ports);
|
||||
|
||||
private slots:
|
||||
void on_buttonBox_accepted();
|
||||
void on_sbPorts_valueChanged(int ports);
|
||||
void selectionChanged(QComboBox *w);
|
||||
|
||||
private:
|
||||
Ui::TraceExportDialog *ui;
|
||||
TraceModel &model;
|
||||
std::vector<std::vector<QComboBox*>> cTraces;
|
||||
|
||||
unsigned int points;
|
||||
double lowerFreq, upperFreq;
|
||||
bool freqsSet;
|
||||
};
|
||||
|
||||
#endif // TRACEEXPORTDIALOG_H
|
243
Software/PC_Application/Traces/traceexportdialog.ui
Normal file
@ -0,0 +1,243 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceExportDialog</class>
|
||||
<widget class="QDialog" name="TraceExportDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>516</width>
|
||||
<height>486</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Ports:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="sbPorts">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gbTraces">
|
||||
<property name="title">
|
||||
<string>Traces</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Touchstone Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Unit:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cUnit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Hz</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>kHz</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>MHz</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>GHz</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="cFormat">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>db/Angle</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Mag/Angle</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Real/Imag</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Trace Info</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Points:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="points">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Lower Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lowerFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Upper Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="upperFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>TraceExportDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
178
Software/PC_Application/Traces/traceimportdialog.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
#include "traceimportdialog.h"
|
||||
#include "ui_traceimportdialog.h"
|
||||
#include <QAbstractTableModel>
|
||||
#include <QObject>
|
||||
#include <QModelIndex>
|
||||
#include <QColorDialog>
|
||||
|
||||
TraceImportDialog::TraceImportDialog(TraceModel &model, std::vector<Trace*> traces, QString prefix, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::TraceImportDialog),
|
||||
model(model)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
tableModel = new TraceParameterModel(traces, prefix);
|
||||
ui->tableView->setModel(tableModel);
|
||||
}
|
||||
|
||||
TraceImportDialog::~TraceImportDialog()
|
||||
{
|
||||
delete ui;
|
||||
delete tableModel;
|
||||
}
|
||||
|
||||
void TraceImportDialog::on_buttonBox_accepted()
|
||||
{
|
||||
tableModel->import(model);
|
||||
}
|
||||
|
||||
TraceParameterModel::TraceParameterModel(std::vector<Trace *> traces, QString prefix, QObject *parent)
|
||||
: QAbstractTableModel(parent),
|
||||
traces(traces)
|
||||
{
|
||||
int hue = 0;
|
||||
for(auto t : traces) {
|
||||
Parameter p;
|
||||
p.name = prefix + t->name();
|
||||
p.color = QColor::fromHsl((hue++ * 30) % 360, 250, 128);
|
||||
p.enabled = true;
|
||||
p.trace = t->name();
|
||||
params.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
int TraceParameterModel::rowCount(const QModelIndex &) const {
|
||||
return params.size();
|
||||
}
|
||||
|
||||
int TraceParameterModel::columnCount(const QModelIndex &) const {
|
||||
return 4;
|
||||
}
|
||||
|
||||
QVariant TraceParameterModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if ((unsigned int) index.row() >= params.size())
|
||||
return QVariant();
|
||||
auto p = params[index.row()];
|
||||
if (index.column() == 0) {
|
||||
if (role == Qt::DisplayRole) {
|
||||
return p.trace;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
} else if (index.column() == 1) {
|
||||
if (role == Qt::CheckStateRole) {
|
||||
if(p.enabled) {
|
||||
return Qt::Checked;
|
||||
} else {
|
||||
return Qt::Unchecked;
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
} else if (index.column() == 2) {
|
||||
if (role == Qt::BackgroundRole || role == Qt::EditRole) {
|
||||
if(p.enabled) {
|
||||
return p.color;
|
||||
} else {
|
||||
return (QColor) Qt::gray;
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
} else if (index.column() == 3) {
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
||||
return p.name;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant TraceParameterModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||
switch(section) {
|
||||
case 0: return "Parameter"; break;
|
||||
case 1: return "Import"; break;
|
||||
case 2: return "Color"; break;
|
||||
case 3: return "Tracename"; break;
|
||||
default: return QVariant(); break;
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceParameterModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if((unsigned int) index.row() >= params.size()) {
|
||||
return false;
|
||||
}
|
||||
auto &p = params[index.row()];
|
||||
if(role == Qt::CheckStateRole && index.column()==1) {
|
||||
if((Qt::CheckState)value.toInt() == Qt::Checked) {
|
||||
p.enabled = true;
|
||||
} else {
|
||||
p.enabled = false;
|
||||
}
|
||||
dataChanged(this->index(index.row(),2), this->index(index.row(),3));
|
||||
return true;
|
||||
} else if(role == Qt::EditRole && index.column() == 2) {
|
||||
p.color = value.value<QColor>();
|
||||
return true;
|
||||
} else if(role == Qt::EditRole && index.column() == 3) {
|
||||
p.name = value.toString();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::ItemFlags TraceParameterModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
int flags = Qt::NoItemFlags;
|
||||
if(index.column() == 0) {
|
||||
flags |= Qt::ItemIsEnabled;
|
||||
} else if(index.column() == 1) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
flags |= Qt::ItemIsEnabled;
|
||||
} else if(index.column() == 2) {
|
||||
if(params[index.row()].enabled) {
|
||||
flags |= Qt::ItemIsEnabled;
|
||||
}
|
||||
} else if(index.column() == 3) {
|
||||
flags |= Qt::ItemIsEditable;
|
||||
if(params[index.row()].enabled) {
|
||||
flags |= Qt::ItemIsEnabled;
|
||||
}
|
||||
}
|
||||
return (Qt::ItemFlags) flags;
|
||||
}
|
||||
|
||||
void TraceParameterModel::import(TraceModel &model)
|
||||
{
|
||||
for(unsigned int i=0;i<params.size();i++) {
|
||||
if(params[i].enabled) {
|
||||
traces[i]->setColor(params[i].color);
|
||||
traces[i]->setName(params[i].name);
|
||||
model.addTrace(traces[i]);
|
||||
} else {
|
||||
delete traces[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceImportDialog::on_tableView_doubleClicked(const QModelIndex &index)
|
||||
{
|
||||
if(index.column() == 2 && tableModel->params[index.row()].enabled) {
|
||||
auto initialColor = tableModel->params[index.row()].color;
|
||||
auto newColor = QColorDialog::getColor(initialColor, this, "Select color", QColorDialog::DontUseNativeDialog);
|
||||
if(newColor.isValid()) {
|
||||
tableModel->setData(index, newColor);
|
||||
}
|
||||
}
|
||||
}
|
59
Software/PC_Application/Traces/traceimportdialog.h
Normal file
@ -0,0 +1,59 @@
|
||||
#ifndef TRACEIMPORTDIALOG_H
|
||||
#define TRACEIMPORTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "tracemodel.h"
|
||||
#include <vector>
|
||||
|
||||
namespace Ui {
|
||||
class TraceImportDialog;
|
||||
}
|
||||
|
||||
class TraceParameterModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class TraceImportDialog;
|
||||
public:
|
||||
TraceParameterModel(std::vector<Trace*> traces, QString prefix, QObject *parent = 0);
|
||||
~TraceParameterModel(){};
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
// adds all enabled traces to the model, deletes other traces
|
||||
void import(TraceModel &model);
|
||||
private:
|
||||
class Parameter {
|
||||
public:
|
||||
bool enabled;
|
||||
QString trace;
|
||||
QString name;
|
||||
QColor color;
|
||||
};
|
||||
std::vector<Parameter> params;
|
||||
std::vector<Trace*> traces;
|
||||
};
|
||||
|
||||
class TraceImportDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TraceImportDialog(TraceModel &model, std::vector<Trace*> traces, QString prefix = QString(), QWidget *parent = nullptr);
|
||||
~TraceImportDialog();
|
||||
|
||||
private slots:
|
||||
void on_buttonBox_accepted();
|
||||
void on_tableView_doubleClicked(const QModelIndex &index);
|
||||
|
||||
private:
|
||||
Ui::TraceImportDialog *ui;
|
||||
TraceModel &model;
|
||||
TraceParameterModel *tableModel;
|
||||
};
|
||||
|
||||
#endif // TRACEIMPORTDIALOG_H
|
83
Software/PC_Application/Traces/traceimportdialog.ui
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceImportDialog</class>
|
||||
<widget class="QDialog" name="TraceImportDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>396</width>
|
||||
<height>420</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Import Traces</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="tableView">
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderMinimumSectionSize">
|
||||
<number>21</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderDefaultSectionSize">
|
||||
<number>80</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>TraceImportDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>TraceImportDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
122
Software/PC_Application/Traces/tracemarker.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include "tracemarker.h"
|
||||
#include <QPainter>
|
||||
|
||||
TraceMarker::TraceMarker()
|
||||
: parentTrace(nullptr),
|
||||
frequency(1000000000),
|
||||
number(1),
|
||||
data(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TraceMarker::~TraceMarker()
|
||||
{
|
||||
if(parentTrace) {
|
||||
parentTrace->removeMarker(this);
|
||||
}
|
||||
emit deleted(this);
|
||||
}
|
||||
|
||||
void TraceMarker::assignTrace(Trace *t)
|
||||
{
|
||||
if(parentTrace) {
|
||||
// remove connection from previous parent trace
|
||||
parentTrace->removeMarker(this);
|
||||
disconnect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted);
|
||||
disconnect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
|
||||
disconnect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
|
||||
}
|
||||
parentTrace = t;
|
||||
connect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted);
|
||||
connect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
|
||||
connect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
|
||||
constrainFrequency();
|
||||
updateSymbol();
|
||||
parentTrace->addMarker(this);
|
||||
}
|
||||
|
||||
Trace *TraceMarker::trace()
|
||||
{
|
||||
return parentTrace;
|
||||
}
|
||||
|
||||
QString TraceMarker::readableData()
|
||||
{
|
||||
auto db = 20*log10(abs(data));
|
||||
auto phase = arg(data);
|
||||
return QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
|
||||
}
|
||||
|
||||
void TraceMarker::setFrequency(double freq)
|
||||
{
|
||||
frequency = freq;
|
||||
constrainFrequency();
|
||||
}
|
||||
|
||||
void TraceMarker::parentTraceDeleted(Trace *t)
|
||||
{
|
||||
if(t == parentTrace) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceMarker::traceDataChanged()
|
||||
{
|
||||
// some data of the parent trace changed, check if marker data also changed
|
||||
auto tracedata = parentTrace->getData(frequency);
|
||||
if(tracedata != data) {
|
||||
data = tracedata;
|
||||
emit dataChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceMarker::updateSymbol()
|
||||
{
|
||||
constexpr int width = 15, height = 15;
|
||||
symbol = QPixmap(width, height);
|
||||
symbol.fill(Qt::transparent);
|
||||
QPainter p(&symbol);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
QPointF points[] = {QPointF(0,0),QPointF(width,0),QPointF(width/2,height)};
|
||||
auto traceColor = parentTrace->color();
|
||||
p.setPen(traceColor);
|
||||
p.setBrush(traceColor);
|
||||
p.drawConvexPolygon(points, 3);
|
||||
auto brightness = traceColor.redF() * 0.299 + traceColor.greenF() * 0.587 + traceColor.blueF() * 0.114;
|
||||
p.setPen((brightness > 0.6) ? Qt::black : Qt::white);
|
||||
p.drawText(QRectF(0,0,width, height*2.0/3.0), Qt::AlignCenter, QString::number(number));
|
||||
}
|
||||
|
||||
void TraceMarker::constrainFrequency()
|
||||
{
|
||||
if(parentTrace && parentTrace->size() > 0) {
|
||||
if(frequency > parentTrace->maxFreq()) {
|
||||
frequency = parentTrace->maxFreq();
|
||||
} else if(frequency < parentTrace->minFreq()) {
|
||||
frequency = parentTrace->minFreq();
|
||||
}
|
||||
traceDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int TraceMarker::getNumber() const
|
||||
{
|
||||
return number;
|
||||
}
|
||||
|
||||
std::complex<double> TraceMarker::getData() const
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
QPixmap &TraceMarker::getSymbol()
|
||||
{
|
||||
return symbol;
|
||||
}
|
||||
|
||||
double TraceMarker::getFrequency() const
|
||||
{
|
||||
return frequency;
|
||||
}
|
||||
|
45
Software/PC_Application/Traces/tracemarker.h
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef TRACEMARKER_H
|
||||
#define TRACEMARKER_H
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QObject>
|
||||
#include "trace.h"
|
||||
|
||||
class TraceMarker : public QObject
|
||||
{
|
||||
friend class TraceMarkerModel;
|
||||
Q_OBJECT;
|
||||
public:
|
||||
TraceMarker();
|
||||
~TraceMarker();
|
||||
void assignTrace(Trace *t);
|
||||
Trace* trace();
|
||||
QString readableData();
|
||||
|
||||
double getFrequency() const;
|
||||
std::complex<double> getData() const;
|
||||
|
||||
QPixmap& getSymbol();
|
||||
|
||||
int getNumber() const;
|
||||
|
||||
public slots:
|
||||
void setFrequency(double freq);
|
||||
signals:
|
||||
void deleted(TraceMarker *m);
|
||||
void dataChanged(TraceMarker *m);
|
||||
|
||||
private slots:
|
||||
void parentTraceDeleted(Trace *t);
|
||||
void traceDataChanged();
|
||||
void updateSymbol();
|
||||
private:
|
||||
void constrainFrequency();
|
||||
Trace *parentTrace;
|
||||
double frequency;
|
||||
int number;
|
||||
std::complex<double> data;
|
||||
QPixmap symbol;
|
||||
};
|
||||
|
||||
#endif // TRACEMARKER_H
|
228
Software/PC_Application/Traces/tracemarkermodel.cpp
Normal file
@ -0,0 +1,228 @@
|
||||
#include "tracemarkermodel.h"
|
||||
#include "unit.h"
|
||||
#include <QComboBox>
|
||||
#include <QApplication>
|
||||
|
||||
TraceMarkerModel::TraceMarkerModel(TraceModel &model, QObject *parent)
|
||||
: QAbstractTableModel(parent),
|
||||
model(model)
|
||||
{
|
||||
markers.clear();
|
||||
}
|
||||
|
||||
TraceMarker *TraceMarkerModel::createDefaultMarker()
|
||||
{
|
||||
// find lowest free number
|
||||
int number = 0;
|
||||
bool used;
|
||||
do {
|
||||
number++;
|
||||
used = false;
|
||||
for(auto m : markers) {
|
||||
if(m->number == number) {
|
||||
used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (used);
|
||||
auto marker = new TraceMarker();
|
||||
marker->number = number;
|
||||
marker->frequency = 2150000000;
|
||||
marker->assignTrace(model.trace(0));
|
||||
return marker;
|
||||
}
|
||||
|
||||
void TraceMarkerModel::addMarker(TraceMarker *t)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), markers.size(), markers.size());
|
||||
markers.push_back(t);
|
||||
endInsertRows();
|
||||
connect(t, &TraceMarker::dataChanged, this, &TraceMarkerModel::markerDataChanged);
|
||||
connect(t, &TraceMarker::deleted, this, qOverload<TraceMarker*>(&TraceMarkerModel::removeMarker));
|
||||
emit markerAdded(t);
|
||||
}
|
||||
|
||||
void TraceMarkerModel::removeMarker(unsigned int index, bool delete_marker)
|
||||
{
|
||||
if (index < markers.size()) {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
if(delete_marker) {
|
||||
delete markers[index];
|
||||
}
|
||||
markers.erase(markers.begin() + index);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void TraceMarkerModel::removeMarker(TraceMarker *m)
|
||||
{
|
||||
auto it = std::find(markers.begin(), markers.end(), m);
|
||||
if(it != markers.end()) {
|
||||
removeMarker(it - markers.begin(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceMarkerModel::markerDataChanged(TraceMarker *)
|
||||
{
|
||||
emit dataChanged(index(0, ColIndexFreq), index(markers.size()-1, ColIndexData));
|
||||
}
|
||||
|
||||
TraceMarker *TraceMarkerModel::marker(int index)
|
||||
{
|
||||
return markers.at(index);
|
||||
}
|
||||
|
||||
int TraceMarkerModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return markers.size();
|
||||
}
|
||||
|
||||
int TraceMarkerModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
QVariant TraceMarkerModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
auto marker = markers[index.row()];
|
||||
switch(index.column()) {
|
||||
case ColIndexNumber:
|
||||
switch(role) {
|
||||
case Qt::DisplayRole: return QVariant((unsigned int)marker->number); break;
|
||||
}
|
||||
case ColIndexTrace:
|
||||
switch(role) {
|
||||
case Qt::DisplayRole:
|
||||
if(marker->parentTrace) {
|
||||
return marker->parentTrace->name();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ColIndexFreq:
|
||||
switch(role) {
|
||||
case Qt::DisplayRole: return Unit::ToString(marker->frequency, "Hz", " kMG", 6); break;
|
||||
}
|
||||
case ColIndexData:
|
||||
switch(role) {
|
||||
case Qt::DisplayRole: return marker->readableData(); break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant TraceMarkerModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||
switch(section) {
|
||||
case ColIndexNumber: return "#"; break;
|
||||
case ColIndexTrace: return "Trace"; break;
|
||||
case ColIndexFreq: return "Frequency"; break;
|
||||
case ColIndexData: return "Data"; break;
|
||||
default: return QVariant(); break;
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceMarkerModel::setData(const QModelIndex &index, const QVariant &value, int)
|
||||
{
|
||||
if((unsigned int) index.row() >= markers.size()) {
|
||||
return false;
|
||||
}
|
||||
auto m = markers[index.row()];
|
||||
switch(index.column()) {
|
||||
case ColIndexNumber: {
|
||||
bool convertOk;
|
||||
unsigned int number;
|
||||
number = value.toUInt(&convertOk);
|
||||
if(convertOk) {
|
||||
m->number = number;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ColIndexTrace: {
|
||||
auto trace = qvariant_cast<Trace*>(value);
|
||||
m->assignTrace(trace);
|
||||
}
|
||||
break;
|
||||
case ColIndexFreq: {
|
||||
auto newval = Unit::FromString(value.toString(), "Hz", " kMG");
|
||||
if(!qIsNaN(newval)) {
|
||||
m->setFrequency(newval);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::ItemFlags TraceMarkerModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
int flags = Qt::NoItemFlags;
|
||||
switch(index.column()) {
|
||||
case ColIndexNumber: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
|
||||
case ColIndexTrace: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
|
||||
case ColIndexFreq: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
|
||||
case ColIndexData: flags |= Qt::ItemIsEnabled; break;
|
||||
}
|
||||
return (Qt::ItemFlags) flags;
|
||||
}
|
||||
|
||||
std::vector<TraceMarker *> TraceMarkerModel::getMarker()
|
||||
{
|
||||
return markers;
|
||||
}
|
||||
|
||||
std::vector<TraceMarker *> TraceMarkerModel::getMarker(Trace *t)
|
||||
{
|
||||
std::vector<TraceMarker*> attachedMarkers;
|
||||
for(auto m : markers) {
|
||||
if(m->parentTrace == t) {
|
||||
attachedMarkers.push_back(m);
|
||||
}
|
||||
}
|
||||
return attachedMarkers;
|
||||
}
|
||||
|
||||
TraceModel &TraceMarkerModel::getModel()
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
QWidget *TraceChooserDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
|
||||
{
|
||||
auto model = (TraceMarkerModel*) index.model();
|
||||
auto c = new QComboBox(parent);
|
||||
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [c](int) {
|
||||
c->clearFocus();
|
||||
});
|
||||
auto traces = model->getModel().getTraces();
|
||||
for(auto t : traces) {
|
||||
c->addItem(t->name(), QVariant::fromValue<Trace*>(t));
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void TraceChooserDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
auto model = (TraceMarkerModel*) index.model();
|
||||
auto marker = model->getMarker()[index.row()];
|
||||
auto c = (QComboBox*) editor;
|
||||
for(int i=0;i<c->count();i++) {
|
||||
if(qvariant_cast<Trace*>(c->itemData(i)) == marker->trace()) {
|
||||
c->setCurrentIndex(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceChooserDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
|
||||
{
|
||||
auto markerModel = (TraceMarkerModel*) model;
|
||||
auto c = (QComboBox*) editor;
|
||||
markerModel->setData(index, c->itemData(c->currentIndex()));
|
||||
}
|
61
Software/PC_Application/Traces/tracemarkermodel.h
Normal file
@ -0,0 +1,61 @@
|
||||
#ifndef TRACEMARKERMODEL_H
|
||||
#define TRACEMARKERMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include "tracemarker.h"
|
||||
#include <vector>
|
||||
#include "tracemodel.h"
|
||||
#include <QItemDelegate>
|
||||
|
||||
class TraceChooserDelegate : public QItemDelegate
|
||||
{
|
||||
Q_OBJECT;
|
||||
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;
|
||||
};
|
||||
|
||||
class TraceMarkerModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TraceMarkerModel(TraceModel &model, QObject *parent = 0);
|
||||
|
||||
enum {
|
||||
ColIndexNumber = 0,
|
||||
ColIndexTrace = 1,
|
||||
ColIndexFreq = 2,
|
||||
ColIndexData = 3,
|
||||
};
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
TraceMarker* createDefaultMarker();
|
||||
TraceMarker *marker(int index);
|
||||
std::vector<TraceMarker*> getMarker();
|
||||
std::vector<TraceMarker*> getMarker(Trace *t);
|
||||
TraceModel& getModel();
|
||||
|
||||
public slots:
|
||||
void addMarker(TraceMarker *t);
|
||||
void removeMarker(unsigned int index, bool delete_marker = true);
|
||||
void removeMarker(TraceMarker *m);
|
||||
|
||||
|
||||
signals:
|
||||
void markerAdded(TraceMarker *t);
|
||||
|
||||
private slots:
|
||||
void markerDataChanged(TraceMarker *m);
|
||||
private:
|
||||
std::vector<TraceMarker*> markers;
|
||||
TraceModel &model;
|
||||
|
||||
};
|
||||
|
||||
#endif // TRACEMARKERMODEL_H
|
137
Software/PC_Application/Traces/tracemodel.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
#include "tracemodel.h"
|
||||
#include <QIcon>
|
||||
|
||||
using namespace std;
|
||||
|
||||
TraceModel::TraceModel(QObject *parent)
|
||||
: QAbstractTableModel(parent)
|
||||
{
|
||||
traces.clear();
|
||||
}
|
||||
|
||||
void TraceModel::addTrace(Trace *t)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), traces.size(), traces.size());
|
||||
traces.push_back(t);
|
||||
endInsertRows();
|
||||
emit traceAdded(t);
|
||||
}
|
||||
|
||||
void TraceModel::removeTrace(unsigned int index)
|
||||
{
|
||||
if (index < traces.size()) {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
auto trace = traces[index];
|
||||
delete trace;
|
||||
traces.erase(traces.begin() + index);
|
||||
endRemoveRows();
|
||||
emit traceRemoved(trace);
|
||||
}
|
||||
}
|
||||
|
||||
Trace *TraceModel::trace(unsigned int index)
|
||||
{
|
||||
return traces.at(index);
|
||||
}
|
||||
|
||||
void TraceModel::toggleVisibility(unsigned int index)
|
||||
{
|
||||
if (index < traces.size()) {
|
||||
traces[index]->setVisible(!traces[index]->isVisible());
|
||||
emit dataChanged(createIndex(index, 0), createIndex(index, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void TraceModel::togglePause(unsigned int index)
|
||||
{
|
||||
if (index < traces.size()) {
|
||||
if(traces[index]->isPaused()) {
|
||||
traces[index]->resume();
|
||||
} else {
|
||||
traces[index]->pause();
|
||||
}
|
||||
emit dataChanged(createIndex(index, 1), createIndex(index, 1));
|
||||
}
|
||||
}
|
||||
|
||||
int TraceModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return traces.size();
|
||||
}
|
||||
|
||||
int TraceModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
QVariant TraceModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if ((unsigned int) index.row() >= traces.size())
|
||||
return QVariant();
|
||||
if (index.column() == 0) {
|
||||
if (role == Qt::DecorationRole) {
|
||||
if (traces[index.row()]->isVisible()) {
|
||||
return QIcon(":/icons/visible.svg");
|
||||
} else {
|
||||
return QIcon(":/icons/invisible.svg");
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
} else if (index.column() == 1) {
|
||||
if (role == Qt::DecorationRole && !traces[index.row()]->isTouchstone()) {
|
||||
if (traces[index.row()]->isPaused()) {
|
||||
return QIcon(":/icons/pause.svg");
|
||||
} else {
|
||||
return QIcon(":/icons/play.svg");
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
} else if (index.column() == 2) {
|
||||
if (role == Qt::DisplayRole) {
|
||||
return traces[index.row()]->name();
|
||||
} else if (role == Qt::ForegroundRole) {
|
||||
return traces[index.row()]->color();
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Trace *> TraceModel::getTraces()
|
||||
{
|
||||
return traces;
|
||||
}
|
||||
|
||||
void TraceModel::clearVNAData()
|
||||
{
|
||||
for(auto t : traces) {
|
||||
if (!t->isTouchstone()) {
|
||||
// this trace is fed from live data
|
||||
t->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceModel::addVNAData(Protocol::Datapoint d)
|
||||
{
|
||||
for(auto t : traces) {
|
||||
if (t->isLive()) {
|
||||
Trace::Data td;
|
||||
td.frequency = d.frequency;
|
||||
switch(t->liveParameter()) {
|
||||
case Trace::LiveParameter::S11: td.S = complex<double>(d.real_S11, d.imag_S11); break;
|
||||
case Trace::LiveParameter::S12: td.S = complex<double>(d.real_S12, d.imag_S12); break;
|
||||
case Trace::LiveParameter::S21: td.S = complex<double>(d.real_S21, d.imag_S21); break;
|
||||
case Trace::LiveParameter::S22: td.S = complex<double>(d.real_S22, d.imag_S22); break;
|
||||
}
|
||||
t->addData(td);
|
||||
}
|
||||
}
|
||||
}
|
38
Software/PC_Application/Traces/tracemodel.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef TRACEMODEL_H
|
||||
#define TRACEMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include "trace.h"
|
||||
#include <vector>
|
||||
#include "Device/device.h"
|
||||
|
||||
class TraceModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TraceModel(QObject *parent = 0);
|
||||
|
||||
void addTrace(Trace *t);
|
||||
void removeTrace(unsigned int index);
|
||||
Trace *trace(unsigned int index);
|
||||
void toggleVisibility(unsigned int index);
|
||||
void togglePause(unsigned int index);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
std::vector<Trace*> getTraces();
|
||||
signals:
|
||||
void traceAdded(Trace *t);
|
||||
void traceRemoved(Trace *t);
|
||||
|
||||
public slots:
|
||||
void clearVNAData();
|
||||
void addVNAData(Protocol::Datapoint d);
|
||||
|
||||
private:
|
||||
std::vector<Trace*> traces;
|
||||
};
|
||||
|
||||
#endif // TRACEMODEL_H
|
145
Software/PC_Application/Traces/traceplot.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
#include "traceplot.h"
|
||||
|
||||
const QColor TracePlot::Background = QColor(0,0,0);
|
||||
const QColor TracePlot::Border = QColor(255,255,255);
|
||||
const QColor TracePlot::Divisions = QColor(255,255,255);
|
||||
#include "tracemarker.h"
|
||||
|
||||
std::set<TracePlot*> TracePlot::plots;
|
||||
|
||||
TracePlot::TracePlot(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
contextmenu = new QMenu();
|
||||
markedForDeletion = false;
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
lastUpdate = QTime::currentTime();
|
||||
plots.insert(this);
|
||||
}
|
||||
|
||||
TracePlot::~TracePlot()
|
||||
{
|
||||
plots.erase(this);
|
||||
delete contextmenu;
|
||||
}
|
||||
|
||||
void TracePlot::enableTrace(Trace *t, bool enabled)
|
||||
{
|
||||
if(traces[t] != enabled) {
|
||||
traces[t] = enabled;
|
||||
if(enabled) {
|
||||
// connect signals
|
||||
connect(t, &Trace::dataChanged, this, &TracePlot::triggerReplot);
|
||||
connect(t, &Trace::visibilityChanged, this, &TracePlot::triggerReplot);
|
||||
connect(t, &Trace::markerAdded, this, &TracePlot::markerAdded);
|
||||
connect(t, &Trace::markerRemoved, this, &TracePlot::markerRemoved);
|
||||
} else {
|
||||
// disconnect from notifications
|
||||
disconnect(t, &Trace::dataChanged, this, &TracePlot::triggerReplot);
|
||||
disconnect(t, &Trace::visibilityChanged, this, &TracePlot::triggerReplot);
|
||||
disconnect(t, &Trace::markerAdded, this, &TracePlot::markerAdded);
|
||||
disconnect(t, &Trace::markerRemoved, this, &TracePlot::markerRemoved);
|
||||
}
|
||||
updateContextMenu();
|
||||
triggerReplot();
|
||||
}
|
||||
}
|
||||
|
||||
void TracePlot::mouseDoubleClickEvent(QMouseEvent *) {
|
||||
emit doubleClicked(this);
|
||||
}
|
||||
|
||||
void TracePlot::UpdateSpan(double fmin, double fmax)
|
||||
{
|
||||
for(auto p : plots) {
|
||||
p->setXAxis(fmin, fmax);
|
||||
}
|
||||
}
|
||||
|
||||
void TracePlot::initializeTraceInfo(TraceModel &model)
|
||||
{
|
||||
// Populate already present traces
|
||||
auto tvect = model.getTraces();
|
||||
for(auto t : tvect) {
|
||||
newTraceAvailable(t);
|
||||
}
|
||||
|
||||
// connect notification of traces added at later point
|
||||
connect(&model, &TraceModel::traceAdded, this, &TracePlot::newTraceAvailable);
|
||||
}
|
||||
|
||||
void TracePlot::contextMenuEvent(QContextMenuEvent *event)
|
||||
{
|
||||
contextmenu->exec(event->globalPos());
|
||||
if(markedForDeletion) {
|
||||
emit deleted(this);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void TracePlot::updateContextMenu()
|
||||
{
|
||||
contextmenu->clear();
|
||||
contextmenu->addSection("Traces");
|
||||
// Populate context menu
|
||||
for(auto t : traces) {
|
||||
auto action = new QAction(t.first->name());
|
||||
action->setCheckable(true);
|
||||
if(t.second) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
connect(action, &QAction::toggled, [=](bool active) {
|
||||
enableTrace(t.first, active);
|
||||
});
|
||||
contextmenu->addAction(action);
|
||||
}
|
||||
contextmenu->addSeparator();
|
||||
auto close = new QAction("Close");
|
||||
contextmenu->addAction(close);
|
||||
connect(close, &QAction::triggered, [=]() {
|
||||
markedForDeletion = true;
|
||||
});
|
||||
}
|
||||
|
||||
std::set<TracePlot *> TracePlot::getPlots()
|
||||
{
|
||||
return plots;
|
||||
}
|
||||
|
||||
void TracePlot::newTraceAvailable(Trace *t)
|
||||
{
|
||||
if(supported(t)) {
|
||||
traces[t] = false;
|
||||
connect(t, &Trace::deleted, this, &TracePlot::traceDeleted);
|
||||
connect(t, &Trace::nameChanged, this, &TracePlot::updateContextMenu);
|
||||
connect(t, &Trace::typeChanged, this, &TracePlot::updateContextMenu);
|
||||
}
|
||||
updateContextMenu();
|
||||
}
|
||||
|
||||
void TracePlot::traceDeleted(Trace *t)
|
||||
{
|
||||
enableTrace(t, false);
|
||||
traces.erase(t);
|
||||
updateContextMenu();
|
||||
triggerReplot();
|
||||
}
|
||||
|
||||
void TracePlot::triggerReplot()
|
||||
{
|
||||
auto now = QTime::currentTime();
|
||||
if (lastUpdate.msecsTo(now) >= MinUpdateInterval) {
|
||||
replot();
|
||||
lastUpdate = now;
|
||||
}
|
||||
}
|
||||
|
||||
void TracePlot::markerAdded(TraceMarker *m)
|
||||
{
|
||||
connect(m, &TraceMarker::dataChanged, this, &TracePlot::triggerReplot);
|
||||
triggerReplot();
|
||||
}
|
||||
|
||||
void TracePlot::markerRemoved(TraceMarker *)
|
||||
{
|
||||
triggerReplot();
|
||||
}
|
55
Software/PC_Application/Traces/traceplot.h
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef TRACEPLOT_H
|
||||
#define TRACEPLOT_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "tracemodel.h"
|
||||
#include <QMenu>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QTime>
|
||||
|
||||
class TracePlot : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TracePlot( QWidget *parent = nullptr);
|
||||
~TracePlot();
|
||||
|
||||
virtual void enableTrace(Trace *t, bool enabled);
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
virtual void setXAxis(double min, double max){Q_UNUSED(min);Q_UNUSED(max)};
|
||||
|
||||
static std::set<TracePlot *> getPlots();
|
||||
static void UpdateSpan(double fmin, double fmax);
|
||||
|
||||
signals:
|
||||
void doubleClicked(QWidget *w);
|
||||
void deleted(TracePlot*);
|
||||
|
||||
protected:
|
||||
static const QColor Background;// = QColor(0,0,0);
|
||||
static const QColor Border;// = QColor(255,255,255);
|
||||
static const QColor Divisions;// = QColor(255,255,255);
|
||||
static constexpr int MinUpdateInterval = 100;
|
||||
// need to be called in derived class constructor
|
||||
void initializeTraceInfo(TraceModel &model);
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
virtual void updateContextMenu();
|
||||
virtual bool supported(Trace *t) = 0;
|
||||
virtual void replot(){};
|
||||
std::map<Trace*, bool> traces;
|
||||
QMenu *contextmenu;
|
||||
QTime lastUpdate;
|
||||
bool markedForDeletion;
|
||||
|
||||
static std::set<TracePlot*> plots;
|
||||
|
||||
protected slots:
|
||||
void newTraceAvailable(Trace *t);
|
||||
void traceDeleted(Trace *t);
|
||||
void triggerReplot();
|
||||
virtual void markerAdded(TraceMarker *m);
|
||||
virtual void markerRemoved(TraceMarker *m);
|
||||
|
||||
};
|
||||
|
||||
#endif // TRACEPLOT_H
|
184
Software/PC_Application/Traces/tracesmithchart.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#include "tracesmithchart.h"
|
||||
#include <QPainter>
|
||||
#include <array>
|
||||
#include <math.h>
|
||||
#include "tracemarker.h"
|
||||
#include <QDebug>
|
||||
|
||||
using namespace std;
|
||||
|
||||
TraceSmithChart::TraceSmithChart(TraceModel &model, QWidget *parent)
|
||||
: TracePlot(parent)
|
||||
{
|
||||
chartLinesPen = QPen(palette().windowText(), 0.75);
|
||||
thinPen = QPen(palette().windowText(), 0.25);
|
||||
textPen = QPen(palette().windowText(), 0.25);
|
||||
pointDataPen = QPen(QColor("red"), 4.0, Qt::SolidLine, Qt::RoundCap);
|
||||
lineDataPen = QPen(QColor("blue"), 1.0);
|
||||
initializeTraceInfo(model);
|
||||
}
|
||||
|
||||
QPoint TraceSmithChart::plotToPixel(std::complex<double> S)
|
||||
{
|
||||
QPoint ret;
|
||||
ret.setX(S.real() * plotToPixelXScale + plotToPixelXOffset);
|
||||
ret.setY(S.imag() * plotToPixelYScale + plotToPixelYOffset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::complex<double> TraceSmithChart::pixelToPlot(const QPoint &pos)
|
||||
{
|
||||
return complex<double>((pos.x() - plotToPixelXOffset) / plotToPixelXScale, (pos.y() - plotToPixelYOffset) / plotToPixelYScale);
|
||||
}
|
||||
|
||||
void TraceSmithChart::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
auto clickPoint = event->pos();
|
||||
unsigned int closestDistance = numeric_limits<unsigned int>::max();
|
||||
TraceMarker *closestMarker = nullptr;
|
||||
for(auto t : traces) {
|
||||
auto markers = t.first->getMarkers();
|
||||
for(auto m : markers) {
|
||||
auto S = m->getData();
|
||||
auto markerPoint = plotToPixel(S);
|
||||
auto yDiff = abs(markerPoint.y() - clickPoint.y());
|
||||
auto xDiff = abs(markerPoint.x() - clickPoint.x());
|
||||
unsigned int distance = xDiff * xDiff + yDiff * yDiff;
|
||||
if(distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestMarker = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(closestDistance <= 400) {
|
||||
selectedMarker = closestMarker;
|
||||
} else {
|
||||
selectedMarker = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceSmithChart::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if(selectedMarker) {
|
||||
auto t = selectedMarker->trace();
|
||||
auto mouseS = pixelToPlot(event->pos());
|
||||
auto samples = t->size();
|
||||
double closestDistance = numeric_limits<double>::max();
|
||||
unsigned int closestIndex = 0;
|
||||
for(unsigned int i=0;i<samples;i++) {
|
||||
auto data = t->sample(i);
|
||||
auto distance = norm(data.S - mouseS);
|
||||
if(distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestIndex = i;
|
||||
}
|
||||
}
|
||||
selectedMarker->setFrequency(t->sample(closestIndex).frequency);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceSmithChart::draw(QPainter * painter, double width_factor) {
|
||||
painter->setPen(QPen(1.0 * width_factor));
|
||||
painter->setBrush(palette().windowText());
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// // Display parameter name
|
||||
// QFont font = painter->font();
|
||||
// font.setPixelSize(48);
|
||||
// font.setBold(true);
|
||||
// painter->setFont(font);
|
||||
// painter->drawText(-512, -512, title);
|
||||
|
||||
// Outer circle
|
||||
painter->setPen(QPen(Border, 1.5 * width_factor));
|
||||
QRectF rectangle(-smithCoordMax, -smithCoordMax, 2*smithCoordMax, 2*smithCoordMax);
|
||||
painter->drawArc(rectangle, 0, 5760);
|
||||
|
||||
constexpr int Circles = 6;
|
||||
painter->setPen(QPen(Divisions, 0.5 * width_factor, Qt::DashLine));
|
||||
for(int i=1;i<Circles;i++) {
|
||||
rectangle.adjust(2*smithCoordMax/Circles, smithCoordMax/Circles, 0, -smithCoordMax/Circles);
|
||||
painter->drawArc(rectangle, 0, 5760);
|
||||
}
|
||||
|
||||
painter->drawLine(-smithCoordMax, 0, smithCoordMax, 0);
|
||||
constexpr std::array<double, 5> impedanceLines = {10, 25, 50, 100, 250};
|
||||
for(auto z : impedanceLines) {
|
||||
z /= ReferenceImpedance;
|
||||
auto radius = smithCoordMax * 1.0/z;
|
||||
double span = M_PI - 2 * atan(radius/smithCoordMax);
|
||||
span *= 5760 / (2 * M_PI);
|
||||
QRectF rectangle(smithCoordMax - radius, -2*radius, 2 * radius, 2 * radius);
|
||||
painter->drawArc(rectangle, 4320 - span, span);
|
||||
rectangle = QRectF(smithCoordMax - radius, 0, 2 * radius, 2 * radius);
|
||||
painter->drawArc(rectangle, 1440, span);
|
||||
}
|
||||
|
||||
for(auto t : traces) {
|
||||
if(!t.second) {
|
||||
// trace not enabled in plot
|
||||
continue;
|
||||
}
|
||||
auto trace = t.first;
|
||||
if(!trace->isVisible()) {
|
||||
// trace marked invisible
|
||||
continue;
|
||||
}
|
||||
painter->setPen(QPen(trace->color(), 1.5 * width_factor));
|
||||
int nPoints = trace->size();
|
||||
for(int i=1;i<nPoints;i++) {
|
||||
auto last = trace->sample(i-1).S;
|
||||
auto now = trace->sample(i).S;
|
||||
if(isnan(now.real())) {
|
||||
break;
|
||||
}
|
||||
// scale to size of smith diagram
|
||||
last *= smithCoordMax;
|
||||
now *= smithCoordMax;
|
||||
// draw line
|
||||
painter->drawLine(std::real(last), -std::imag(last), std::real(now), -std::imag(now));
|
||||
}
|
||||
auto markers = t.first->getMarkers();
|
||||
for(auto m : markers) {
|
||||
auto coords = m->getData();
|
||||
coords *= smithCoordMax;
|
||||
auto symbol = m->getSymbol();
|
||||
symbol = symbol.scaled(symbol.width()*width_factor, symbol.height()*width_factor);
|
||||
painter->drawPixmap(coords.real() - symbol.width()/2, -coords.imag() - symbol.height(), symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceSmithChart::replot()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void TraceSmithChart::paintEvent(QPaintEvent * /* the event */)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setBackground(QBrush(Background));
|
||||
painter.fillRect(0, 0, width(), height(), QBrush(Background));
|
||||
|
||||
double side = qMin(width(), height()) * screenUsage;
|
||||
|
||||
painter.setViewport((width()-side)/2, (height()-side)/2, side, side);
|
||||
painter.setWindow(-smithCoordMax, -smithCoordMax, 2*smithCoordMax, 2*smithCoordMax);
|
||||
|
||||
plotToPixelXOffset = width()/2;
|
||||
plotToPixelYOffset = height()/2;
|
||||
plotToPixelXScale = side/2;
|
||||
plotToPixelYScale = -side/2;
|
||||
|
||||
draw(&painter, 2*smithCoordMax/side);
|
||||
}
|
||||
|
||||
bool TraceSmithChart::supported(Trace *t)
|
||||
{
|
||||
if(t->isReflection()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
44
Software/PC_Application/Traces/tracesmithchart.h
Normal file
@ -0,0 +1,44 @@
|
||||
#ifndef TRACESMITHCHART_H
|
||||
#define TRACESMITHCHART_H
|
||||
|
||||
#include "traceplot.h"
|
||||
#include <QPen>
|
||||
|
||||
class TraceSmithChart : public TracePlot
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TraceSmithChart(TraceModel &model, QWidget *parent = 0);
|
||||
|
||||
protected:
|
||||
static constexpr double ReferenceImpedance = 50.0;
|
||||
static constexpr double screenUsage = 0.9;
|
||||
static constexpr int smithCoordMax = 4096;
|
||||
|
||||
QPoint plotToPixel(std::complex<double> S);
|
||||
std::complex<double> pixelToPlot(const QPoint &pos);
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
bool supported(Trace *t) override;
|
||||
void draw(QPainter * painter, double width_factor);
|
||||
void replot() override;
|
||||
QPen textPen;
|
||||
QPen chartLinesPen;
|
||||
QPen thinPen;
|
||||
QPen pointDataPen;
|
||||
QPen lineDataPen;
|
||||
|
||||
/// Path for the thin arcs
|
||||
QPainterPath thinArcsPath;
|
||||
/// Path for the thick arcs
|
||||
QPainterPath thickArcsPath;
|
||||
|
||||
double plotToPixelXOffset, plotToPixelXScale;
|
||||
double plotToPixelYOffset, plotToPixelYScale;
|
||||
TraceMarker *selectedMarker;
|
||||
};
|
||||
|
||||
#endif // TRACESMITHCHART_H
|
121
Software/PC_Application/Traces/tracewidget.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include "tracewidget.h"
|
||||
#include "ui_tracewidget.h"
|
||||
#include "trace.h"
|
||||
#include <QKeyEvent>
|
||||
#include "traceeditdialog.h"
|
||||
#include "traceimportdialog.h"
|
||||
#include "traceexportdialog.h"
|
||||
#include <QFileDialog>
|
||||
|
||||
TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::TraceWidget),
|
||||
model(model)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->view->setModel(&model);
|
||||
ui->view->setAutoScroll(false);
|
||||
installEventFilter(this);
|
||||
createCount = 0;
|
||||
}
|
||||
|
||||
TraceWidget::~TraceWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void TraceWidget::on_add_clicked()
|
||||
{
|
||||
createCount++;
|
||||
auto t = new Trace("Trace #"+QString::number(createCount));
|
||||
t->setColor(QColor::fromHsl((createCount * 50) % 360, 250, 128));
|
||||
model.addTrace(t);
|
||||
}
|
||||
|
||||
void TraceWidget::on_remove_clicked()
|
||||
{
|
||||
model.removeTrace(ui->view->currentIndex().row());
|
||||
}
|
||||
|
||||
bool TraceWidget::eventFilter(QObject *, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
int key = static_cast<QKeyEvent *>(event)->key();
|
||||
if(key == Qt::Key_Escape) {
|
||||
ui->view->clearSelection();
|
||||
return true;
|
||||
} else if(key == Qt::Key_Delete) {
|
||||
model.removeTrace(ui->view->currentIndex().row());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TraceWidget::on_edit_clicked()
|
||||
{
|
||||
if(ui->view->currentIndex().isValid()) {
|
||||
auto edit = new TraceEditDialog(*model.trace(ui->view->currentIndex().row()));
|
||||
edit->show();
|
||||
}
|
||||
}
|
||||
|
||||
void TraceWidget::on_view_doubleClicked(const QModelIndex &index)
|
||||
{
|
||||
if(index.column() == 2) {
|
||||
auto edit = new TraceEditDialog(*model.trace(index.row()));
|
||||
edit->show();
|
||||
}
|
||||
}
|
||||
|
||||
void TraceWidget::on_view_clicked(const QModelIndex &index)
|
||||
{
|
||||
if(index.column()==0) {
|
||||
model.toggleVisibility(index.row());
|
||||
} else if(index.column()==1) {
|
||||
model.togglePause(index.row());
|
||||
}
|
||||
}
|
||||
|
||||
void TraceWidget::on_bImport_clicked()
|
||||
{
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if (filename.length() > 0) {
|
||||
auto t = Touchstone::fromFile(filename.toStdString());
|
||||
std::vector<Trace*> traces;
|
||||
for(unsigned int i=0;i<t.ports()*t.ports();i++) {
|
||||
auto trace = new Trace();
|
||||
trace->fillFromTouchstone(t, i, filename);
|
||||
unsigned int sink = i / t.ports() + 1;
|
||||
unsigned int source = i % t.ports() + 1;
|
||||
trace->setName("S"+QString::number(sink)+QString::number(source));
|
||||
traces.push_back(trace);
|
||||
}
|
||||
// contruct prefix from filename
|
||||
// remove any directory names (keep only the filename itself)
|
||||
int lastSlash = qMax(filename.lastIndexOf('/'), filename.lastIndexOf('\\'));
|
||||
if(lastSlash != -1) {
|
||||
filename.remove(0, lastSlash + 1);
|
||||
}
|
||||
// remove file type
|
||||
filename.truncate(filename.indexOf('.'));
|
||||
auto i = new TraceImportDialog(model, traces, filename+"_");
|
||||
i->show();
|
||||
}
|
||||
}
|
||||
|
||||
void TraceWidget::on_bExport_clicked()
|
||||
{
|
||||
auto e = new TraceExportDialog(model);
|
||||
// Attempt to set default traces (this will result in correctly populated
|
||||
// 2 port export if the initial 4 traces have not been modified)
|
||||
e->setPortNum(2);
|
||||
auto traces = model.getTraces();
|
||||
for(unsigned int i=0;i<4;i++) {
|
||||
if(i >= traces.size()) {
|
||||
break;
|
||||
}
|
||||
e->setTrace(i%2, i/2, traces[i]);
|
||||
}
|
||||
e->show();
|
||||
}
|
41
Software/PC_Application/Traces/tracewidget.h
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef TRACEWIDGET_H
|
||||
#define TRACEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "tracemodel.h"
|
||||
|
||||
namespace Ui {
|
||||
class TraceWidget;
|
||||
}
|
||||
|
||||
class TraceWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TraceWidget(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TraceWidget();
|
||||
|
||||
public slots:
|
||||
void on_add_clicked();
|
||||
|
||||
private slots:
|
||||
void on_remove_clicked();
|
||||
void on_edit_clicked();
|
||||
|
||||
void on_view_doubleClicked(const QModelIndex &index);
|
||||
|
||||
void on_view_clicked(const QModelIndex &index);
|
||||
|
||||
void on_bImport_clicked();
|
||||
|
||||
void on_bExport_clicked();
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
Ui::TraceWidget *ui;
|
||||
TraceModel &model;
|
||||
int createCount;
|
||||
};
|
||||
|
||||
#endif // TRACEWIDGET_H
|
179
Software/PC_Application/Traces/tracewidget.ui
Normal file
@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceWidget</class>
|
||||
<widget class="QWidget" name="TraceWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>206</width>
|
||||
<height>268</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="view">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Ignored" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerItem</enum>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderMinimumSectionSize">
|
||||
<number>0</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderDefaultSectionSize">
|
||||
<number>21</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderDefaultSectionSize">
|
||||
<number>21</number>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="add">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add"/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="remove">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bImport">
|
||||
<property name="toolTip">
|
||||
<string>Import</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/icons/import.svg</normaloff>:/icons/import.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bExport">
|
||||
<property name="toolTip">
|
||||
<string>Export</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/icons/export.svg</normaloff>:/icons/export.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="edit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="accessories-text-editor"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
78
Software/PC_Application/averaging.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "averaging.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Averaging::Averaging()
|
||||
{
|
||||
averages = 1;
|
||||
}
|
||||
|
||||
void Averaging::reset()
|
||||
{
|
||||
avg.clear();
|
||||
}
|
||||
|
||||
void Averaging::setAverages(unsigned int a)
|
||||
{
|
||||
averages = a;
|
||||
reset();
|
||||
}
|
||||
|
||||
Protocol::Datapoint Averaging::process(Protocol::Datapoint d)
|
||||
{
|
||||
auto S11 = complex<double>(d.real_S11, d.imag_S11);
|
||||
auto S12 = complex<double>(d.real_S12, d.imag_S12);
|
||||
auto S21 = complex<double>(d.real_S21, d.imag_S21);
|
||||
auto S22 = complex<double>(d.real_S22, d.imag_S22);
|
||||
|
||||
if (d.pointNum == avg.size()) {
|
||||
// add moving average entry
|
||||
deque<array<complex<double>, 4>> deque;
|
||||
avg.push_back(deque);
|
||||
}
|
||||
|
||||
if (d.pointNum < avg.size()) {
|
||||
// can compute average
|
||||
// get correct queue
|
||||
auto deque = &avg[d.pointNum];
|
||||
// add newest sample to queue
|
||||
array<complex<double>, 4> sample = {S11, S12, S21, S22};
|
||||
deque->push_back(sample);
|
||||
if(deque->size() > averages) {
|
||||
deque->pop_front();
|
||||
}
|
||||
|
||||
// calculate average
|
||||
complex<double> sum[4];
|
||||
for(auto s : *deque) {
|
||||
sum[0] += s[0];
|
||||
sum[1] += s[1];
|
||||
sum[2] += s[2];
|
||||
sum[3] += s[3];
|
||||
}
|
||||
S11 = sum[0] / (double) (deque->size());
|
||||
S12 = sum[1] / (double) (deque->size());
|
||||
S21 = sum[2] / (double) (deque->size());
|
||||
S22 = sum[3] / (double) (deque->size());
|
||||
}
|
||||
|
||||
d.real_S11 = S11.real();
|
||||
d.imag_S11 = S11.imag();
|
||||
d.real_S12 = S12.real();
|
||||
d.imag_S12 = S12.imag();
|
||||
d.real_S21 = S21.real();
|
||||
d.imag_S21 = S21.imag();
|
||||
d.real_S22 = S22.real();
|
||||
d.imag_S22 = S22.imag();
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
unsigned int Averaging::getLevel()
|
||||
{
|
||||
if(avg.size() > 0) {
|
||||
return avg.back().size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
23
Software/PC_Application/averaging.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef AVERAGING_H
|
||||
#define AVERAGING_H
|
||||
|
||||
|
||||
#include "Device/device.h"
|
||||
#include <deque>
|
||||
#include <complex>
|
||||
|
||||
class Averaging
|
||||
{
|
||||
public:
|
||||
Averaging();
|
||||
void reset();
|
||||
void setAverages(unsigned int a);
|
||||
Protocol::Datapoint process(Protocol::Datapoint d);
|
||||
unsigned int getLevel();
|
||||
private:
|
||||
std::vector<std::deque<std::array<std::complex<double>, 4>>> avg;
|
||||
int maxPoints;
|
||||
unsigned int averages;
|
||||
};
|
||||
|
||||
#endif // AVERAGING_H
|
26
Software/PC_Application/icons.qrc
Normal file
@ -0,0 +1,26 @@
|
||||
<RCC>
|
||||
<qresource prefix="/icons"/>
|
||||
<qresource prefix="/">
|
||||
<file>icons/add.png</file>
|
||||
<file>icons/delete.png</file>
|
||||
<file>icons/edit.png</file>
|
||||
<file>icons/invisible.svg</file>
|
||||
<file>icons/visible.svg</file>
|
||||
<file>icons/pause.svg</file>
|
||||
<file>icons/play.svg</file>
|
||||
<file>icons/plus.svg</file>
|
||||
<file>icons/export.svg</file>
|
||||
<file>icons/import.svg</file>
|
||||
<file>icons/close.svg</file>
|
||||
<file>icons/horizontal.svg</file>
|
||||
<file>icons/vertical.svg</file>
|
||||
<file>icons/pCsC_small.png</file>
|
||||
<file>icons/pCsL_small.png</file>
|
||||
<file>icons/pLsC_small.png</file>
|
||||
<file>icons/pLsL_small.png</file>
|
||||
<file>icons/sCpC_small.png</file>
|
||||
<file>icons/sCpL_small.png</file>
|
||||
<file>icons/sLpC_small.png</file>
|
||||
<file>icons/sLpL_small.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
2
Software/PC_Application/icons/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
tex
|
||||
|
BIN
Software/PC_Application/icons/add.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
46
Software/PC_Application/icons/close.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 492 492" style="enable-background:new 0 0 492 492;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M300.188,246L484.14,62.04c5.06-5.064,7.852-11.82,7.86-19.024c0-7.208-2.792-13.972-7.86-19.028L468.02,7.872
|
||||
c-5.068-5.076-11.824-7.856-19.036-7.856c-7.2,0-13.956,2.78-19.024,7.856L246.008,191.82L62.048,7.872
|
||||
c-5.06-5.076-11.82-7.856-19.028-7.856c-7.2,0-13.96,2.78-19.02,7.856L7.872,23.988c-10.496,10.496-10.496,27.568,0,38.052
|
||||
L191.828,246L7.872,429.952c-5.064,5.072-7.852,11.828-7.852,19.032c0,7.204,2.788,13.96,7.852,19.028l16.124,16.116
|
||||
c5.06,5.072,11.824,7.856,19.02,7.856c7.208,0,13.968-2.784,19.028-7.856l183.96-183.952l183.952,183.952
|
||||
c5.068,5.072,11.824,7.856,19.024,7.856h0.008c7.204,0,13.96-2.784,19.028-7.856l16.12-16.116
|
||||
c5.06-5.064,7.852-11.824,7.852-19.028c0-7.204-2.792-13.96-7.852-19.028L300.188,246z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Software/PC_Application/icons/delete.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
Software/PC_Application/icons/edit.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
50
Software/PC_Application/icons/export.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M352,288.994v127.008H64v-288h96v-64H32c-17.664,0-32,14.336-32,32v352c0,17.696,14.336,32,32,32h352
|
||||
c17.696,0,32-14.304,32-32V288.994H352z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M505.6,131.202l-128-96c-4.8-3.648-11.328-4.224-16.736-1.504c-5.44,2.72-8.864,8.256-8.864,14.304v48h-48
|
||||
c-97.056,0-176,78.944-176,176c0,7.424,5.12,13.888,12.32,15.584c1.216,0.288,2.464,0.416,3.68,0.416
|
||||
c5.952,0,11.552-3.328,14.304-8.832l3.776-7.52c24.544-49.12,73.888-79.648,128.8-79.648H352v48
|
||||
c0,6.048,3.424,11.584,8.832,14.304c5.408,2.72,11.936,2.144,16.768-1.504l128-96c4.032-3.008,6.4-7.776,6.4-12.8
|
||||
S509.632,134.21,505.6,131.202z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
50
Software/PC_Application/icons/horizontal.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 365.368 365.368" style="enable-background:new 0 0 365.368 365.368;" xml:space="preserve">
|
||||
<g>
|
||||
<path style="fill:#231F20;" d="M363.171,177.381L311.1,125.309c-1.406-1.407-3.314-2.197-5.303-2.197
|
||||
c-1.989,0-3.897,0.79-5.303,2.197l-14.143,14.143c-1.407,1.406-2.197,3.314-2.197,5.303c0,1.989,0.79,3.897,2.197,5.303
|
||||
l15.126,15.125h-52.543v-95.5c0-4.142-3.358-7.5-7.5-7.5h-20c-4.142,0-7.5,3.358-7.5,7.5v226c0,4.142,3.358,7.5,7.5,7.5h20
|
||||
c4.142,0,7.5-3.358,7.5-7.5v-95.5h52.543l-15.126,15.126c-1.407,1.406-2.197,3.314-2.197,5.303c0,1.989,0.79,3.897,2.197,5.303
|
||||
l14.143,14.143c1.406,1.407,3.314,2.197,5.303,2.197c1.989,0,3.897-0.79,5.303-2.197l52.071-52.071
|
||||
C366.1,185.058,366.1,180.309,363.171,177.381z"/>
|
||||
<path style="fill:#231F20;" d="M143.934,62.184h-20c-4.142,0-7.5,3.358-7.5,7.5v95.5H63.891l15.126-15.125
|
||||
c2.929-2.929,2.929-7.677,0-10.606l-14.142-14.143c-1.407-1.406-3.315-2.197-5.304-2.197c-1.989,0-3.897,0.79-5.303,2.197
|
||||
L2.197,177.381C0.79,178.787,0,180.695,0,182.684c0,1.989,0.79,3.897,2.197,5.303l52.072,52.071
|
||||
c1.407,1.407,3.314,2.197,5.303,2.197c1.989,0,3.897-0.79,5.304-2.197l14.142-14.143c2.929-2.929,2.928-7.678,0-10.606
|
||||
l-15.126-15.126h52.543v95.5c0,4.142,3.358,7.5,7.5,7.5h20c4.142,0,7.5-3.358,7.5-7.5v-226
|
||||
C151.434,65.542,148.076,62.184,143.934,62.184z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
50
Software/PC_Application/icons/import.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M287.52,224.48c-3.36-3.36-8-5.088-12.736-4.64l-124.448,11.296c-6.176,0.576-11.52,4.672-13.6,10.496
|
||||
c-2.112,5.856-0.672,12.384,3.712,16.768l33.952,33.952L4.704,462.048c-6.24,6.24-6.24,16.384,0,22.624l22.624,22.624
|
||||
c6.24,6.272,16.352,6.272,22.624,0L219.648,337.6l33.952,33.952c4.384,4.384,10.912,5.824,16.768,3.744
|
||||
c2.24-0.832,4.224-2.112,5.856-3.744c2.592-2.592,4.288-6.048,4.608-9.888l11.328-124.448
|
||||
C292.608,232.48,290.88,227.84,287.52,224.48z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M480,0H32C14.336,0,0,14.336,0,32v320h64V64h384v384H160v64h320c17.696,0,32-14.304,32-32V32C512,14.336,497.696,0,480,0z
|
||||
"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
Software/PC_Application/icons/invisible.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 166 132" height="132px" id="Layer_1" version="1.1" viewBox="0 0 166 132" width="166px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z" fill="#231F20"/><path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z" fill="#231F20"/></g></svg>
|
After Width: | Height: | Size: 953 B |
BIN
Software/PC_Application/icons/pCsC.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
Software/PC_Application/icons/pCsC_small.png
Normal file
After Width: | Height: | Size: 4.5 KiB |