Move eye diagram from tools to new graph type, enable zoom/pan on graphs
This commit is contained in:
parent
329f4487ee
commit
ee3c6274ad
@ -5,6 +5,7 @@
|
|||||||
#include "Traces/tracesmithchart.h"
|
#include "Traces/tracesmithchart.h"
|
||||||
#include "Traces/tracewaterfall.h"
|
#include "Traces/tracewaterfall.h"
|
||||||
#include "Traces/tracepolarchart.h"
|
#include "Traces/tracepolarchart.h"
|
||||||
|
#include "Traces/eyediagramplot.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
@ -76,6 +77,9 @@ nlohmann::json TileWidget::toJSON()
|
|||||||
case TracePlot::Type::PolarChart:
|
case TracePlot::Type::PolarChart:
|
||||||
plotname = "PolarChart";
|
plotname = "PolarChart";
|
||||||
break;
|
break;
|
||||||
|
case TracePlot::Type::EyeDiagram:
|
||||||
|
plotname = "EyeDiagram";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
j["plot"] = plotname;
|
j["plot"] = plotname;
|
||||||
j["plotsettings"] = content->toJSON();
|
j["plotsettings"] = content->toJSON();
|
||||||
@ -108,6 +112,8 @@ void TileWidget::fromJSON(nlohmann::json j)
|
|||||||
content = new TraceWaterfall(model);
|
content = new TraceWaterfall(model);
|
||||||
} else if (plotname == "PolarChart"){
|
} else if (plotname == "PolarChart"){
|
||||||
content = new TracePolarChart(model);
|
content = new TracePolarChart(model);
|
||||||
|
} else if (plotname == "EyeDiagram"){
|
||||||
|
content = new EyeDiagramPlot(model);
|
||||||
}
|
}
|
||||||
if(content) {
|
if(content) {
|
||||||
setContent(content);
|
setContent(content);
|
||||||
@ -337,3 +343,10 @@ void TileWidget::on_bPolarchart_clicked()
|
|||||||
{
|
{
|
||||||
setContent(new TracePolarChart(model));
|
setContent(new TracePolarChart(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TileWidget::on_eyeDiagram_clicked()
|
||||||
|
{
|
||||||
|
auto plot = new EyeDiagramPlot(model);
|
||||||
|
setContent(plot);
|
||||||
|
plot->axisSetupDialog();
|
||||||
|
}
|
||||||
|
@ -45,10 +45,11 @@ private slots:
|
|||||||
void on_bXYplot_clicked();
|
void on_bXYplot_clicked();
|
||||||
void on_plotDoubleClicked();
|
void on_plotDoubleClicked();
|
||||||
void plotDeleted();
|
void plotDeleted();
|
||||||
|
|
||||||
void on_bWaterfall_clicked();
|
void on_bWaterfall_clicked();
|
||||||
void on_bPolarchart_clicked();
|
void on_bPolarchart_clicked();
|
||||||
|
|
||||||
|
void on_eyeDiagram_clicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TileWidget(TraceModel &model, TileWidget &parent);
|
TileWidget(TraceModel &model, TileWidget &parent);
|
||||||
void split(bool moveContentToSecondChild = false);
|
void split(bool moveContentToSecondChild = false);
|
||||||
|
@ -99,6 +99,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="eyeDiagram">
|
||||||
|
<property name="text">
|
||||||
|
<string>Eye Diagram</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -33,7 +33,6 @@ HEADERS += \
|
|||||||
SpectrumAnalyzer/spectrumanalyzer.h \
|
SpectrumAnalyzer/spectrumanalyzer.h \
|
||||||
SpectrumAnalyzer/tracewidgetsa.h \
|
SpectrumAnalyzer/tracewidgetsa.h \
|
||||||
Tools/eseries.h \
|
Tools/eseries.h \
|
||||||
Tools/eyediagramdialog.h \
|
|
||||||
Tools/impedancematchdialog.h \
|
Tools/impedancematchdialog.h \
|
||||||
Tools/parameters.h \
|
Tools/parameters.h \
|
||||||
Traces/Marker/marker.h \
|
Traces/Marker/marker.h \
|
||||||
@ -95,6 +94,7 @@ HEADERS += \
|
|||||||
Traces/Math/timegate.h \
|
Traces/Math/timegate.h \
|
||||||
Traces/Math/tracemath.h \
|
Traces/Math/tracemath.h \
|
||||||
Traces/Math/windowfunction.h \
|
Traces/Math/windowfunction.h \
|
||||||
|
Traces/eyediagramplot.h \
|
||||||
Traces/fftcomplex.h \
|
Traces/fftcomplex.h \
|
||||||
Traces/sparamtraceselector.h \
|
Traces/sparamtraceselector.h \
|
||||||
Traces/trace.h \
|
Traces/trace.h \
|
||||||
@ -177,7 +177,6 @@ SOURCES += \
|
|||||||
SpectrumAnalyzer/spectrumanalyzer.cpp \
|
SpectrumAnalyzer/spectrumanalyzer.cpp \
|
||||||
SpectrumAnalyzer/tracewidgetsa.cpp \
|
SpectrumAnalyzer/tracewidgetsa.cpp \
|
||||||
Tools/eseries.cpp \
|
Tools/eseries.cpp \
|
||||||
Tools/eyediagramdialog.cpp \
|
|
||||||
Tools/impedancematchdialog.cpp \
|
Tools/impedancematchdialog.cpp \
|
||||||
Tools/parameters.cpp \
|
Tools/parameters.cpp \
|
||||||
Traces/Marker/marker.cpp \
|
Traces/Marker/marker.cpp \
|
||||||
@ -228,6 +227,7 @@ SOURCES += \
|
|||||||
Traces/Math/timegate.cpp \
|
Traces/Math/timegate.cpp \
|
||||||
Traces/Math/tracemath.cpp \
|
Traces/Math/tracemath.cpp \
|
||||||
Traces/Math/windowfunction.cpp \
|
Traces/Math/windowfunction.cpp \
|
||||||
|
Traces/eyediagramplot.cpp \
|
||||||
Traces/fftcomplex.cpp \
|
Traces/fftcomplex.cpp \
|
||||||
Traces/sparamtraceselector.cpp \
|
Traces/sparamtraceselector.cpp \
|
||||||
Traces/trace.cpp \
|
Traces/trace.cpp \
|
||||||
@ -305,7 +305,6 @@ FORMS += \
|
|||||||
Device/firmwareupdatedialog.ui \
|
Device/firmwareupdatedialog.ui \
|
||||||
Device/manualcontroldialog.ui \
|
Device/manualcontroldialog.ui \
|
||||||
Generator/signalgenwidget.ui \
|
Generator/signalgenwidget.ui \
|
||||||
Tools/eyediagramdialog.ui \
|
|
||||||
Tools/impedancematchdialog.ui \
|
Tools/impedancematchdialog.ui \
|
||||||
Traces/Marker/markerwidget.ui \
|
Traces/Marker/markerwidget.ui \
|
||||||
Traces/Math/dftdialog.ui \
|
Traces/Math/dftdialog.ui \
|
||||||
@ -321,6 +320,7 @@ FORMS += \
|
|||||||
Traces/Math/timegatedialog.ui \
|
Traces/Math/timegatedialog.ui \
|
||||||
Traces/Math/timegateexplanationwidget.ui \
|
Traces/Math/timegateexplanationwidget.ui \
|
||||||
Traces/XYPlotConstantLineEditDialog.ui \
|
Traces/XYPlotConstantLineEditDialog.ui \
|
||||||
|
Traces/eyediagrameditdialog.ui \
|
||||||
Traces/smithchartdialog.ui \
|
Traces/smithchartdialog.ui \
|
||||||
Traces/polarchartdialog.ui \
|
Traces/polarchartdialog.ui \
|
||||||
Traces/tracecsvexport.ui \
|
Traces/tracecsvexport.ui \
|
||||||
|
@ -1,656 +0,0 @@
|
|||||||
#include "eyediagramdialog.h"
|
|
||||||
#include "ui_eyediagramdialog.h"
|
|
||||||
#include "Util/prbs.h"
|
|
||||||
#include "Traces/Math/tdr.h"
|
|
||||||
#include "Util/util.h"
|
|
||||||
#include "preferences.h"
|
|
||||||
#include "Traces/fftcomplex.h"
|
|
||||||
#include "Traces/traceaxis.h"
|
|
||||||
#include "unit.h"
|
|
||||||
|
|
||||||
#include <random>
|
|
||||||
#include <thread>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
|
|
||||||
EyeDiagramDialog::EyeDiagramDialog(TraceModel &model) :
|
|
||||||
QDialog(nullptr),
|
|
||||||
ui(new Ui::EyeDiagramDialog)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
|
|
||||||
workingBuffer = &eyeBuffer[0];
|
|
||||||
finishedBuffer = &eyeBuffer[1];
|
|
||||||
|
|
||||||
updating = false;
|
|
||||||
firstUpdate = true;
|
|
||||||
|
|
||||||
trace = nullptr;
|
|
||||||
|
|
||||||
tdr = new Math::TDR();
|
|
||||||
|
|
||||||
ui->update->setEnabled(false);
|
|
||||||
|
|
||||||
ui->datarate->setUnit("bps");
|
|
||||||
ui->datarate->setPrefixes(" kMG");
|
|
||||||
ui->datarate->setPrecision(3);
|
|
||||||
|
|
||||||
ui->risetime->setUnit("s");
|
|
||||||
ui->risetime->setPrefixes("pnum ");
|
|
||||||
ui->risetime->setPrecision(3);
|
|
||||||
|
|
||||||
ui->falltime->setUnit("s");
|
|
||||||
ui->falltime->setPrefixes("pnum ");
|
|
||||||
ui->falltime->setPrecision(3);
|
|
||||||
|
|
||||||
ui->highLevel->setUnit("V");
|
|
||||||
ui->highLevel->setPrefixes("m ");
|
|
||||||
ui->highLevel->setPrecision(3);
|
|
||||||
|
|
||||||
ui->lowLevel->setUnit("V");
|
|
||||||
ui->lowLevel->setPrefixes("m ");
|
|
||||||
ui->lowLevel->setPrecision(3);
|
|
||||||
|
|
||||||
ui->noise->setUnit("V");
|
|
||||||
ui->noise->setPrefixes("um ");
|
|
||||||
ui->noise->setPrecision(3);
|
|
||||||
|
|
||||||
ui->jitter->setUnit("s");
|
|
||||||
ui->jitter->setPrefixes("pnum ");
|
|
||||||
ui->jitter->setPrecision(3);
|
|
||||||
|
|
||||||
ui->datarate->setValue(100000000);
|
|
||||||
ui->risetime->setValue(0.000000001);
|
|
||||||
ui->falltime->setValue(0.000000001);
|
|
||||||
ui->highLevel->setValue(1);
|
|
||||||
ui->lowLevel->setValue(0);
|
|
||||||
ui->noise->setValue(0.01);
|
|
||||||
ui->jitter->setValue(0.0000000001);
|
|
||||||
|
|
||||||
ui->displayedCycles->setValue(200);
|
|
||||||
|
|
||||||
ui->widget->setDialog(this);
|
|
||||||
|
|
||||||
connect(this, &EyeDiagramDialog::calculationStatus, ui->status, &QLabel::setText, Qt::QueuedConnection);
|
|
||||||
connect(ui->update, &QPushButton::clicked, this, &EyeDiagramDialog::triggerUpdate);
|
|
||||||
connect(this, &EyeDiagramDialog::updateDone, ui->widget, qOverload<>(&QWidget::update));
|
|
||||||
|
|
||||||
connect(ui->traceSelector, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
|
|
||||||
trace = qvariant_cast<Trace*>(ui->traceSelector->itemData(ui->traceSelector->currentIndex()));
|
|
||||||
tdr->assignInput(trace);
|
|
||||||
ui->update->setEnabled(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(tdr, &Math::TDR::outputSamplesChanged, [=](){
|
|
||||||
if(ui->updateOnTraceChange->isChecked() || firstUpdate) {
|
|
||||||
triggerUpdate();
|
|
||||||
firstUpdate = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// find applicable traces
|
|
||||||
for(auto t : model.getTraces()) {
|
|
||||||
if(t->getDataType() != Trace::DataType::Frequency) {
|
|
||||||
// wrong domain
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(t->isReflection()) {
|
|
||||||
// can't work with reflection measurements
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(t->numSamples() < 100) {
|
|
||||||
// not enough samples
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto start = t->getSample(0).x;
|
|
||||||
auto stop = t->getSample(t->numSamples() - 1).x;
|
|
||||||
if(stop - start < start * 100) {
|
|
||||||
// span/start is not suitable for step response TDR
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// can use this trace
|
|
||||||
ui->traceSelector->addItem(t->name(), QVariant::fromValue<Trace*>(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EyeDiagramDialog::~EyeDiagramDialog()
|
|
||||||
{
|
|
||||||
while(updating) {
|
|
||||||
std::this_thread::sleep_for(20ms);
|
|
||||||
}
|
|
||||||
delete tdr;
|
|
||||||
delete ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int EyeDiagramDialog::getCalculatedPixelsX()
|
|
||||||
{
|
|
||||||
return finishedBuffer->size();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int EyeDiagramDialog::getCalculatedPixelsY()
|
|
||||||
{
|
|
||||||
if(getCalculatedPixelsX() > 0) {
|
|
||||||
return (*finishedBuffer)[0].size();
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double EyeDiagramDialog::getIntensity(unsigned int x, unsigned int y)
|
|
||||||
{
|
|
||||||
if(finishedBuffer->size() > x) {
|
|
||||||
if((*finishedBuffer)[x].size() > y) {
|
|
||||||
return (*finishedBuffer)[x][y];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::numeric_limits<double>::quiet_NaN();
|
|
||||||
}
|
|
||||||
|
|
||||||
double EyeDiagramDialog::displayedTime()
|
|
||||||
{
|
|
||||||
return 2 * 1.0/ui->datarate->value();
|
|
||||||
}
|
|
||||||
|
|
||||||
double EyeDiagramDialog::minGraphVoltage()
|
|
||||||
{
|
|
||||||
auto highlevel = ui->highLevel->value();
|
|
||||||
auto lowlevel = ui->lowLevel->value();
|
|
||||||
auto eyeRange = highlevel - lowlevel;
|
|
||||||
return lowlevel - eyeRange * yOverrange;
|
|
||||||
}
|
|
||||||
|
|
||||||
double EyeDiagramDialog::maxGraphVoltage()
|
|
||||||
{
|
|
||||||
auto highlevel = ui->highLevel->value();
|
|
||||||
auto lowlevel = ui->lowLevel->value();
|
|
||||||
auto eyeRange = highlevel - lowlevel;
|
|
||||||
return highlevel + eyeRange * yOverrange;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EyeDiagramDialog::triggerUpdate()
|
|
||||||
{
|
|
||||||
update(ui->widget->eyeWidth(), ui->widget->eyeHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EyeDiagramDialog::update(unsigned int width, unsigned int height)
|
|
||||||
{
|
|
||||||
if(updating) {
|
|
||||||
// already updating, can't start again
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
updating = true;
|
|
||||||
new std::thread(&EyeDiagramDialog::updateThread, this, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EyeDiagramDialog::updateThread(unsigned int width, unsigned int height)
|
|
||||||
{
|
|
||||||
emit calculationStatus("Starting calculation...");
|
|
||||||
if(!trace) {
|
|
||||||
emit calculationStatus("No trace assigned");
|
|
||||||
updating = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Starting eye diagram calculation";
|
|
||||||
|
|
||||||
auto datarate = ui->datarate->value();
|
|
||||||
auto highlevel = ui->highLevel->value();
|
|
||||||
auto lowlevel = ui->lowLevel->value();
|
|
||||||
auto risetime = ui->risetime->value();
|
|
||||||
auto falltime = ui->falltime->value();
|
|
||||||
auto noise = ui->noise->value();
|
|
||||||
auto jitter = ui->jitter->value();
|
|
||||||
bool linearEdge = ui->fallrisetype->currentIndex() == 0;
|
|
||||||
unsigned int patternbits = ui->patternLength->currentIndex() + 2;
|
|
||||||
unsigned int cycles = ui->displayedCycles->value() + 1; // first cycle will not be displayed
|
|
||||||
|
|
||||||
// sanity check values
|
|
||||||
if(datarate >= trace->getSample(trace->numSamples() - 1).x) {
|
|
||||||
emit calculationStatus("Data rate too high");
|
|
||||||
updating = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(datarate <= 0) {
|
|
||||||
emit calculationStatus("Data rate too low");
|
|
||||||
updating = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(risetime > 0.3 * 1.0 / datarate) {
|
|
||||||
emit calculationStatus("Rise time too high");
|
|
||||||
updating = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(falltime > 0.3 * 1.0 / datarate) {
|
|
||||||
emit calculationStatus("Fall time too high");
|
|
||||||
updating = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(jitter > 0.3 * 1.0 / datarate) {
|
|
||||||
emit calculationStatus("Jitter too high");
|
|
||||||
updating = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Eye calculation: input values okay";
|
|
||||||
|
|
||||||
// resize working buffer
|
|
||||||
workingBuffer->clear();
|
|
||||||
workingBuffer->resize(width);
|
|
||||||
for(auto& y : *workingBuffer) {
|
|
||||||
y.resize(height, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit calculationStatus("Generating PRBS sequence...");
|
|
||||||
|
|
||||||
// calculate timestep
|
|
||||||
double timestep = displayedTime() / (width);
|
|
||||||
|
|
||||||
auto prbs = new PRBS(patternbits);
|
|
||||||
|
|
||||||
bool currentBit = prbs->next();
|
|
||||||
bool nextBit = prbs->next();
|
|
||||||
|
|
||||||
// initialize random generator
|
|
||||||
std::random_device rd1;
|
|
||||||
std::mt19937 mt_noise(rd1());
|
|
||||||
std::normal_distribution<> dist_noise(0, noise);
|
|
||||||
|
|
||||||
std::random_device rd2;
|
|
||||||
std::mt19937 mt_jitter(rd2());
|
|
||||||
std::normal_distribution<> dist_jitter(0, jitter);
|
|
||||||
|
|
||||||
// reserve vector for input data
|
|
||||||
std::vector<std::complex<double>> inVec(width * cycles, 0.0);
|
|
||||||
|
|
||||||
unsigned int bitcnt = 1;
|
|
||||||
double transitionTime = -10; // assume that we start with a settled input, last transition was "long" ago
|
|
||||||
for(unsigned int i=0;i<inVec.size();i++) {
|
|
||||||
double time = i*timestep;
|
|
||||||
double voltage;
|
|
||||||
if(time >= transitionTime) {
|
|
||||||
// currently within a bit transition
|
|
||||||
double edgeTime = 0;
|
|
||||||
double expTimeConstant;
|
|
||||||
if(!currentBit && nextBit) {
|
|
||||||
edgeTime = risetime;
|
|
||||||
} else if(currentBit && !nextBit) {
|
|
||||||
edgeTime = falltime;
|
|
||||||
}
|
|
||||||
if(linearEdge) {
|
|
||||||
// edge is modeled as linear rise/fall
|
|
||||||
// increase slightly to account for typical 10/90% fall/rise time
|
|
||||||
edgeTime *= 1.25;
|
|
||||||
} else {
|
|
||||||
// edge is modeled as exponential rise/fall. Adjust time constant to match
|
|
||||||
// selected rise/fall time (with 10-90% signal rise/fall within specified time)
|
|
||||||
expTimeConstant = edgeTime / 2.197224577;
|
|
||||||
edgeTime = 6 * expTimeConstant; // after six time constants, 99.7% of signal movement has happened
|
|
||||||
}
|
|
||||||
if(time >= transitionTime + edgeTime) {
|
|
||||||
// bit transition settled
|
|
||||||
voltage = nextBit ? highlevel : lowlevel;
|
|
||||||
// move on to the next bit
|
|
||||||
currentBit = nextBit;
|
|
||||||
nextBit = prbs->next();
|
|
||||||
transitionTime = bitcnt * 1.0 / datarate + dist_jitter(mt_jitter);
|
|
||||||
bitcnt++;
|
|
||||||
} else {
|
|
||||||
// still within rise or fall time
|
|
||||||
double timeSinceEdge = time - transitionTime;
|
|
||||||
double from = currentBit ? highlevel : lowlevel;
|
|
||||||
double to = nextBit ? highlevel : lowlevel;
|
|
||||||
if(linearEdge) {
|
|
||||||
double edgeRatio = timeSinceEdge / edgeTime;
|
|
||||||
voltage = from * (1.0 - edgeRatio) + to * edgeRatio;
|
|
||||||
} else {
|
|
||||||
voltage = from + (1.0 - exp(-timeSinceEdge/expTimeConstant)) * (to - from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// still before the next edge
|
|
||||||
voltage = currentBit ? highlevel : lowlevel;
|
|
||||||
}
|
|
||||||
voltage += dist_noise(mt_noise);
|
|
||||||
inVec[i] = voltage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// input voltage vector fully assembled
|
|
||||||
qDebug() << "Eye calculation: input data generated";
|
|
||||||
|
|
||||||
emit calculationStatus("Extracting impulse response...");
|
|
||||||
|
|
||||||
// calculate impulse response of trace
|
|
||||||
double eyeTimeShift = 0;
|
|
||||||
std::vector<std::complex<double>> impulseVec;
|
|
||||||
// determine how long the impulse response is
|
|
||||||
auto samples = tdr->numSamples();
|
|
||||||
if(samples == 0) {
|
|
||||||
// TDR calculation not yet done, unable to update
|
|
||||||
updating = false;
|
|
||||||
emit calculationStatus("No time-domain data from trace");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto length = tdr->getSample(samples - 1).x;
|
|
||||||
|
|
||||||
// determine average delay
|
|
||||||
auto total_step = tdr->getStepResponse(samples - 1);
|
|
||||||
for(unsigned int i=0;i<samples;i++) {
|
|
||||||
auto step = tdr->getStepResponse(i);
|
|
||||||
if(abs(total_step - step) <= abs(step)) {
|
|
||||||
// mid point reached
|
|
||||||
eyeTimeShift = tdr->getSample(i).x;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long convolutedSize = length / timestep;
|
|
||||||
if(convolutedSize > inVec.size()) {
|
|
||||||
// impulse response is longer than what we display, truncate
|
|
||||||
convolutedSize = inVec.size();
|
|
||||||
}
|
|
||||||
impulseVec.resize(convolutedSize);
|
|
||||||
/*
|
|
||||||
* we can't use the impulse response directly because we most likely need samples inbetween
|
|
||||||
* the calculated values. Interpolation is available but if our sample spacing here is much
|
|
||||||
* wider than the impulse response data, we might miss peaks (or severely miscalculate their
|
|
||||||
* amplitude.
|
|
||||||
* Instead, the step response is interpolated and the impulse response determined by deriving
|
|
||||||
* it from the interpolated step response data. As the step response is the integrated imulse
|
|
||||||
* response data, we can't miss narrow peaks that way.
|
|
||||||
*/
|
|
||||||
double lastStepResponse = 0.0;
|
|
||||||
for(unsigned long i=0;i<convolutedSize;i++) {
|
|
||||||
auto x = i*timestep;
|
|
||||||
auto step = tdr->getInterpolatedStepResponse(x);
|
|
||||||
impulseVec[i] = step - lastStepResponse;
|
|
||||||
lastStepResponse = step;
|
|
||||||
}
|
|
||||||
|
|
||||||
eyeTimeShift += (risetime + falltime) * 1.25 / 4;
|
|
||||||
eyeTimeShift += 0.5 / datarate;
|
|
||||||
int eyeXshift = eyeTimeShift / timestep;
|
|
||||||
|
|
||||||
qDebug() << "Eye calculation: TDR calculation done";
|
|
||||||
|
|
||||||
emit calculationStatus("Performing convolution...");
|
|
||||||
|
|
||||||
unsigned int highestIntensity = 0;
|
|
||||||
|
|
||||||
qDebug() << "Convolve via FFT start";
|
|
||||||
std::vector<std::complex<double>> outVec;
|
|
||||||
impulseVec.resize(inVec.size(), 0.0);
|
|
||||||
outVec.resize(inVec.size());
|
|
||||||
Fft::convolve(inVec, impulseVec, outVec);
|
|
||||||
qDebug() << "Convolve via FFT stop";
|
|
||||||
|
|
||||||
auto addLine = [&](int x0, int y0, int x1, int y1, bool skipFirst = true) {
|
|
||||||
bool first = true;
|
|
||||||
auto putpixel = [&](int x, int y) {
|
|
||||||
if(skipFirst && first) {
|
|
||||||
first = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(x < 0 || x >= width || y < 0 || y >= height) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto &bin = (*workingBuffer)[x][y];
|
|
||||||
bin++;
|
|
||||||
if(bin > highestIntensity) {
|
|
||||||
highestIntensity = bin;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
|
|
||||||
int dy = -abs (y1 - y0), sy = y0 < y1 ? 1 : -1;
|
|
||||||
int err = dx + dy, e2; /* error value e_xy */
|
|
||||||
|
|
||||||
for (;;){ /* loop */
|
|
||||||
putpixel (x0,y0);
|
|
||||||
if (x0 == x1 && y0 == y1) break;
|
|
||||||
e2 = 2 * err;
|
|
||||||
if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
|
|
||||||
if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
emit calculationStatus("Creating intensity bitmap...");
|
|
||||||
|
|
||||||
// got the input data and the convolution data, calculate output
|
|
||||||
int lastyBin;
|
|
||||||
for(unsigned int i=width;i<inVec.size();i++) {
|
|
||||||
double voltage = outVec[i].real();
|
|
||||||
int yBin = Util::Scale<double>(voltage, minGraphVoltage(), maxGraphVoltage(), height-1, 0);
|
|
||||||
// increment pixel bin
|
|
||||||
if(yBin < 0) {
|
|
||||||
yBin = 0;
|
|
||||||
} else if(yBin >= height) {
|
|
||||||
yBin = height - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto xlast = (i-1-eyeXshift)%width;
|
|
||||||
auto xnow = (i-eyeXshift)%width;
|
|
||||||
if(xnow > xlast && i > width) {
|
|
||||||
addLine(xlast, lastyBin, xnow, yBin, xlast > 0);
|
|
||||||
}
|
|
||||||
lastyBin = yBin;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Eye calculation: Convolution done";
|
|
||||||
|
|
||||||
// normalize intensity
|
|
||||||
for(auto &y : *workingBuffer) {
|
|
||||||
for(auto &v : y) {
|
|
||||||
v /= highestIntensity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// switch buffers
|
|
||||||
auto buf = finishedBuffer;
|
|
||||||
finishedBuffer = workingBuffer;
|
|
||||||
workingBuffer = buf;
|
|
||||||
updating = false;
|
|
||||||
|
|
||||||
emit calculationStatus("Eye calculation complete");
|
|
||||||
emit updateDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
EyeDiagramPlot::EyeDiagramPlot(QDialog *dialog)
|
|
||||||
{
|
|
||||||
Q_UNUSED(dialog)
|
|
||||||
}
|
|
||||||
|
|
||||||
void EyeDiagramPlot::setDialog(EyeDiagramDialog *dialog)
|
|
||||||
{
|
|
||||||
this->dialog = dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int EyeDiagramPlot::eyeWidth()
|
|
||||||
{
|
|
||||||
return width() - leftSpace() - rightSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int EyeDiagramPlot::eyeHeight()
|
|
||||||
{
|
|
||||||
return height() - topSpace() - bottomSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int EyeDiagramPlot::leftSpace()
|
|
||||||
{
|
|
||||||
auto &pref = Preferences::getInstance();
|
|
||||||
return pref.Graphs.fontSizeAxis * 5.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int EyeDiagramPlot::bottomSpace()
|
|
||||||
{
|
|
||||||
auto &pref = Preferences::getInstance();
|
|
||||||
return pref.Graphs.fontSizeAxis * 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EyeDiagramPlot::paintEvent(QPaintEvent *event)
|
|
||||||
{
|
|
||||||
if(!dialog) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &pref = Preferences::getInstance();
|
|
||||||
int plotAreaLeft = leftSpace();
|
|
||||||
int plotAreaWidth = width() - leftSpace() - rightSpace();
|
|
||||||
int plotAreaTop = topSpace();
|
|
||||||
int plotAreaHeight = height() - topSpace() - bottomSpace();
|
|
||||||
|
|
||||||
QPainter p(this);
|
|
||||||
p.setBackground(QBrush(pref.Graphs.Color.background));
|
|
||||||
p.fillRect(0, 0, width(), height(), QBrush(pref.Graphs.Color.background));
|
|
||||||
|
|
||||||
auto pen = QPen(pref.Graphs.Color.axis, 0);
|
|
||||||
pen.setCosmetic(true);
|
|
||||||
p.setPen(pen);
|
|
||||||
auto plotRect = QRect(plotAreaLeft, plotAreaTop, plotAreaWidth + 1, plotAreaHeight + 1);
|
|
||||||
p.drawRect(plotRect);
|
|
||||||
|
|
||||||
// Y axis
|
|
||||||
QString labelY = "Voltage";
|
|
||||||
auto font = p.font();
|
|
||||||
font.setPixelSize(pref.Graphs.fontSizeAxis);
|
|
||||||
p.setFont(font);
|
|
||||||
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
|
||||||
p.save();
|
|
||||||
p.translate(0, height()-bottomSpace());
|
|
||||||
p.rotate(-90);
|
|
||||||
p.drawText(QRect(0, 0, height()-bottomSpace(), pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, labelY);
|
|
||||||
p.restore();
|
|
||||||
|
|
||||||
XAxis axis;
|
|
||||||
axis.set(XAxis::Type::Time, false, true, dialog->minGraphVoltage(), dialog->maxGraphVoltage(), 10);
|
|
||||||
// draw ticks
|
|
||||||
if(axis.getTicks().size() > 0) {
|
|
||||||
// this only works for evenly distributed ticks:
|
|
||||||
auto max = qMax(abs(axis.getTicks().front()), abs(axis.getTicks().back()));
|
|
||||||
double step;
|
|
||||||
if(axis.getTicks().size() >= 2) {
|
|
||||||
step = abs(axis.getTicks()[0] - axis.getTicks()[1]);
|
|
||||||
} else {
|
|
||||||
// only one tick, set arbitrary number of digits
|
|
||||||
step = max / 1000;
|
|
||||||
}
|
|
||||||
int significantDigits = floor(log10(max)) - floor(log10(step)) + 1;
|
|
||||||
|
|
||||||
for(unsigned int j = 0; j < axis.getTicks().size(); j++) {
|
|
||||||
auto yCoord = axis.transform(axis.getTicks()[j], plotAreaTop + plotAreaHeight, plotAreaTop);
|
|
||||||
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
|
||||||
// draw tickmark on axis
|
|
||||||
auto tickStart = plotAreaLeft;
|
|
||||||
auto tickLen = -2;
|
|
||||||
p.drawLine(tickStart, yCoord, tickStart + tickLen, yCoord);
|
|
||||||
QString unit = "";
|
|
||||||
QString prefix = " ";
|
|
||||||
if(pref.Graphs.showUnits) {
|
|
||||||
unit = "V";
|
|
||||||
prefix = "um ";
|
|
||||||
}
|
|
||||||
auto tickValue = Unit::ToString(axis.getTicks()[j], unit, prefix, significantDigits);
|
|
||||||
p.drawText(QRectF(0, yCoord - pref.Graphs.fontSizeAxis/2 - 2, tickStart + 2 * tickLen, pref.Graphs.fontSizeAxis), Qt::AlignRight, tickValue);
|
|
||||||
|
|
||||||
// tick lines
|
|
||||||
if(yCoord == plotAreaTop || yCoord == plotAreaTop + plotAreaHeight) {
|
|
||||||
// skip tick lines right on the plot borders
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// only draw tick lines for primary axis
|
|
||||||
if (pref.Graphs.Color.Ticks.Background.enabled) {
|
|
||||||
if (j%2)
|
|
||||||
{
|
|
||||||
int yCoordTop = axis.transform(axis.getTicks()[j], plotAreaTop, plotAreaTop + plotAreaHeight);
|
|
||||||
int yCoordBot = axis.transform(axis.getTicks()[j-1], plotAreaTop, plotAreaTop + plotAreaHeight);
|
|
||||||
if(yCoordTop > yCoordBot) {
|
|
||||||
auto buf = yCoordBot;
|
|
||||||
yCoordBot = yCoordTop;
|
|
||||||
yCoordTop = buf;
|
|
||||||
}
|
|
||||||
p.setBrush(pref.Graphs.Color.Ticks.Background.background);
|
|
||||||
p.setPen(pref.Graphs.Color.Ticks.Background.background);
|
|
||||||
auto rect = QRect(plotAreaLeft+1, yCoordTop+1, plotAreaWidth-2, yCoordBot-yCoordTop-2);
|
|
||||||
p.drawRect(rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine));
|
|
||||||
p.drawLine(plotAreaLeft, yCoord, plotAreaLeft + plotAreaWidth, yCoord);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the XY-plot axes for tick calculation
|
|
||||||
axis.set(XAxis::Type::Time, false, true, 0, dialog->displayedTime(), 10);
|
|
||||||
|
|
||||||
// X axis name
|
|
||||||
p.drawText(QRect(plotAreaLeft, height()-pref.Graphs.fontSizeAxis*1.5, plotAreaWidth, pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, axis.TypeToName());
|
|
||||||
|
|
||||||
// draw X axis ticks
|
|
||||||
if(axis.getTicks().size() >= 1) {
|
|
||||||
// draw X ticks
|
|
||||||
int significantDigits;
|
|
||||||
// this only works for evenly distributed ticks:
|
|
||||||
auto max = qMax(abs(axis.getTicks().front()), abs(axis.getTicks().back()));
|
|
||||||
double step;
|
|
||||||
if(axis.getTicks().size() >= 2) {
|
|
||||||
step = abs(axis.getTicks()[0] - axis.getTicks()[1]);
|
|
||||||
} else {
|
|
||||||
// only one tick, set arbitrary number of digits
|
|
||||||
step = max / 1000;
|
|
||||||
}
|
|
||||||
significantDigits = floor(log10(max)) - floor(log10(step)) + 1;
|
|
||||||
QString prefixes = "fpnum kMG";
|
|
||||||
QString unit = "";
|
|
||||||
if(pref.Graphs.showUnits) {
|
|
||||||
unit = axis.Unit();
|
|
||||||
}
|
|
||||||
QString commonPrefix = QString();
|
|
||||||
int lastTickLabelEnd = 0;
|
|
||||||
for(auto t : axis.getTicks()) {
|
|
||||||
auto xCoord = axis.transform(t, plotAreaLeft, plotAreaLeft + plotAreaWidth);
|
|
||||||
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
|
||||||
p.drawLine(xCoord, plotAreaTop + plotAreaHeight, xCoord, plotAreaTop + plotAreaHeight + 2);
|
|
||||||
if(xCoord != plotAreaLeft && xCoord != plotAreaLeft + plotAreaWidth) {
|
|
||||||
p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine));
|
|
||||||
p.drawLine(xCoord, plotAreaTop, xCoord, plotAreaTop + plotAreaHeight);
|
|
||||||
}
|
|
||||||
if(xCoord - 40 <= lastTickLabelEnd) {
|
|
||||||
// would overlap previous tick label, skip
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto tickValue = Unit::ToString(t, unit, prefixes, significantDigits);
|
|
||||||
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
|
||||||
QRect bounding;
|
|
||||||
p.drawText(QRect(xCoord - pref.Graphs.fontSizeAxis*2, plotAreaTop + plotAreaHeight + 5, pref.Graphs.fontSizeAxis*4,
|
|
||||||
pref.Graphs.fontSizeAxis), Qt::AlignHCenter, tickValue, &bounding);
|
|
||||||
lastTickLabelEnd = bounding.x() + bounding.width();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dialog->getCalculatedPixelsX() == 0 || dialog->getCalculatedPixelsY() == 0) {
|
|
||||||
// no eye data
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eye data is normally calculated to match the displayed pixels in this widget.
|
|
||||||
// But the window size mighe have been adjusted since the last eye calculation.
|
|
||||||
// Use scale factors until the eye data is updated
|
|
||||||
double xScale = (double) plotAreaWidth / dialog->getCalculatedPixelsX();
|
|
||||||
double yScale = (double) plotAreaHeight / dialog->getCalculatedPixelsY();
|
|
||||||
|
|
||||||
for(unsigned int i=0;i<plotAreaWidth;i++) {
|
|
||||||
for(unsigned int j=0;j<plotAreaHeight;j++) {
|
|
||||||
auto value = dialog->getIntensity(i / xScale, j / yScale);
|
|
||||||
if(isnan(value) || value == 0) {
|
|
||||||
// do not paint, just leave the background shining through
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto pen = QPen(Util::getIntensityGradeColor(value));
|
|
||||||
pen.setCosmetic(true);
|
|
||||||
p.setPen(pen);
|
|
||||||
p.drawPoint(plotAreaLeft + i + 1, plotAreaTop + j + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
#ifndef EYEDIAGRAMDIALOG_H
|
|
||||||
#define EYEDIAGRAMDIALOG_H
|
|
||||||
|
|
||||||
#include "Traces/tracemodel.h"
|
|
||||||
#include "Traces/Math/tdr.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class EyeDiagramDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EyeDiagramDialog;
|
|
||||||
|
|
||||||
class EyeDiagramPlot : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
EyeDiagramPlot(QDialog *dialog);
|
|
||||||
|
|
||||||
void setDialog(EyeDiagramDialog *dialog);
|
|
||||||
|
|
||||||
unsigned int eyeWidth();
|
|
||||||
unsigned int eyeHeight();
|
|
||||||
private:
|
|
||||||
unsigned int leftSpace();
|
|
||||||
unsigned int rightSpace() {return 10;}
|
|
||||||
unsigned int topSpace() {return 10;}
|
|
||||||
unsigned int bottomSpace();
|
|
||||||
void paintEvent(QPaintEvent *event) override;
|
|
||||||
|
|
||||||
EyeDiagramDialog *dialog;
|
|
||||||
};
|
|
||||||
|
|
||||||
class EyeDiagramDialog : public QDialog
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit EyeDiagramDialog(TraceModel &model);
|
|
||||||
~EyeDiagramDialog();
|
|
||||||
|
|
||||||
unsigned int getCalculatedPixelsX();
|
|
||||||
unsigned int getCalculatedPixelsY();
|
|
||||||
double getIntensity(unsigned int x, unsigned int y);
|
|
||||||
|
|
||||||
double displayedTime();
|
|
||||||
double minGraphVoltage();
|
|
||||||
double maxGraphVoltage();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
bool triggerUpdate();
|
|
||||||
bool update(unsigned int width, unsigned int height);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void updateDone();
|
|
||||||
|
|
||||||
private:
|
|
||||||
signals:
|
|
||||||
void calculationStatus(QString s);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr double yOverrange = 0.2;
|
|
||||||
void updateThread(unsigned int width, unsigned int height);
|
|
||||||
|
|
||||||
Ui::EyeDiagramDialog *ui;
|
|
||||||
|
|
||||||
Trace *trace;
|
|
||||||
|
|
||||||
std::vector<std::vector<double>> eyeBuffer[2];
|
|
||||||
std::vector<std::vector<double>> *workingBuffer;
|
|
||||||
std::vector<std::vector<double>> *finishedBuffer;
|
|
||||||
|
|
||||||
Math::TDR *tdr;
|
|
||||||
|
|
||||||
bool updating;
|
|
||||||
bool firstUpdate;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // EYEDIAGRAMDIALOG_H
|
|
@ -1,285 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>EyeDiagramDialog</class>
|
|
||||||
<widget class="QDialog" name="EyeDiagramDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>897</width>
|
|
||||||
<height>575</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Eye Diagram</string>
|
|
||||||
</property>
|
|
||||||
<property name="modal">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox_3">
|
|
||||||
<property name="title">
|
|
||||||
<string>Trace Selection</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QFormLayout" name="formLayout_3">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_11">
|
|
||||||
<property name="text">
|
|
||||||
<string>Transmission line trace:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QComboBox" name="traceSelector"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox">
|
|
||||||
<property name="title">
|
|
||||||
<string>Input Datastream Configuration</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QFormLayout" name="formLayout">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string>Data rate:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="SIUnitEdit" name="datarate"/>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Rise time:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="SIUnitEdit" name="risetime"/>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Fall time:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="SIUnitEdit" name="falltime"/>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QLabel" name="label_4">
|
|
||||||
<property name="text">
|
|
||||||
<string>High level:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="1">
|
|
||||||
<widget class="SIUnitEdit" name="highLevel"/>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="0">
|
|
||||||
<widget class="QLabel" name="label_5">
|
|
||||||
<property name="text">
|
|
||||||
<string>Low level:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="1">
|
|
||||||
<widget class="SIUnitEdit" name="lowLevel"/>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="0">
|
|
||||||
<widget class="QLabel" name="label_6">
|
|
||||||
<property name="text">
|
|
||||||
<string>Noise (RMS):</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="1">
|
|
||||||
<widget class="SIUnitEdit" name="noise"/>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="0">
|
|
||||||
<widget class="QLabel" name="label_7">
|
|
||||||
<property name="text">
|
|
||||||
<string>Jitter (RMS):</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="1">
|
|
||||||
<widget class="SIUnitEdit" name="jitter"/>
|
|
||||||
</item>
|
|
||||||
<item row="8" column="0">
|
|
||||||
<widget class="QLabel" name="label_8">
|
|
||||||
<property name="text">
|
|
||||||
<string>Pattern length:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="8" column="1">
|
|
||||||
<widget class="QComboBox" name="patternLength">
|
|
||||||
<property name="currentIndex">
|
|
||||||
<number>7</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>3</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>7</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>15</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>31</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>63</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>127</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>255</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>511</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>1023</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>2047</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="label_12">
|
|
||||||
<property name="text">
|
|
||||||
<string>Rise/Fall type:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QComboBox" name="fallrisetype">
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Linear</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Exponential</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
|
||||||
<property name="title">
|
|
||||||
<string>Eye Calculation</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<layout class="QFormLayout" name="formLayout_2">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_9">
|
|
||||||
<property name="text">
|
|
||||||
<string>Displayed cycles:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QSpinBox" name="displayedCycles">
|
|
||||||
<property name="minimum">
|
|
||||||
<number>1</number>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>1000</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_10">
|
|
||||||
<property name="text">
|
|
||||||
<string>Update when trace changes:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QCheckBox" name="updateOnTraceChange">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="update">
|
|
||||||
<property name="text">
|
|
||||||
<string>Update</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="status">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="EyeDiagramPlot" name="widget" native="true"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<customwidgets>
|
|
||||||
<customwidget>
|
|
||||||
<class>SIUnitEdit</class>
|
|
||||||
<extends>QLineEdit</extends>
|
|
||||||
<header>CustomWidgets/siunitedit.h</header>
|
|
||||||
</customwidget>
|
|
||||||
<customwidget>
|
|
||||||
<class>EyeDiagramPlot</class>
|
|
||||||
<extends>QWidget</extends>
|
|
||||||
<header>Tools/eyediagramdialog.h</header>
|
|
||||||
<container>1</container>
|
|
||||||
</customwidget>
|
|
||||||
</customwidgets>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
@ -121,7 +121,7 @@ double TraceMath::getInterpolatedStepResponse(double x)
|
|||||||
auto it = lower_bound(data.begin(), data.end(), x, [](const Data &lhs, const double x) -> bool {
|
auto it = lower_bound(data.begin(), data.end(), x, [](const Data &lhs, const double x) -> bool {
|
||||||
return lhs.x < x;
|
return lhs.x < x;
|
||||||
});
|
});
|
||||||
if(it->x == x) {
|
if(it->x == x || it == data.begin()) {
|
||||||
ret = stepResponse[it - data.begin()];
|
ret = stepResponse[it - data.begin()];
|
||||||
} else {
|
} else {
|
||||||
// no exact match, needs to interpolate
|
// no exact match, needs to interpolate
|
||||||
|
@ -0,0 +1,482 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>EyeDiagramEditDialog</class>
|
||||||
|
<widget class="QDialog" name="EyeDiagramEditDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>844</width>
|
||||||
|
<height>429</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Eye Diagram Settings</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
|
<property name="title">
|
||||||
|
<string>Axes Configuration</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_4">
|
||||||
|
<property name="title">
|
||||||
|
<string>X Axis</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout_3">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_19">
|
||||||
|
<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_20">
|
||||||
|
<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_21">
|
||||||
|
<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_22">
|
||||||
|
<property name="text">
|
||||||
|
<string>Divisions:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="Xdivs"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_5">
|
||||||
|
<property name="title">
|
||||||
|
<string>Y Axis</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout_5">
|
||||||
|
<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="Yauto">
|
||||||
|
<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="Ymax"/>
|
||||||
|
</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="Ymin"/>
|
||||||
|
</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="Ydivs"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_3">
|
||||||
|
<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>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Input Datastream Configuration</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Data rate:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="datarate"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Rise time:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="risetime"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fall time:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="falltime"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>High level:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="highLevel"/>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Low level:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="lowLevel"/>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Noise (RMS):</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="noise"/>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Jitter (RMS):</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="jitter"/>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Pattern length:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="1">
|
||||||
|
<widget class="QComboBox" name="patternLength">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>7</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>3</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>7</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>15</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>31</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>63</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>127</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>255</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>511</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1023</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>2047</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_12">
|
||||||
|
<property name="text">
|
||||||
|
<string>Rise/Fall type:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QComboBox" name="fallrisetype">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Linear</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Exponential</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="text">
|
||||||
|
<string>Signal levels:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QComboBox" name="signalLevels">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>2</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>4</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>8</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>16</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</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>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>Eye Calculation</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>Displayed cycles:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QSpinBox" name="displayedCycles">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>1000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_13">
|
||||||
|
<property name="text">
|
||||||
|
<string>Points per cycle:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QSpinBox" name="pointsPerCycle">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>1000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</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>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Apply|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>EyeDiagramEditDialog</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>EyeDiagramEditDialog</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>
|
938
Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp
Normal file
938
Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp
Normal file
@ -0,0 +1,938 @@
|
|||||||
|
#include "eyediagramplot.h"
|
||||||
|
|
||||||
|
#include "ui_eyediagrameditdialog.h"
|
||||||
|
#include "unit.h"
|
||||||
|
#include "Util/prbs.h"
|
||||||
|
#include "Util/util.h"
|
||||||
|
#include "fftcomplex.h"
|
||||||
|
#include "preferences.h"
|
||||||
|
#include "appwindow.h"
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
EyeDiagramPlot::EyeDiagramPlot(TraceModel &model, QWidget *parent)
|
||||||
|
: TracePlot(model, parent),
|
||||||
|
trace(nullptr),
|
||||||
|
updating(false),
|
||||||
|
updateScheduled(false),
|
||||||
|
xSamples(200),
|
||||||
|
datarate(100000000),
|
||||||
|
highlevel(1.0),
|
||||||
|
lowlevel(0.0),
|
||||||
|
bitsPerSymbol(1),
|
||||||
|
risetime(0.000000001),
|
||||||
|
falltime(0.000000001),
|
||||||
|
noise(0.01),
|
||||||
|
jitter(0.0000000001),
|
||||||
|
linearEdge(true),
|
||||||
|
patternbits(9),
|
||||||
|
cycles(200)
|
||||||
|
{
|
||||||
|
plotAreaTop = 0;
|
||||||
|
plotAreaLeft = 0;
|
||||||
|
plotAreaWidth = 0;
|
||||||
|
plotAreaBottom = 0;
|
||||||
|
|
||||||
|
tdr = new Math::TDR;
|
||||||
|
|
||||||
|
calcData = &data[0];
|
||||||
|
displayData = &data[1];
|
||||||
|
|
||||||
|
xAxis.set(XAxis::Type::Time, false, true, 0, 0.000001, 1);
|
||||||
|
yAxis.set(YAxis::Type::Real, false, true, -1, 1, 1);
|
||||||
|
initializeTraceInfo();
|
||||||
|
|
||||||
|
connect(tdr, &Math::TDR::outputSamplesChanged, this, &EyeDiagramPlot::triggerUpdate);
|
||||||
|
|
||||||
|
replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
EyeDiagramPlot::~EyeDiagramPlot()
|
||||||
|
{
|
||||||
|
while(updating) {
|
||||||
|
std::this_thread::sleep_for(20ms);
|
||||||
|
}
|
||||||
|
delete tdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::enableTrace(Trace *t, bool enabled)
|
||||||
|
{
|
||||||
|
if(enabled) {
|
||||||
|
// only one trace at a time is allowed, disable all others
|
||||||
|
for(auto t : traces) {
|
||||||
|
if(t.second) {
|
||||||
|
enableTrace(t.first, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TracePlot::enableTrace(t, enabled);
|
||||||
|
if(enabled) {
|
||||||
|
trace = t;
|
||||||
|
tdr->assignInput(trace);
|
||||||
|
} else {
|
||||||
|
if(trace) {
|
||||||
|
tdr->removeInput();
|
||||||
|
while(updating) {
|
||||||
|
std::this_thread::sleep_for(20ms);
|
||||||
|
}
|
||||||
|
displayData->clear();
|
||||||
|
calcData->clear();
|
||||||
|
}
|
||||||
|
trace = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::replot()
|
||||||
|
{
|
||||||
|
if(xAxis.getAutorange()) {
|
||||||
|
xAxis.set(xAxis.getType(), false, true, 0, calculatedTime(), 8);
|
||||||
|
}
|
||||||
|
if(yAxis.getAutorange()) {
|
||||||
|
yAxis.set(yAxis.getType(), false, true, minDisplayVoltage(), maxDisplayVoltage(), 8);
|
||||||
|
}
|
||||||
|
TracePlot::replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::move(const QPoint &vect)
|
||||||
|
{
|
||||||
|
if(!xAxis.getLog()) {
|
||||||
|
// can only move axis in linear mode
|
||||||
|
// calculate amount of movement
|
||||||
|
double distance = xAxis.inverseTransform(vect.x(), 0, plotAreaWidth) - xAxis.getRangeMin();
|
||||||
|
xAxis.set(xAxis.getType(), false, false, xAxis.getRangeMin() - distance, xAxis.getRangeMax() - distance, xAxis.getRangeDiv());
|
||||||
|
}
|
||||||
|
if(!yAxis.getLog()) {
|
||||||
|
// can only move axis in linear mode
|
||||||
|
// calculate amount of movement
|
||||||
|
double distance = yAxis.inverseTransform(vect.y(), 0, plotAreaTop - plotAreaBottom) - yAxis.getRangeMin();
|
||||||
|
yAxis.set(yAxis.getType(), false, false, yAxis.getRangeMin() - distance, yAxis.getRangeMax() - distance, yAxis.getRangeDiv());
|
||||||
|
}
|
||||||
|
replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically)
|
||||||
|
{
|
||||||
|
if(horizontally && !xAxis.getLog()) {
|
||||||
|
// can only zoom axis in linear mode
|
||||||
|
// calculate center point
|
||||||
|
double cp = xAxis.inverseTransform(center.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth);
|
||||||
|
double min = ((xAxis.getRangeMin() - cp) * factor) + cp;
|
||||||
|
double max = ((xAxis.getRangeMax() - cp) * factor) + cp;
|
||||||
|
xAxis.set(xAxis.getType(), false, false, min, max, xAxis.getRangeDiv() * factor);
|
||||||
|
}
|
||||||
|
if(vertically) {
|
||||||
|
// can only move axis in linear mode
|
||||||
|
// calculate center point
|
||||||
|
double cp = yAxis.inverseTransform(center.y(), plotAreaBottom, plotAreaTop);
|
||||||
|
double min = ((yAxis.getRangeMin() - cp) * factor) + cp;
|
||||||
|
double max = ((yAxis.getRangeMax() - cp) * factor) + cp;
|
||||||
|
yAxis.set(yAxis.getType(), false, false, min, max, yAxis.getRangeDiv() * factor);
|
||||||
|
}
|
||||||
|
replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::setAuto(bool horizontally, bool vertically)
|
||||||
|
{
|
||||||
|
if(horizontally) {
|
||||||
|
xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getRangeDiv());
|
||||||
|
}
|
||||||
|
if(vertically) {
|
||||||
|
yAxis.set(yAxis.getType(), yAxis.getLog(), true, yAxis.getRangeMin(), yAxis.getRangeMax(), yAxis.getRangeDiv());
|
||||||
|
}
|
||||||
|
replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::fromJSON(nlohmann::json j)
|
||||||
|
{
|
||||||
|
auto jX = j["XAxis"];
|
||||||
|
bool xAuto = jX.value("autorange", xAxis.getAutorange());
|
||||||
|
double xMin = jX.value("min", xAxis.getRangeMin());
|
||||||
|
double xMax = jX.value("max", xAxis.getRangeMax());
|
||||||
|
double xDivs = jX.value("div", xAxis.getRangeDiv());
|
||||||
|
xAxis.set(xAxis.getType(), false, xAuto, xMin, xMax, xDivs);
|
||||||
|
|
||||||
|
auto jY = j["YAxis"];
|
||||||
|
bool yAuto = jY.value("autorange", yAxis.getAutorange());
|
||||||
|
double yMin = jY.value("min", yAxis.getRangeMin());
|
||||||
|
double yMax = jY.value("max", yAxis.getRangeMax());
|
||||||
|
double yDivs = jY.value("div", yAxis.getRangeDiv());
|
||||||
|
yAxis.set(yAxis.getType(), false, yAuto, yMin, yMax, yDivs);
|
||||||
|
|
||||||
|
datarate = j.value("datarate", datarate);
|
||||||
|
risetime = j.value("risetime", risetime);
|
||||||
|
falltime = j.value("falltime", falltime);
|
||||||
|
linearEdge = j.value("linearEdge", linearEdge);
|
||||||
|
highlevel = j.value("highlevel", highlevel);
|
||||||
|
lowlevel = j.value("lowlevel", lowlevel);
|
||||||
|
bitsPerSymbol = j.value("bitPerSymbol", bitsPerSymbol);
|
||||||
|
noise = j.value("noise", noise);
|
||||||
|
jitter = j.value("jitter", jitter);
|
||||||
|
patternbits = j.value("patternBits", patternbits);
|
||||||
|
cycles = j.value("cycles", cycles);
|
||||||
|
xSamples = j.value("xSamples", xSamples);
|
||||||
|
|
||||||
|
for(unsigned int hash : j["traces"]) {
|
||||||
|
// attempt to find the traces with this hash
|
||||||
|
bool found = false;
|
||||||
|
for(auto t : model.getTraces()) {
|
||||||
|
if(t->toHash() == hash) {
|
||||||
|
enableTrace(t, true);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!found) {
|
||||||
|
qWarning() << "Unable to find trace with hash" << hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json EyeDiagramPlot::toJSON()
|
||||||
|
{
|
||||||
|
nlohmann::json j;
|
||||||
|
nlohmann::json jX;
|
||||||
|
jX["autorange"] = yAxis.getAutorange();
|
||||||
|
jX["min"] = xAxis.getRangeMin();
|
||||||
|
jX["max"] = xAxis.getRangeMax();
|
||||||
|
jX["div"] = xAxis.getRangeDiv();
|
||||||
|
j["XAxis"] = jX;
|
||||||
|
nlohmann::json jY;
|
||||||
|
jY["autorange"] = yAxis.getAutorange();
|
||||||
|
jY["min"] = yAxis.getRangeMin();
|
||||||
|
jY["max"] = yAxis.getRangeMax();
|
||||||
|
jY["div"] = yAxis.getRangeDiv();
|
||||||
|
j["YAxis"] = jY;
|
||||||
|
nlohmann::json jtraces;
|
||||||
|
for(auto t : traces) {
|
||||||
|
if(t.second) {
|
||||||
|
jtraces.push_back(t.first->toHash());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j["traces"] = jtraces;
|
||||||
|
|
||||||
|
j["datarate"] = datarate;
|
||||||
|
j["risetime"] = risetime;
|
||||||
|
j["falltime"] = falltime;
|
||||||
|
j["linearEdge"] = linearEdge;
|
||||||
|
j["highlevel"] = highlevel;
|
||||||
|
j["lowlevel"] = lowlevel;
|
||||||
|
j["bitPerSymbol"] = bitsPerSymbol;
|
||||||
|
j["noise"] = noise;
|
||||||
|
j["jitter"] = jitter;
|
||||||
|
j["patternBits"] = patternbits;
|
||||||
|
j["cycles"] = cycles;
|
||||||
|
j["xSamples"] = xSamples;
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::axisSetupDialog()
|
||||||
|
{
|
||||||
|
auto d = new QDialog(this);
|
||||||
|
d->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
auto ui = new Ui::EyeDiagramEditDialog;
|
||||||
|
ui->setupUi(d);
|
||||||
|
|
||||||
|
ui->datarate->setUnit("bps");
|
||||||
|
ui->datarate->setPrefixes(" kMG");
|
||||||
|
ui->datarate->setPrecision(3);
|
||||||
|
|
||||||
|
ui->risetime->setUnit("s");
|
||||||
|
ui->risetime->setPrefixes("pnum ");
|
||||||
|
ui->risetime->setPrecision(3);
|
||||||
|
|
||||||
|
ui->falltime->setUnit("s");
|
||||||
|
ui->falltime->setPrefixes("pnum ");
|
||||||
|
ui->falltime->setPrecision(3);
|
||||||
|
|
||||||
|
ui->highLevel->setUnit("V");
|
||||||
|
ui->highLevel->setPrefixes("m ");
|
||||||
|
ui->highLevel->setPrecision(3);
|
||||||
|
|
||||||
|
ui->lowLevel->setUnit("V");
|
||||||
|
ui->lowLevel->setPrefixes("m ");
|
||||||
|
ui->lowLevel->setPrecision(3);
|
||||||
|
|
||||||
|
ui->noise->setUnit("V");
|
||||||
|
ui->noise->setPrefixes("um ");
|
||||||
|
ui->noise->setPrecision(3);
|
||||||
|
|
||||||
|
ui->jitter->setUnit("s");
|
||||||
|
ui->jitter->setPrefixes("pnum ");
|
||||||
|
ui->jitter->setPrecision(3);
|
||||||
|
|
||||||
|
ui->Xmin->setUnit("s");
|
||||||
|
ui->Xmin->setPrefixes("pnum ");
|
||||||
|
ui->Xmin->setPrecision(5);
|
||||||
|
|
||||||
|
ui->Xmax->setUnit("s");
|
||||||
|
ui->Xmax->setPrefixes("pnum ");
|
||||||
|
ui->Xmax->setPrecision(5);
|
||||||
|
|
||||||
|
ui->Xdivs->setUnit("s");
|
||||||
|
ui->Xdivs->setPrefixes("pnum ");
|
||||||
|
ui->Xdivs->setPrecision(3);
|
||||||
|
|
||||||
|
ui->Ymin->setUnit("V");
|
||||||
|
ui->Ymin->setPrefixes("um ");
|
||||||
|
ui->Ymin->setPrecision(4);
|
||||||
|
|
||||||
|
ui->Ymax->setUnit("V");
|
||||||
|
ui->Ymax->setPrefixes("um ");
|
||||||
|
ui->Ymax->setPrecision(4);
|
||||||
|
|
||||||
|
ui->Ydivs->setUnit("V");
|
||||||
|
ui->Ydivs->setPrefixes("um ");
|
||||||
|
ui->Ydivs->setPrecision(3);
|
||||||
|
|
||||||
|
// set initial values
|
||||||
|
ui->datarate->setValue(datarate);
|
||||||
|
ui->risetime->setValue(risetime);
|
||||||
|
ui->falltime->setValue(falltime);
|
||||||
|
ui->highLevel->setValue(highlevel);
|
||||||
|
ui->lowLevel->setValue(lowlevel);
|
||||||
|
ui->noise->setValue(noise);
|
||||||
|
ui->jitter->setValue(jitter);
|
||||||
|
|
||||||
|
ui->signalLevels->setCurrentIndex(bitsPerSymbol - 1);
|
||||||
|
ui->patternLength->setCurrentIndex(patternbits - 2);
|
||||||
|
ui->fallrisetype->setCurrentIndex(linearEdge ? 0 : 1);
|
||||||
|
|
||||||
|
ui->displayedCycles->setValue(cycles);
|
||||||
|
ui->pointsPerCycle->setValue(xSamples);
|
||||||
|
|
||||||
|
connect(ui->Xauto, &QCheckBox::toggled, [=](bool checked) {
|
||||||
|
ui->Xmin->setEnabled(!checked);
|
||||||
|
ui->Xmax->setEnabled(!checked);
|
||||||
|
ui->Xdivs->setEnabled(!checked);
|
||||||
|
});
|
||||||
|
ui->Xauto->setChecked(xAxis.getAutorange());
|
||||||
|
ui->Xmin->setValue(xAxis.getRangeMin());
|
||||||
|
ui->Xmax->setValue(xAxis.getRangeMax());
|
||||||
|
ui->Xdivs->setValue(xAxis.getRangeDiv());
|
||||||
|
|
||||||
|
connect(ui->Yauto, &QCheckBox::toggled, [=](bool checked) {
|
||||||
|
ui->Ymin->setEnabled(!checked);
|
||||||
|
ui->Ymax->setEnabled(!checked);
|
||||||
|
ui->Ydivs->setEnabled(!checked);
|
||||||
|
});
|
||||||
|
ui->Yauto->setChecked(yAxis.getAutorange());
|
||||||
|
ui->Ymin->setValue(yAxis.getRangeMin());
|
||||||
|
ui->Ymax->setValue(yAxis.getRangeMax());
|
||||||
|
ui->Ydivs->setValue(yAxis.getRangeDiv());
|
||||||
|
|
||||||
|
auto updateValues = [=](){
|
||||||
|
std::lock_guard<std::mutex> guard(calcMutex);
|
||||||
|
datarate = ui->datarate->value();
|
||||||
|
risetime = ui->risetime->value();
|
||||||
|
falltime = ui->falltime->value();
|
||||||
|
highlevel = ui->highLevel->value();
|
||||||
|
lowlevel = ui->lowLevel->value();
|
||||||
|
noise = ui->noise->value();
|
||||||
|
jitter = ui->jitter->value();
|
||||||
|
|
||||||
|
bitsPerSymbol = ui->signalLevels->currentIndex() + 1;
|
||||||
|
patternbits = ui->patternLength->currentIndex() + 2;
|
||||||
|
linearEdge = ui->fallrisetype->currentIndex() == 0;
|
||||||
|
|
||||||
|
cycles = ui->displayedCycles->value();
|
||||||
|
xSamples = ui->pointsPerCycle->value();
|
||||||
|
|
||||||
|
xAxis.set(xAxis.getType(), false, ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value());
|
||||||
|
yAxis.set(yAxis.getType(), false, ui->Yauto->isChecked(), ui->Ymin->value(), ui->Ymax->value(), ui->Ydivs->value());
|
||||||
|
};
|
||||||
|
|
||||||
|
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [=](){
|
||||||
|
updateValues();
|
||||||
|
});
|
||||||
|
connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, [=](){
|
||||||
|
updateValues();
|
||||||
|
});
|
||||||
|
|
||||||
|
if(AppWindow::showGUI()) {
|
||||||
|
d->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::updateContextMenu()
|
||||||
|
{
|
||||||
|
contextmenu->clear();
|
||||||
|
auto setup = new QAction("Setup...", contextmenu);
|
||||||
|
connect(setup, &QAction::triggered, this, &EyeDiagramPlot::axisSetupDialog);
|
||||||
|
contextmenu->addAction(setup);
|
||||||
|
|
||||||
|
contextmenu->addSeparator();
|
||||||
|
auto image = new QAction("Save image...", contextmenu);
|
||||||
|
contextmenu->addAction(image);
|
||||||
|
connect(image, &QAction::triggered, [=]() {
|
||||||
|
auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||||
|
if(filename.isEmpty()) {
|
||||||
|
// aborted selection
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(filename.endsWith(".png")) {
|
||||||
|
filename.chop(4);
|
||||||
|
}
|
||||||
|
filename += ".png";
|
||||||
|
grab().save(filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
contextmenu->addSection("Traces");
|
||||||
|
// Populate context menu
|
||||||
|
for(auto t : orderedTraces()) {
|
||||||
|
if(!supported(t)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto action = new QAction(t->name(), contextmenu);
|
||||||
|
action->setCheckable(true);
|
||||||
|
if(traces[t]) {
|
||||||
|
action->setChecked(true);
|
||||||
|
}
|
||||||
|
connect(action, &QAction::toggled, [=](bool active) {
|
||||||
|
enableTrace(t, active);
|
||||||
|
});
|
||||||
|
contextmenu->addAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
finishContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyeDiagramPlot::positionWithinGraphArea(const QPoint &p)
|
||||||
|
{
|
||||||
|
return p.x() >= plotAreaLeft && p.x() <= plotAreaLeft + plotAreaWidth
|
||||||
|
&& p.y() >= plotAreaTop && p.y() <= plotAreaBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::draw(QPainter &p)
|
||||||
|
{
|
||||||
|
auto &pref = Preferences::getInstance();
|
||||||
|
|
||||||
|
auto w = p.window();
|
||||||
|
auto yAxisSpace = pref.Graphs.fontSizeAxis * 5.5;
|
||||||
|
auto xAxisSpace = pref.Graphs.fontSizeAxis * 3;
|
||||||
|
plotAreaLeft = yAxisSpace;
|
||||||
|
plotAreaWidth = w.width() - plotAreaLeft - 10;
|
||||||
|
plotAreaTop = 10;
|
||||||
|
plotAreaBottom = w.height() - xAxisSpace;
|
||||||
|
|
||||||
|
p.setBackground(QBrush(pref.Graphs.Color.background));
|
||||||
|
p.fillRect(0, 0, width(), height(), QBrush(pref.Graphs.Color.background));
|
||||||
|
|
||||||
|
auto pen = QPen(pref.Graphs.Color.axis, 0);
|
||||||
|
pen.setCosmetic(true);
|
||||||
|
p.setPen(pen);
|
||||||
|
auto plotRect = QRect(plotAreaLeft, plotAreaTop, plotAreaWidth + 1, plotAreaBottom - plotAreaTop + 1);
|
||||||
|
p.drawRect(plotRect);
|
||||||
|
|
||||||
|
// Y axis
|
||||||
|
QString labelY = "Voltage";
|
||||||
|
auto font = p.font();
|
||||||
|
font.setPixelSize(pref.Graphs.fontSizeAxis);
|
||||||
|
p.setFont(font);
|
||||||
|
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
||||||
|
p.save();
|
||||||
|
p.translate(0, w.height()-xAxisSpace);
|
||||||
|
p.rotate(-90);
|
||||||
|
p.drawText(QRect(0, 0, w.height()-xAxisSpace, pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, labelY);
|
||||||
|
p.restore();
|
||||||
|
|
||||||
|
// draw ticks
|
||||||
|
if(yAxis.getTicks().size() > 0) {
|
||||||
|
// this only works for evenly distributed ticks:
|
||||||
|
auto max = qMax(abs(yAxis.getTicks().front()), abs(yAxis.getTicks().back()));
|
||||||
|
double step;
|
||||||
|
if(yAxis.getTicks().size() >= 2) {
|
||||||
|
step = abs(yAxis.getTicks()[0] - yAxis.getTicks()[1]);
|
||||||
|
} else {
|
||||||
|
// only one tick, set arbitrary number of digits
|
||||||
|
step = max / 1000;
|
||||||
|
}
|
||||||
|
int significantDigits = floor(log10(max)) - floor(log10(step)) + 1;
|
||||||
|
|
||||||
|
for(unsigned int j = 0; j < yAxis.getTicks().size(); j++) {
|
||||||
|
auto yCoord = yAxis.transform(yAxis.getTicks()[j], plotAreaBottom, plotAreaTop);
|
||||||
|
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
||||||
|
// draw tickmark on axis
|
||||||
|
auto tickStart = plotAreaLeft;
|
||||||
|
auto tickLen = -2;
|
||||||
|
p.drawLine(tickStart, yCoord, tickStart + tickLen, yCoord);
|
||||||
|
QString unit = "";
|
||||||
|
QString prefix = " ";
|
||||||
|
if(pref.Graphs.showUnits) {
|
||||||
|
unit = "V";
|
||||||
|
prefix = "um ";
|
||||||
|
}
|
||||||
|
auto tickValue = Unit::ToString(yAxis.getTicks()[j], unit, prefix, significantDigits);
|
||||||
|
p.drawText(QRectF(0, yCoord - pref.Graphs.fontSizeAxis/2 - 2, tickStart + 2 * tickLen, pref.Graphs.fontSizeAxis), Qt::AlignRight, tickValue);
|
||||||
|
|
||||||
|
// tick lines
|
||||||
|
if(yCoord == plotAreaTop || yCoord == plotAreaBottom) {
|
||||||
|
// skip tick lines right on the plot borders
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// only draw tick lines for primary axis
|
||||||
|
if (pref.Graphs.Color.Ticks.Background.enabled) {
|
||||||
|
if (j%2)
|
||||||
|
{
|
||||||
|
int yCoordTop = yAxis.transform(yAxis.getTicks()[j], plotAreaTop, plotAreaBottom);
|
||||||
|
int yCoordBot = yAxis.transform(yAxis.getTicks()[j-1], plotAreaTop, plotAreaBottom);
|
||||||
|
if(yCoordTop > yCoordBot) {
|
||||||
|
auto buf = yCoordBot;
|
||||||
|
yCoordBot = yCoordTop;
|
||||||
|
yCoordTop = buf;
|
||||||
|
}
|
||||||
|
p.setBrush(pref.Graphs.Color.Ticks.Background.background);
|
||||||
|
p.setPen(pref.Graphs.Color.Ticks.Background.background);
|
||||||
|
auto rect = QRect(plotAreaLeft+1, yCoordTop+1, plotAreaWidth-2, yCoordBot-yCoordTop-2);
|
||||||
|
p.drawRect(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine));
|
||||||
|
p.drawLine(plotAreaLeft, yCoord, plotAreaLeft + plotAreaWidth, yCoord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// X axis name
|
||||||
|
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
||||||
|
p.drawText(QRect(plotAreaLeft, w.height()-pref.Graphs.fontSizeAxis*1.5, plotAreaWidth, pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, "Time");
|
||||||
|
|
||||||
|
// draw X axis ticks
|
||||||
|
if(xAxis.getTicks().size() >= 1) {
|
||||||
|
// draw X ticks
|
||||||
|
int significantDigits;
|
||||||
|
// this only works for evenly distributed ticks:
|
||||||
|
auto max = qMax(abs(xAxis.getTicks().front()), abs(xAxis.getTicks().back()));
|
||||||
|
double step;
|
||||||
|
if(xAxis.getTicks().size() >= 2) {
|
||||||
|
step = abs(xAxis.getTicks()[0] - xAxis.getTicks()[1]);
|
||||||
|
} else {
|
||||||
|
// only one tick, set arbitrary number of digits
|
||||||
|
step = max / 1000;
|
||||||
|
}
|
||||||
|
significantDigits = floor(log10(max)) - floor(log10(step)) + 1;
|
||||||
|
QString prefixes = "fpnum kMG";
|
||||||
|
QString unit = "";
|
||||||
|
if(pref.Graphs.showUnits) {
|
||||||
|
unit = xAxis.Unit();
|
||||||
|
}
|
||||||
|
int lastTickLabelEnd = 0;
|
||||||
|
for(auto t : xAxis.getTicks()) {
|
||||||
|
auto xCoord = xAxis.transform(t, plotAreaLeft, plotAreaLeft + plotAreaWidth);
|
||||||
|
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
||||||
|
p.drawLine(xCoord, plotAreaBottom, xCoord, plotAreaBottom + 2);
|
||||||
|
if(xCoord != plotAreaLeft && xCoord != plotAreaLeft + plotAreaWidth) {
|
||||||
|
p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine));
|
||||||
|
p.drawLine(xCoord, plotAreaTop, xCoord, plotAreaBottom);
|
||||||
|
}
|
||||||
|
if(xCoord - 40 <= lastTickLabelEnd) {
|
||||||
|
// would overlap previous tick label, skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto tickValue = Unit::ToString(t, unit, prefixes, significantDigits);
|
||||||
|
p.setPen(QPen(pref.Graphs.Color.axis, 1));
|
||||||
|
QRect bounding;
|
||||||
|
p.drawText(QRect(xCoord - pref.Graphs.fontSizeAxis*2, plotAreaBottom + 5, pref.Graphs.fontSizeAxis*4,
|
||||||
|
pref.Graphs.fontSizeAxis), Qt::AlignHCenter, tickValue, &bounding);
|
||||||
|
lastTickLabelEnd = bounding.x() + bounding.width();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(displayData->size() >= 2) {
|
||||||
|
std::lock_guard<std::mutex> guard(bufferSwitchMutex);
|
||||||
|
unsigned int pxWidth = plotAreaWidth;
|
||||||
|
unsigned int pxHeight = plotAreaBottom - plotAreaTop;
|
||||||
|
std::vector<std::vector<unsigned int>> bitmap;
|
||||||
|
bitmap.resize(pxWidth);
|
||||||
|
for(auto &y : bitmap) {
|
||||||
|
y.resize(pxHeight, 0);
|
||||||
|
}
|
||||||
|
unsigned int highestIntensity = 0;
|
||||||
|
unsigned int numTraces = (*displayData)[0].y.size();
|
||||||
|
|
||||||
|
auto addLine = [&](int x0, int y0, int x1, int y1, bool skipFirst = true) {
|
||||||
|
bool first = true;
|
||||||
|
auto putpixel = [&](int x, int y) {
|
||||||
|
if(skipFirst && first) {
|
||||||
|
first = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(x < 0 || x >= (int) pxWidth || y < 0 || y >= (int) pxHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &bin = bitmap[x][y];
|
||||||
|
bin++;
|
||||||
|
if(bin > highestIntensity) {
|
||||||
|
highestIntensity = bin;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
|
||||||
|
int dy = -abs (y1 - y0), sy = y0 < y1 ? 1 : -1;
|
||||||
|
int err = dx + dy, e2; /* error value e_xy */
|
||||||
|
|
||||||
|
for (;;){ /* loop */
|
||||||
|
putpixel (x0,y0);
|
||||||
|
if (x0 == x1 && y0 == y1) break;
|
||||||
|
e2 = 2 * err;
|
||||||
|
if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
|
||||||
|
if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assemble the bitmap
|
||||||
|
for(unsigned int i=1;i<xSamples;i++) {
|
||||||
|
int x0 = xAxis.transform((*displayData)[i-1].x, 0, pxWidth);
|
||||||
|
int x1 = xAxis.transform((*displayData)[i].x, 0, pxWidth);
|
||||||
|
if((x0 < 0 && x1 < 0) || (x0 >= (int) pxWidth && x1 >= (int) pxWidth)) {
|
||||||
|
// completely out of the frame
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for(unsigned int j=0;j<numTraces;j++) {
|
||||||
|
int y0 = yAxis.transform((*displayData)[i-1].y[j], pxHeight, 0);
|
||||||
|
int y1 = yAxis.transform((*displayData)[i].y[j], pxHeight, 0);
|
||||||
|
addLine(x0, y0, x1, y1, i > 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the bitmap
|
||||||
|
pen = QPen();
|
||||||
|
pen.setCosmetic(true);
|
||||||
|
for(unsigned int i=1;i<pxWidth;i++) {
|
||||||
|
for(unsigned int j=0;j<pxHeight;j++) {
|
||||||
|
if(bitmap[i][j] > 0) {
|
||||||
|
double value = (double) bitmap[i][j] / highestIntensity;
|
||||||
|
pen.setColor(Util::getIntensityGradeColor(value));
|
||||||
|
p.setPen(pen);
|
||||||
|
p.drawPoint(plotAreaLeft + i + 1, plotAreaTop + j + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(dropPending) {
|
||||||
|
p.setOpacity(0.5);
|
||||||
|
p.setBrush(Qt::white);
|
||||||
|
p.setPen(Qt::white);
|
||||||
|
// show drop area over whole plot
|
||||||
|
p.drawRect(plotRect);
|
||||||
|
auto font = p.font();
|
||||||
|
font.setPixelSize(20);
|
||||||
|
p.setFont(font);
|
||||||
|
p.setOpacity(1.0);
|
||||||
|
p.setPen(Qt::white);
|
||||||
|
auto text = "Drop here to add\n" + dropTrace->name() + "\nto waterfall plot";
|
||||||
|
p.drawText(plotRect, Qt::AlignCenter, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyeDiagramPlot::supported(Trace *t)
|
||||||
|
{
|
||||||
|
if(t->getDataType() != Trace::DataType::Frequency) {
|
||||||
|
// wrong domain
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(t->isReflection()) {
|
||||||
|
// can't work with reflection measurements
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString EyeDiagramPlot::mouseText(QPoint pos)
|
||||||
|
{
|
||||||
|
QString ret;
|
||||||
|
if(positionWithinGraphArea(pos)) {
|
||||||
|
// cursor within plot area
|
||||||
|
QPointF coords = pixelToPlotValue(pos);
|
||||||
|
int significantDigits = floor(log10(abs(xAxis.getRangeMax()))) - floor(log10((abs(xAxis.getRangeMax() - xAxis.getRangeMin())) / 1000.0)) + 1;
|
||||||
|
ret += Unit::ToString(coords.x(), xAxis.Unit(), "fpnum kMG", significantDigits) + "\n";
|
||||||
|
auto max = qMax(abs(yAxis.getRangeMax()), abs(yAxis.getRangeMin()));
|
||||||
|
auto step = abs(yAxis.getRangeMax() - yAxis.getRangeMin()) / 1000.0;
|
||||||
|
significantDigits = floor(log10(max)) - floor(log10(step)) + 1;
|
||||||
|
ret += Unit::ToString(coords.y(), "V", yAxis.Prefixes(), significantDigits) + "\n";
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint EyeDiagramPlot::plotValueToPixel(QPointF plotValue)
|
||||||
|
{
|
||||||
|
QPoint p;
|
||||||
|
p.setX(xAxis.transform(plotValue.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth));
|
||||||
|
p.setY(yAxis.transform(plotValue.y(), plotAreaBottom, plotAreaTop));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointF EyeDiagramPlot::pixelToPlotValue(QPoint pixel)
|
||||||
|
{
|
||||||
|
QPointF p;
|
||||||
|
p.setX(xAxis.inverseTransform(pixel.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth));
|
||||||
|
p.setY(yAxis.inverseTransform(pixel.y(), plotAreaBottom, plotAreaTop));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::updateThread(unsigned int xSamples)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> calc(calcMutex);
|
||||||
|
do {
|
||||||
|
updateScheduled = false;
|
||||||
|
setStatus("Starting calculation...");
|
||||||
|
if(!trace) {
|
||||||
|
setStatus("No trace assigned");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Starting eye diagram calculation";
|
||||||
|
|
||||||
|
// sanity check values
|
||||||
|
if(datarate >= trace->getSample(trace->numSamples() - 1).x) {
|
||||||
|
setStatus("Data rate too high");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(datarate <= 0) {
|
||||||
|
setStatus("Data rate too low");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(risetime > 0.3 * 1.0 / datarate) {
|
||||||
|
setStatus("Rise time too high");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(falltime > 0.3 * 1.0 / datarate) {
|
||||||
|
setStatus("Fall time too high");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(jitter > 0.3 * 1.0 / datarate) {
|
||||||
|
setStatus("Jitter too high");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Eye calculation: input values okay";
|
||||||
|
|
||||||
|
// calculate timestep
|
||||||
|
double timestep = calculatedTime() / xSamples;
|
||||||
|
// reserve vector for input data
|
||||||
|
std::vector<std::complex<double>> inVec(xSamples * (cycles + 1), 0.0); // needs to calculate one more cycle than required for the display (settling)
|
||||||
|
|
||||||
|
// resize working buffer
|
||||||
|
calcData->clear();
|
||||||
|
calcData->resize(xSamples);
|
||||||
|
for(auto& s : *calcData) {
|
||||||
|
s.y.resize(cycles, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus("Extracting impulse response...");
|
||||||
|
|
||||||
|
// calculate impulse response of trace
|
||||||
|
double eyeTimeShift = 0;
|
||||||
|
std::vector<std::complex<double>> impulseVec;
|
||||||
|
// determine how long the impulse response is
|
||||||
|
auto samples = tdr->numSamples();
|
||||||
|
if(samples == 0) {
|
||||||
|
// TDR calculation not yet done, unable to update
|
||||||
|
updating = false;
|
||||||
|
setStatus("No time-domain data from trace");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto length = tdr->getSample(samples - 1).x;
|
||||||
|
|
||||||
|
// determine average delay
|
||||||
|
auto total_step = tdr->getStepResponse(samples - 1);
|
||||||
|
for(unsigned int i=0;i<samples;i++) {
|
||||||
|
auto step = tdr->getStepResponse(i);
|
||||||
|
if(abs(total_step - step) <= abs(step)) {
|
||||||
|
// mid point reached
|
||||||
|
eyeTimeShift = tdr->getSample(i).x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long convolutedSize = length / timestep;
|
||||||
|
if(convolutedSize > inVec.size()) {
|
||||||
|
// impulse response is longer than what we display, truncate
|
||||||
|
convolutedSize = inVec.size();
|
||||||
|
}
|
||||||
|
impulseVec.resize(convolutedSize);
|
||||||
|
/*
|
||||||
|
* we can't use the impulse response directly because we most likely need samples inbetween
|
||||||
|
* the calculated values. Interpolation is available but if our sample spacing here is much
|
||||||
|
* wider than the impulse response data, we might miss peaks (or severely miscalculate their
|
||||||
|
* amplitude.
|
||||||
|
* Instead, the step response is interpolated and the impulse response determined by deriving
|
||||||
|
* it from the interpolated step response data. As the step response is the integrated imulse
|
||||||
|
* response data, we can't miss narrow peaks that way.
|
||||||
|
*/
|
||||||
|
double lastStepResponse = 0.0;
|
||||||
|
for(unsigned long i=0;i<convolutedSize;i++) {
|
||||||
|
auto x = i*timestep;
|
||||||
|
auto step = tdr->getInterpolatedStepResponse(x);
|
||||||
|
impulseVec[i] = step - lastStepResponse;
|
||||||
|
lastStepResponse = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
eyeTimeShift += (risetime + falltime) * 1.25 / 4;
|
||||||
|
eyeTimeShift += 0.5 / datarate;
|
||||||
|
int eyeXshift = eyeTimeShift / timestep;
|
||||||
|
|
||||||
|
qDebug() << "Eye calculation: TDR calculation done";
|
||||||
|
|
||||||
|
setStatus("Generating PRBS sequence...");
|
||||||
|
|
||||||
|
auto prbs = new PRBS(patternbits);
|
||||||
|
|
||||||
|
auto getNextLevel = [&]() -> unsigned int {
|
||||||
|
unsigned int level = 0;
|
||||||
|
for(unsigned int i=0;i<bitsPerSymbol;i++) {
|
||||||
|
level <<= 1;
|
||||||
|
if(prbs->next()) {
|
||||||
|
level |= 0x01;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto levelToVoltage = [=](unsigned int level) -> double {
|
||||||
|
unsigned int maxLevel = (0x01 << bitsPerSymbol) - 1;
|
||||||
|
return Util::Scale((double) level, 0.0, (double) maxLevel, lowlevel, highlevel);
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned int currentSignal = getNextLevel();
|
||||||
|
unsigned int nextSignal = getNextLevel();
|
||||||
|
|
||||||
|
// initialize random generator
|
||||||
|
std::random_device rd1;
|
||||||
|
std::mt19937 mt_noise(rd1());
|
||||||
|
std::normal_distribution<> dist_noise(0, noise);
|
||||||
|
|
||||||
|
std::random_device rd2;
|
||||||
|
std::mt19937 mt_jitter(rd2());
|
||||||
|
std::normal_distribution<> dist_jitter(0, jitter);
|
||||||
|
|
||||||
|
unsigned int bitcnt = 1;
|
||||||
|
double transitionTime = -10; // assume that we start with a settled input, last transition was "long" ago
|
||||||
|
for(unsigned int i=0;i<inVec.size();i++) {
|
||||||
|
double time = (i+eyeXshift)*timestep;
|
||||||
|
double voltage;
|
||||||
|
if(time >= transitionTime) {
|
||||||
|
// currently within a bit transition
|
||||||
|
double edgeTime = 0;
|
||||||
|
double expTimeConstant;
|
||||||
|
if(currentSignal < nextSignal) {
|
||||||
|
edgeTime = risetime;
|
||||||
|
} else if(currentSignal > nextSignal) {
|
||||||
|
edgeTime = falltime;
|
||||||
|
}
|
||||||
|
if(linearEdge) {
|
||||||
|
// edge is modeled as linear rise/fall
|
||||||
|
// increase slightly to account for typical 10/90% fall/rise time
|
||||||
|
edgeTime *= 1.25;
|
||||||
|
} else {
|
||||||
|
// edge is modeled as exponential rise/fall. Adjust time constant to match
|
||||||
|
// selected rise/fall time (with 10-90% signal rise/fall within specified time)
|
||||||
|
expTimeConstant = edgeTime / 2.197224577;
|
||||||
|
edgeTime = 6 * expTimeConstant; // after six time constants, 99.7% of signal movement has happened
|
||||||
|
}
|
||||||
|
if(time >= transitionTime + edgeTime) {
|
||||||
|
// bit transition settled
|
||||||
|
voltage = levelToVoltage(nextSignal);
|
||||||
|
// move on to the next bit
|
||||||
|
currentSignal = nextSignal;
|
||||||
|
nextSignal = getNextLevel();
|
||||||
|
transitionTime = bitcnt * 1.0 / datarate + dist_jitter(mt_jitter);
|
||||||
|
bitcnt++;
|
||||||
|
} else {
|
||||||
|
// still within rise or fall time
|
||||||
|
double timeSinceEdge = time - transitionTime;
|
||||||
|
double from = levelToVoltage(currentSignal);
|
||||||
|
double to = levelToVoltage(nextSignal);
|
||||||
|
if(linearEdge) {
|
||||||
|
double edgeRatio = timeSinceEdge / edgeTime;
|
||||||
|
voltage = from * (1.0 - edgeRatio) + to * edgeRatio;
|
||||||
|
} else {
|
||||||
|
voltage = from + (1.0 - exp(-timeSinceEdge/expTimeConstant)) * (to - from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// still before the next edge
|
||||||
|
voltage = levelToVoltage(currentSignal);
|
||||||
|
}
|
||||||
|
voltage += dist_noise(mt_noise);
|
||||||
|
inVec[i] = voltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// input voltage vector fully assembled
|
||||||
|
qDebug() << "Eye calculation: input data generated";
|
||||||
|
|
||||||
|
setStatus("Performing convolution...");
|
||||||
|
|
||||||
|
qDebug() << "Convolve via FFT start";
|
||||||
|
std::vector<std::complex<double>> outVec;
|
||||||
|
impulseVec.resize(inVec.size(), 0.0);
|
||||||
|
outVec.resize(inVec.size());
|
||||||
|
Fft::convolve(inVec, impulseVec, outVec);
|
||||||
|
qDebug() << "Convolve via FFT stop";
|
||||||
|
|
||||||
|
// fill data from outVec
|
||||||
|
for(unsigned int i=0;i<xSamples;i++) {
|
||||||
|
(*calcData)[i].x = i * timestep;
|
||||||
|
}
|
||||||
|
for(unsigned int i=xSamples;i<inVec.size();i++) {
|
||||||
|
unsigned int x = i % xSamples;
|
||||||
|
unsigned int y = i / xSamples;
|
||||||
|
(*calcData)[x].y[y] = outVec[i].real();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Eye calculation: Convolution done";
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(bufferSwitchMutex);
|
||||||
|
// switch buffers
|
||||||
|
auto buf = displayData;
|
||||||
|
displayData = calcData;
|
||||||
|
calcData = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus("Eye calculation complete");
|
||||||
|
replot();
|
||||||
|
} while (updateScheduled);
|
||||||
|
updating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::triggerUpdate()
|
||||||
|
{
|
||||||
|
if(updating) {
|
||||||
|
// already updating, can't start again, schedule for later
|
||||||
|
updateScheduled = true;
|
||||||
|
} else {
|
||||||
|
updating = true;
|
||||||
|
new std::thread(&EyeDiagramPlot::updateThread, this, xSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyeDiagramPlot::setStatus(QString s)
|
||||||
|
{
|
||||||
|
status = s;
|
||||||
|
emit statusChanged(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
double EyeDiagramPlot::calculatedTime()
|
||||||
|
{
|
||||||
|
return 2.0 / datarate;
|
||||||
|
}
|
||||||
|
|
||||||
|
double EyeDiagramPlot::minDisplayVoltage()
|
||||||
|
{
|
||||||
|
auto eyeRange = highlevel - lowlevel;
|
||||||
|
return lowlevel - eyeRange * yOverrange;
|
||||||
|
}
|
||||||
|
|
||||||
|
double EyeDiagramPlot::maxDisplayVoltage()
|
||||||
|
{
|
||||||
|
auto eyeRange = highlevel - lowlevel;
|
||||||
|
return highlevel + eyeRange * yOverrange;
|
||||||
|
}
|
103
Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h
Normal file
103
Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#ifndef EYEDIAGRAMPLOT_H
|
||||||
|
#define EYEDIAGRAMPLOT_H
|
||||||
|
|
||||||
|
#include "traceplot.h"
|
||||||
|
#include "traceaxis.h"
|
||||||
|
#include "Traces/Math/tdr.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class EyeDiagramPlot : public TracePlot
|
||||||
|
{
|
||||||
|
// friend class WaterfallAxisDialog;
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
EyeDiagramPlot(TraceModel &model, QWidget *parent = 0);
|
||||||
|
~EyeDiagramPlot();
|
||||||
|
|
||||||
|
virtual void enableTrace(Trace *t, bool enabled) override;
|
||||||
|
void replot() override;
|
||||||
|
|
||||||
|
virtual void move(const QPoint &vect) override;
|
||||||
|
virtual void zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) override;
|
||||||
|
virtual void setAuto(bool horizontally, bool vertically) override;
|
||||||
|
|
||||||
|
virtual Type getType() override { return Type::EyeDiagram;}
|
||||||
|
|
||||||
|
void fromJSON(nlohmann::json j) override;
|
||||||
|
nlohmann::json toJSON() override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void axisSetupDialog();
|
||||||
|
signals:
|
||||||
|
void statusChanged(QString);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void updateContextMenu() override;
|
||||||
|
virtual bool positionWithinGraphArea(const QPoint &p) override;
|
||||||
|
virtual void draw(QPainter& p) override;
|
||||||
|
virtual bool supported(Trace *t) override;
|
||||||
|
|
||||||
|
virtual QPoint markerToPixel(Marker *m) override { Q_UNUSED(m) return QPoint(0,0);} // never used but most be implemented
|
||||||
|
virtual double nearestTracePoint(Trace *t, QPoint pixel, double *distance = nullptr) override {Q_UNUSED(t)Q_UNUSED(pixel)Q_UNUSED(distance)return 0.0;} // never used but most be implemented
|
||||||
|
|
||||||
|
virtual QString mouseText(QPoint pos) override;
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
virtual bool markerVisible(double x) override {Q_UNUSED(x)return false;} // never show markers
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void triggerUpdate();
|
||||||
|
private:
|
||||||
|
static constexpr double yOverrange = 0.2;
|
||||||
|
QPoint plotValueToPixel(QPointF plotValue);
|
||||||
|
QPointF pixelToPlotValue(QPoint pixel);
|
||||||
|
void updateThread(unsigned int xSamples);
|
||||||
|
void setStatus(QString s);
|
||||||
|
double calculatedTime();
|
||||||
|
double minDisplayVoltage();
|
||||||
|
double maxDisplayVoltage();
|
||||||
|
|
||||||
|
Math::TDR *tdr;
|
||||||
|
|
||||||
|
Trace *trace;
|
||||||
|
|
||||||
|
XAxis xAxis;
|
||||||
|
YAxis yAxis;
|
||||||
|
|
||||||
|
class Xdata {
|
||||||
|
public:
|
||||||
|
double x;
|
||||||
|
std::vector<double> y;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Xdata> data[2];
|
||||||
|
std::vector<Xdata> *displayData;
|
||||||
|
std::vector<Xdata> *calcData;
|
||||||
|
bool updating;
|
||||||
|
bool updateScheduled;
|
||||||
|
|
||||||
|
unsigned int xSamples;
|
||||||
|
double datarate;
|
||||||
|
double highlevel;
|
||||||
|
double lowlevel;
|
||||||
|
unsigned int bitsPerSymbol;
|
||||||
|
double risetime;
|
||||||
|
double falltime;
|
||||||
|
double noise;
|
||||||
|
double jitter;
|
||||||
|
bool linearEdge;
|
||||||
|
unsigned int patternbits;
|
||||||
|
unsigned int cycles;
|
||||||
|
|
||||||
|
int plotAreaLeft, plotAreaWidth, plotAreaBottom, plotAreaTop;
|
||||||
|
|
||||||
|
QString status;
|
||||||
|
|
||||||
|
std::mutex bufferSwitchMutex;
|
||||||
|
std::mutex calcMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EYEDIAGRAMPLOT_H
|
@ -11,6 +11,7 @@
|
|||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
std::set<TracePlot*> TracePlot::plots;
|
std::set<TracePlot*> TracePlot::plots;
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ TracePlot::TracePlot(TraceModel &model, QWidget *parent)
|
|||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
model(model),
|
model(model),
|
||||||
selectedMarker(nullptr),
|
selectedMarker(nullptr),
|
||||||
|
movingGraph(false),
|
||||||
traceRemovalPending(false),
|
traceRemovalPending(false),
|
||||||
dropPending(false),
|
dropPending(false),
|
||||||
dropTrace(nullptr),
|
dropTrace(nullptr),
|
||||||
@ -326,15 +328,29 @@ void TracePlot::mousePressEvent(QMouseEvent *event)
|
|||||||
{
|
{
|
||||||
if(event->buttons() == Qt::LeftButton) {
|
if(event->buttons() == Qt::LeftButton) {
|
||||||
selectedMarker = markerAtPosition(event->pos(), true);
|
selectedMarker = markerAtPosition(event->pos(), true);
|
||||||
|
if(!selectedMarker && positionWithinGraphArea(event->pos())) {
|
||||||
|
// no marker at the position, enter trace moving mode
|
||||||
|
movingGraph = true;
|
||||||
|
lastMousePoint = event->pos();
|
||||||
|
cursorLabel->hide();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedMarker = nullptr;
|
selectedMarker = nullptr;
|
||||||
}
|
}
|
||||||
|
if(event->button() == Qt::MiddleButton) {
|
||||||
|
bool horizontally = !(QApplication::keyboardModifiers() & Qt::ShiftModifier);
|
||||||
|
bool vertically = !(QApplication::keyboardModifiers() & Qt::ControlModifier);
|
||||||
|
setAuto(horizontally, vertically);
|
||||||
|
}
|
||||||
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TracePlot::mouseReleaseEvent(QMouseEvent *event)
|
void TracePlot::mouseReleaseEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
Q_UNUSED(event)
|
Q_UNUSED(event)
|
||||||
selectedMarker = nullptr;
|
selectedMarker = nullptr;
|
||||||
|
movingGraph = false;
|
||||||
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TracePlot::mouseMoveEvent(QMouseEvent *event)
|
void TracePlot::mouseMoveEvent(QMouseEvent *event)
|
||||||
@ -344,6 +360,9 @@ void TracePlot::mouseMoveEvent(QMouseEvent *event)
|
|||||||
auto trace = selectedMarker->getTrace();
|
auto trace = selectedMarker->getTrace();
|
||||||
selectedMarker->setPosition(nearestTracePoint(trace, clickPoint));
|
selectedMarker->setPosition(nearestTracePoint(trace, clickPoint));
|
||||||
cursorLabel->hide();
|
cursorLabel->hide();
|
||||||
|
} else if(movingGraph) {
|
||||||
|
move(event->pos() - lastMousePoint);
|
||||||
|
lastMousePoint = event->pos();
|
||||||
} else {
|
} else {
|
||||||
auto text = mouseText(clickPoint);
|
auto text = mouseText(clickPoint);
|
||||||
if(!text.isEmpty()) {
|
if(!text.isEmpty()) {
|
||||||
@ -358,6 +377,7 @@ void TracePlot::mouseMoveEvent(QMouseEvent *event)
|
|||||||
cursorLabel->hide();
|
cursorLabel->hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TracePlot::leaveEvent(QEvent *event)
|
void TracePlot::leaveEvent(QEvent *event)
|
||||||
@ -365,6 +385,19 @@ void TracePlot::leaveEvent(QEvent *event)
|
|||||||
Q_UNUSED(event);
|
Q_UNUSED(event);
|
||||||
cursorLabel->hide();
|
cursorLabel->hide();
|
||||||
selectedMarker = nullptr;
|
selectedMarker = nullptr;
|
||||||
|
movingGraph = false;
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TracePlot::wheelEvent(QWheelEvent *event)
|
||||||
|
{
|
||||||
|
auto &pref = Preferences::getInstance();
|
||||||
|
if(positionWithinGraphArea(event->pos())) {
|
||||||
|
bool horizontally = !(QApplication::keyboardModifiers() & Qt::ShiftModifier);
|
||||||
|
bool vertically = !(QApplication::keyboardModifiers() & Qt::ControlModifier);
|
||||||
|
double factor = pow((1.0-pref.Graphs.zoomFactor), (double) event->angleDelta().y() / 120.0);
|
||||||
|
zoom(event->pos(), factor, horizontally, vertically);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable)
|
Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable)
|
||||||
|
@ -21,6 +21,7 @@ public:
|
|||||||
XYPlot,
|
XYPlot,
|
||||||
Waterfall,
|
Waterfall,
|
||||||
PolarChart,
|
PolarChart,
|
||||||
|
EyeDiagram,
|
||||||
};
|
};
|
||||||
|
|
||||||
TracePlot(TraceModel &model, QWidget *parent = nullptr);
|
TracePlot(TraceModel &model, QWidget *parent = nullptr);
|
||||||
@ -60,6 +61,9 @@ protected:
|
|||||||
virtual void updateContextMenu(){}
|
virtual void updateContextMenu(){}
|
||||||
// adds common entries at bottom of context menu. Should be called at the end of derived udpateContextMenu functions
|
// adds common entries at bottom of context menu. Should be called at the end of derived udpateContextMenu functions
|
||||||
void finishContextMenu();
|
void finishContextMenu();
|
||||||
|
virtual void move(const QPoint &vect) { Q_UNUSED(vect) }
|
||||||
|
virtual void zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) {Q_UNUSED(center)Q_UNUSED(factor)Q_UNUSED(horizontally)Q_UNUSED(vertically)}
|
||||||
|
virtual void setAuto(bool horizontally, bool vertically) {Q_UNUSED(horizontally)Q_UNUSED(vertically)}
|
||||||
virtual void replot(){update();}
|
virtual void replot(){update();}
|
||||||
virtual void draw(QPainter& p) = 0;
|
virtual void draw(QPainter& p) = 0;
|
||||||
virtual bool supported(Trace *t) = 0;
|
virtual bool supported(Trace *t) = 0;
|
||||||
@ -77,8 +81,10 @@ protected:
|
|||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
void mouseMoveEvent(QMouseEvent *event) override;
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
void leaveEvent(QEvent *event) override;
|
void leaveEvent(QEvent *event) override;
|
||||||
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
|
|
||||||
Marker *markerAtPosition(QPoint p, bool onlyMovable = false);
|
Marker *markerAtPosition(QPoint p, bool onlyMovable = false);
|
||||||
|
virtual bool positionWithinGraphArea(const QPoint &p) {Q_UNUSED(p) return false;}
|
||||||
|
|
||||||
void createMarkerAtPosition(QPoint p);
|
void createMarkerAtPosition(QPoint p);
|
||||||
|
|
||||||
@ -106,6 +112,8 @@ protected:
|
|||||||
double sweep_fmin, sweep_fmax;
|
double sweep_fmin, sweep_fmax;
|
||||||
TraceModel &model;
|
TraceModel &model;
|
||||||
Marker *selectedMarker;
|
Marker *selectedMarker;
|
||||||
|
bool movingGraph;
|
||||||
|
QPoint lastMousePoint;
|
||||||
TileWidget *parentTile;
|
TileWidget *parentTile;
|
||||||
|
|
||||||
// graph settings have been changed, check and possibly remove incompatible traces before next paint event
|
// graph settings have been changed, check and possibly remove incompatible traces before next paint event
|
||||||
|
@ -24,7 +24,7 @@ public:
|
|||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void axisSetupDialog() {};
|
virtual void axisSetupDialog() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static constexpr double polarCoordMax = 4096;
|
static constexpr double polarCoordMax = 4096;
|
||||||
@ -41,7 +41,7 @@ protected:
|
|||||||
virtual bool markerVisible(double x) override;
|
virtual bool markerVisible(double x) override;
|
||||||
|
|
||||||
virtual void updateContextMenu() override;
|
virtual void updateContextMenu() override;
|
||||||
virtual bool supported(Trace *t) override {Q_UNUSED(t) return false;};
|
virtual bool supported(Trace *t) override {Q_UNUSED(t) return false;}
|
||||||
|
|
||||||
// given two points and a circle, the two points are adjusted in such a way that the line they describe
|
// given two points and a circle, the two points are adjusted in such a way that the line they describe
|
||||||
// is constrained within the circle. Returns true if there is a remaining line segment in the circle, false
|
// is constrained within the circle. Returns true if there is a remaining line segment in the circle, false
|
||||||
|
@ -58,7 +58,7 @@ private:
|
|||||||
|
|
||||||
class SmithChartTypeDelegate : public QStyledItemDelegate
|
class SmithChartTypeDelegate : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
Q_OBJECT;
|
Q_OBJECT
|
||||||
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override;
|
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override;
|
||||||
QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
|
QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
|
||||||
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
|
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
|
||||||
@ -66,7 +66,7 @@ class SmithChartTypeDelegate : public QStyledItemDelegate
|
|||||||
|
|
||||||
class SmithChartParamDelegate : public QStyledItemDelegate
|
class SmithChartParamDelegate : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
Q_OBJECT;
|
Q_OBJECT
|
||||||
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override;
|
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override;
|
||||||
QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
|
QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
|
||||||
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
|
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
|
||||||
|
@ -54,7 +54,6 @@ void TraceWaterfall::enableTrace(Trace *t, bool enabled)
|
|||||||
}
|
}
|
||||||
trace = nullptr;
|
trace = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TraceWaterfall::replot()
|
void TraceWaterfall::replot()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "tracexyplot.h"
|
#include "tracexyplot.h"
|
||||||
|
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "CustomWidgets/informationbox.h"
|
#include "CustomWidgets/informationbox.h"
|
||||||
@ -96,6 +96,64 @@ void TraceXYPlot::replot()
|
|||||||
TracePlot::replot();
|
TracePlot::replot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TraceXYPlot::move(const QPoint &vect)
|
||||||
|
{
|
||||||
|
if(!xAxis.getLog()) {
|
||||||
|
// can only move axis in linear mode
|
||||||
|
// calculate amount of movement
|
||||||
|
double distance = xAxis.inverseTransform(vect.x(), 0, plotAreaWidth) - xAxis.getRangeMin();
|
||||||
|
xAxis.set(xAxis.getType(), false, false, xAxis.getRangeMin() - distance, xAxis.getRangeMax() - distance, xAxis.getRangeDiv());
|
||||||
|
xAxisMode = XAxisMode::Manual;
|
||||||
|
}
|
||||||
|
for(int i=0;i<2;i++) {
|
||||||
|
if(!yAxis[i].getLog()) {
|
||||||
|
// can only move axis in linear mode
|
||||||
|
// calculate amount of movement
|
||||||
|
double distance = yAxis[i].inverseTransform(vect.y(), 0, plotAreaTop - plotAreaBottom) - yAxis[i].getRangeMin();
|
||||||
|
yAxis[i].set(yAxis[i].getType(), false, false, yAxis[i].getRangeMin() - distance, yAxis[i].getRangeMax() - distance, yAxis[i].getRangeDiv());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceXYPlot::zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically)
|
||||||
|
{
|
||||||
|
if(horizontally && !xAxis.getLog()) {
|
||||||
|
// can only zoom axis in linear mode
|
||||||
|
// calculate center point
|
||||||
|
double cp = xAxis.inverseTransform(center.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth);
|
||||||
|
double min = ((xAxis.getRangeMin() - cp) * factor) + cp;
|
||||||
|
double max = ((xAxis.getRangeMax() - cp) * factor) + cp;
|
||||||
|
xAxis.set(xAxis.getType(), false, false, min, max, xAxis.getRangeDiv() * factor);
|
||||||
|
xAxisMode = XAxisMode::Manual;
|
||||||
|
}
|
||||||
|
for(int i=0;i<2;i++) {
|
||||||
|
if(vertically && yAxis[i].getType() != YAxis::Type::Disabled && !yAxis[i].getLog()) {
|
||||||
|
// can only move axis in linear mode
|
||||||
|
// calculate center point
|
||||||
|
double cp = yAxis[i].inverseTransform(center.y(), plotAreaBottom, plotAreaTop);
|
||||||
|
double min = ((yAxis[i].getRangeMin() - cp) * factor) + cp;
|
||||||
|
double max = ((yAxis[i].getRangeMax() - cp) * factor) + cp;
|
||||||
|
yAxis[i].set(yAxis[i].getType(), false, false, min, max, yAxis[i].getRangeDiv() * factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceXYPlot::setAuto(bool horizontally, bool vertically)
|
||||||
|
{
|
||||||
|
if(horizontally) {
|
||||||
|
xAxisMode = XAxisMode::FitTraces;
|
||||||
|
xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getRangeDiv());
|
||||||
|
}
|
||||||
|
for(int i=0;i<2;i++) {
|
||||||
|
if(vertically && yAxis[i].getType() != YAxis::Type::Disabled) {
|
||||||
|
yAxis[i].set(yAxis[i].getType(), yAxis[i].getLog(), true, yAxis[i].getRangeMin(), yAxis[i].getRangeMax(), yAxis[i].getRangeDiv());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replot();
|
||||||
|
}
|
||||||
|
|
||||||
nlohmann::json TraceXYPlot::toJSON()
|
nlohmann::json TraceXYPlot::toJSON()
|
||||||
{
|
{
|
||||||
nlohmann::json j;
|
nlohmann::json j;
|
||||||
@ -325,6 +383,12 @@ void TraceXYPlot::updateContextMenu()
|
|||||||
finishContextMenu();
|
finishContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TraceXYPlot::positionWithinGraphArea(const QPoint &p)
|
||||||
|
{
|
||||||
|
return p.x() >= plotAreaLeft && p.x() <= plotAreaLeft + plotAreaWidth
|
||||||
|
&& p.y() >= plotAreaTop && p.y() <= plotAreaBottom;
|
||||||
|
}
|
||||||
|
|
||||||
bool TraceXYPlot::dropSupported(Trace *t)
|
bool TraceXYPlot::dropSupported(Trace *t)
|
||||||
{
|
{
|
||||||
if(domainMatch(t) && !supported(t)) {
|
if(domainMatch(t) && !supported(t)) {
|
||||||
@ -1057,7 +1121,7 @@ void TraceXYPlot::traceDropped(Trace *t, QPoint position)
|
|||||||
QString TraceXYPlot::mouseText(QPoint pos)
|
QString TraceXYPlot::mouseText(QPoint pos)
|
||||||
{
|
{
|
||||||
QString ret;
|
QString ret;
|
||||||
if(QRect(plotAreaLeft, 0, plotAreaWidth + 1, plotAreaBottom).contains(pos)) {
|
if(positionWithinGraphArea(pos)) {
|
||||||
// cursor within plot area
|
// cursor within plot area
|
||||||
QPointF coords[2];
|
QPointF coords[2];
|
||||||
coords[0] = pixelToPlotValue(pos, 0);
|
coords[0] = pixelToPlotValue(pos, 0);
|
||||||
|
@ -74,6 +74,10 @@ public:
|
|||||||
void updateSpan(double min, double max) override;
|
void updateSpan(double min, double max) override;
|
||||||
void replot() override;
|
void replot() override;
|
||||||
|
|
||||||
|
virtual void move(const QPoint &vect) override;
|
||||||
|
virtual void zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) override;
|
||||||
|
virtual void setAuto(bool horizontally, bool vertically) override;
|
||||||
|
|
||||||
virtual Type getType() override { return Type::XYPlot;}
|
virtual Type getType() override { return Type::XYPlot;}
|
||||||
virtual nlohmann::json toJSON() override;
|
virtual nlohmann::json toJSON() override;
|
||||||
virtual void fromJSON(nlohmann::json j) override;
|
virtual void fromJSON(nlohmann::json j) override;
|
||||||
@ -88,6 +92,7 @@ public slots:
|
|||||||
protected:
|
protected:
|
||||||
virtual bool configureForTrace(Trace *t) override;
|
virtual bool configureForTrace(Trace *t) override;
|
||||||
virtual void updateContextMenu() override;
|
virtual void updateContextMenu() override;
|
||||||
|
virtual bool positionWithinGraphArea(const QPoint &p) override;
|
||||||
virtual bool dropSupported(Trace *t) override;
|
virtual bool dropSupported(Trace *t) override;
|
||||||
virtual void draw(QPainter &p) override;
|
virtual void draw(QPainter &p) override;
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
#include "CustomWidgets/siunitedit.h"
|
#include "CustomWidgets/siunitedit.h"
|
||||||
#include "Traces/Marker/markerwidget.h"
|
#include "Traces/Marker/markerwidget.h"
|
||||||
#include "Tools/impedancematchdialog.h"
|
#include "Tools/impedancematchdialog.h"
|
||||||
#include "Tools/eyediagramdialog.h"
|
|
||||||
#include "ui_main.h"
|
#include "ui_main.h"
|
||||||
#include "Device/firmwareupdatedialog.h"
|
#include "Device/firmwareupdatedialog.h"
|
||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
@ -183,8 +182,6 @@ VNA::VNA(AppWindow *window, QString name)
|
|||||||
actions.insert(toolsMenu->menuAction());
|
actions.insert(toolsMenu->menuAction());
|
||||||
auto impedanceMatching = toolsMenu->addAction("Impedance Matching");
|
auto impedanceMatching = toolsMenu->addAction("Impedance Matching");
|
||||||
connect(impedanceMatching, &QAction::triggered, this, &VNA::StartImpedanceMatching);
|
connect(impedanceMatching, &QAction::triggered, this, &VNA::StartImpedanceMatching);
|
||||||
auto eyeDiagram = toolsMenu->addAction("Eye Diagram");
|
|
||||||
connect(eyeDiagram, &QAction::triggered, this, &VNA::StartEyeDiagram);
|
|
||||||
|
|
||||||
defaultCalMenu = new QMenu("Default Calibration", window);
|
defaultCalMenu = new QMenu("Default Calibration", window);
|
||||||
assignDefaultCal = defaultCalMenu->addAction("Assign...");
|
assignDefaultCal = defaultCalMenu->addAction("Assign...");
|
||||||
@ -955,15 +952,6 @@ void VNA::StartImpedanceMatching()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VNA::StartEyeDiagram()
|
|
||||||
{
|
|
||||||
auto dialog = new EyeDiagramDialog(traceModel);
|
|
||||||
if(AppWindow::showGUI()) {
|
|
||||||
dialog->show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void VNA::SetSweepType(SweepType sw)
|
void VNA::SetSweepType(SweepType sw)
|
||||||
{
|
{
|
||||||
if(settings.sweepType != sw) {
|
if(settings.sweepType != sw) {
|
||||||
|
@ -81,7 +81,6 @@ public slots:
|
|||||||
private slots:
|
private slots:
|
||||||
void NewDatapoint(VirtualDevice::VNAMeasurement m);
|
void NewDatapoint(VirtualDevice::VNAMeasurement m);
|
||||||
void StartImpedanceMatching();
|
void StartImpedanceMatching();
|
||||||
void StartEyeDiagram();
|
|
||||||
// Sweep control
|
// Sweep control
|
||||||
void SetSweepType(SweepType sw);
|
void SetSweepType(SweepType sw);
|
||||||
void SetStartFreq(double freq);
|
void SetStartFreq(double freq);
|
||||||
|
@ -128,6 +128,8 @@ public:
|
|||||||
int fontSizeMarkerData;
|
int fontSizeMarkerData;
|
||||||
int fontSizeTraceNames;
|
int fontSizeTraceNames;
|
||||||
int fontSizeCursorOverlay;
|
int fontSizeCursorOverlay;
|
||||||
|
|
||||||
|
double zoomFactor;
|
||||||
} Graphs;
|
} Graphs;
|
||||||
struct {
|
struct {
|
||||||
struct {
|
struct {
|
||||||
@ -211,6 +213,7 @@ private:
|
|||||||
{&Graphs.fontSizeCursorOverlay, "Graphs.fontSizeCursorOverlay", 12},
|
{&Graphs.fontSizeCursorOverlay, "Graphs.fontSizeCursorOverlay", 12},
|
||||||
{&Graphs.fontSizeMarkerData, "Graphs.fontSizeMarkerData", 12},
|
{&Graphs.fontSizeMarkerData, "Graphs.fontSizeMarkerData", 12},
|
||||||
{&Graphs.fontSizeTraceNames, "Graphs.fontSizeTraceNames", 12},
|
{&Graphs.fontSizeTraceNames, "Graphs.fontSizeTraceNames", 12},
|
||||||
|
{&Graphs.zoomFactor, "Graphs.zoomFactor", 0.2},
|
||||||
{&Marker.defaultBehavior.showDataOnGraphs, "Marker.defaultBehavior.ShowDataOnGraphs", true},
|
{&Marker.defaultBehavior.showDataOnGraphs, "Marker.defaultBehavior.ShowDataOnGraphs", true},
|
||||||
{&Marker.defaultBehavior.showAllData, "Marker.defaultBehavior.ShowAllData", false},
|
{&Marker.defaultBehavior.showAllData, "Marker.defaultBehavior.ShowAllData", false},
|
||||||
{&Marker.interpolatePoints, "Marker.interpolatePoints", false},
|
{&Marker.interpolatePoints, "Marker.interpolatePoints", false},
|
||||||
|
327
Software/PC_Application/LibreVNA-GUI/test.setup
Normal file
327
Software/PC_Application/LibreVNA-GUI/test.setup
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
{
|
||||||
|
"Modes": [
|
||||||
|
{
|
||||||
|
"name": "Vector Network Analyzer",
|
||||||
|
"settings": {
|
||||||
|
"de-embedding": null,
|
||||||
|
"de-embedding_enabled": false,
|
||||||
|
"markers": null,
|
||||||
|
"sweep": {
|
||||||
|
"IFBW": 10000.0,
|
||||||
|
"frequency": {
|
||||||
|
"log": false,
|
||||||
|
"power": -10.0,
|
||||||
|
"start": 1000000.0,
|
||||||
|
"stop": 6000000000.0
|
||||||
|
},
|
||||||
|
"points": 501,
|
||||||
|
"power": {
|
||||||
|
"frequency": 1000000000.0,
|
||||||
|
"start": -30.0,
|
||||||
|
"stop": -15.0
|
||||||
|
},
|
||||||
|
"single": false,
|
||||||
|
"type": "Frequency"
|
||||||
|
},
|
||||||
|
"tiles": {
|
||||||
|
"orientation": "vertical",
|
||||||
|
"sizes": [
|
||||||
|
373,
|
||||||
|
372
|
||||||
|
],
|
||||||
|
"split": true,
|
||||||
|
"tile1": {
|
||||||
|
"orientation": "horizontal",
|
||||||
|
"sizes": [
|
||||||
|
797,
|
||||||
|
796
|
||||||
|
],
|
||||||
|
"split": true,
|
||||||
|
"tile1": {
|
||||||
|
"plot": "smithchart",
|
||||||
|
"plotsettings": {
|
||||||
|
"Z0": 50.0,
|
||||||
|
"constantLines": null,
|
||||||
|
"edge_reflection": 1.0,
|
||||||
|
"frequency_override": false,
|
||||||
|
"limit_to_edge": true,
|
||||||
|
"limit_to_span": true,
|
||||||
|
"offset_axis_x": 0.0,
|
||||||
|
"override_max": 6000000000.0,
|
||||||
|
"override_min": 0.0,
|
||||||
|
"traces": [
|
||||||
|
753243053
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"split": false
|
||||||
|
},
|
||||||
|
"tile2": {
|
||||||
|
"plot": "EyeDiagram",
|
||||||
|
"plotsettings": {
|
||||||
|
"XAxis": {
|
||||||
|
"autorange": false,
|
||||||
|
"div": 7.62939453125e-09,
|
||||||
|
"max": 5.653089396158854e-08,
|
||||||
|
"min": -1.9763051350911456e-08
|
||||||
|
},
|
||||||
|
"YAxis": {
|
||||||
|
"autorange": false,
|
||||||
|
"div": 0.3814697265625,
|
||||||
|
"max": 2.9595544473356057,
|
||||||
|
"min": -2.381021724539393
|
||||||
|
},
|
||||||
|
"bitPerSymbol": 1,
|
||||||
|
"cycles": 200,
|
||||||
|
"datarate": 100000000.0,
|
||||||
|
"falltime": 1e-09,
|
||||||
|
"highlevel": 1.0,
|
||||||
|
"jitter": 1e-10,
|
||||||
|
"linearEdge": true,
|
||||||
|
"lowlevel": 0.0,
|
||||||
|
"noise": 0.01,
|
||||||
|
"patternBits": 9,
|
||||||
|
"risetime": 1e-09,
|
||||||
|
"traces": [
|
||||||
|
3153058534
|
||||||
|
],
|
||||||
|
"xSamples": 200
|
||||||
|
},
|
||||||
|
"split": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tile2": {
|
||||||
|
"orientation": "horizontal",
|
||||||
|
"sizes": [
|
||||||
|
797,
|
||||||
|
796
|
||||||
|
],
|
||||||
|
"split": true,
|
||||||
|
"tile1": {
|
||||||
|
"plot": "EyeDiagram",
|
||||||
|
"plotsettings": {
|
||||||
|
"XAxis": {
|
||||||
|
"autorange": true,
|
||||||
|
"div": 1e-07,
|
||||||
|
"max": 1e-06,
|
||||||
|
"min": 0.0
|
||||||
|
},
|
||||||
|
"YAxis": {
|
||||||
|
"autorange": true,
|
||||||
|
"div": 0.1,
|
||||||
|
"max": 1.2,
|
||||||
|
"min": -0.2
|
||||||
|
},
|
||||||
|
"bitPerSymbol": 2,
|
||||||
|
"cycles": 200,
|
||||||
|
"datarate": 2000000.0,
|
||||||
|
"falltime": 1e-09,
|
||||||
|
"highlevel": 1.0,
|
||||||
|
"jitter": 4e-10,
|
||||||
|
"linearEdge": true,
|
||||||
|
"lowlevel": 0.0,
|
||||||
|
"noise": 0.03,
|
||||||
|
"patternBits": 9,
|
||||||
|
"risetime": 1e-09,
|
||||||
|
"traces": [
|
||||||
|
2663219575
|
||||||
|
],
|
||||||
|
"xSamples": 200
|
||||||
|
},
|
||||||
|
"split": false
|
||||||
|
},
|
||||||
|
"tile2": {
|
||||||
|
"plot": "smithchart",
|
||||||
|
"plotsettings": {
|
||||||
|
"Z0": 50.0,
|
||||||
|
"constantLines": null,
|
||||||
|
"edge_reflection": 1.0,
|
||||||
|
"frequency_override": false,
|
||||||
|
"limit_to_edge": true,
|
||||||
|
"limit_to_span": true,
|
||||||
|
"offset_axis_x": 0.0,
|
||||||
|
"override_max": 6000000000.0,
|
||||||
|
"override_min": 0.0,
|
||||||
|
"traces": [
|
||||||
|
1360958916
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"split": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"traces": [
|
||||||
|
{
|
||||||
|
"color": "#ffff00",
|
||||||
|
"hash": 753243053,
|
||||||
|
"livetype": 0,
|
||||||
|
"math": null,
|
||||||
|
"math_enabled": false,
|
||||||
|
"name": "S11",
|
||||||
|
"parameter": "S11",
|
||||||
|
"paused": false,
|
||||||
|
"reflection": true,
|
||||||
|
"type": "Live",
|
||||||
|
"velocityFactor": 0.66,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#0000ff",
|
||||||
|
"hash": 3153058534,
|
||||||
|
"livetype": 0,
|
||||||
|
"math": null,
|
||||||
|
"math_enabled": false,
|
||||||
|
"name": "S12",
|
||||||
|
"parameter": "S12",
|
||||||
|
"paused": false,
|
||||||
|
"reflection": false,
|
||||||
|
"type": "Live",
|
||||||
|
"velocityFactor": 0.66,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#00ff00",
|
||||||
|
"hash": 2663219575,
|
||||||
|
"livetype": 0,
|
||||||
|
"math": null,
|
||||||
|
"math_enabled": false,
|
||||||
|
"name": "S21",
|
||||||
|
"parameter": "S21",
|
||||||
|
"paused": false,
|
||||||
|
"reflection": false,
|
||||||
|
"type": "Live",
|
||||||
|
"velocityFactor": 0.66,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#ff0000",
|
||||||
|
"hash": 1360958916,
|
||||||
|
"livetype": 0,
|
||||||
|
"math": null,
|
||||||
|
"math_enabled": false,
|
||||||
|
"name": "S22",
|
||||||
|
"parameter": "S22",
|
||||||
|
"paused": false,
|
||||||
|
"reflection": true,
|
||||||
|
"type": "Live",
|
||||||
|
"velocityFactor": 0.66,
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "Vector Network Analyzer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Signal Generator",
|
||||||
|
"settings": {
|
||||||
|
"frequency": 1000000000.0,
|
||||||
|
"port": 0,
|
||||||
|
"power": 0.0,
|
||||||
|
"sweep": {
|
||||||
|
"dwell": 1.0,
|
||||||
|
"enabled": false,
|
||||||
|
"span": 0.0,
|
||||||
|
"steps": 100.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "Signal Generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Spectrum Analyzer",
|
||||||
|
"settings": {
|
||||||
|
"markers": null,
|
||||||
|
"sweep": {
|
||||||
|
"acquisition": {
|
||||||
|
"RBW": 100.0,
|
||||||
|
"detector": "+Peak",
|
||||||
|
"signal ID": true,
|
||||||
|
"window": "Kaiser"
|
||||||
|
},
|
||||||
|
"frequency": {
|
||||||
|
"start": 999975000.0,
|
||||||
|
"stop": 1000025000.0
|
||||||
|
},
|
||||||
|
"single": false,
|
||||||
|
"trackingGenerator": {
|
||||||
|
"enabled": false,
|
||||||
|
"offset": 0.0,
|
||||||
|
"port": 1,
|
||||||
|
"power": -20.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tiles": {
|
||||||
|
"plot": "XY-plot",
|
||||||
|
"plotsettings": {
|
||||||
|
"XAxis": {
|
||||||
|
"div": 5000.0,
|
||||||
|
"log": false,
|
||||||
|
"max": 1000025000.0,
|
||||||
|
"min": 999975000.0,
|
||||||
|
"mode": "Use Span",
|
||||||
|
"type": "Frequency"
|
||||||
|
},
|
||||||
|
"YPrimary": {
|
||||||
|
"autorange": false,
|
||||||
|
"div": 10.0,
|
||||||
|
"log": false,
|
||||||
|
"max": 0.0,
|
||||||
|
"min": -120.0,
|
||||||
|
"traces": [
|
||||||
|
3115643686,
|
||||||
|
1375490686
|
||||||
|
],
|
||||||
|
"type": "Magnitude"
|
||||||
|
},
|
||||||
|
"YSecondary": {
|
||||||
|
"autorange": true,
|
||||||
|
"div": 0.0,
|
||||||
|
"log": false,
|
||||||
|
"max": 1.0,
|
||||||
|
"min": -1.0,
|
||||||
|
"traces": null,
|
||||||
|
"type": "Disabled"
|
||||||
|
},
|
||||||
|
"limitLines": null
|
||||||
|
},
|
||||||
|
"split": false
|
||||||
|
},
|
||||||
|
"traces": [
|
||||||
|
{
|
||||||
|
"color": "#ffff00",
|
||||||
|
"hash": 1375490686,
|
||||||
|
"livetype": 0,
|
||||||
|
"math": null,
|
||||||
|
"math_enabled": false,
|
||||||
|
"name": "PORT1",
|
||||||
|
"parameter": "PORT1",
|
||||||
|
"paused": false,
|
||||||
|
"reflection": false,
|
||||||
|
"type": "Live",
|
||||||
|
"velocityFactor": 0.66,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#0000ff",
|
||||||
|
"hash": 3115643686,
|
||||||
|
"livetype": 0,
|
||||||
|
"math": null,
|
||||||
|
"math_enabled": false,
|
||||||
|
"name": "PORT2",
|
||||||
|
"parameter": "PORT2",
|
||||||
|
"paused": false,
|
||||||
|
"reflection": false,
|
||||||
|
"type": "Live",
|
||||||
|
"velocityFactor": 0.66,
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "Spectrum Analyzer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Reference": {
|
||||||
|
"Mode": "Internal",
|
||||||
|
"Output": "Off"
|
||||||
|
},
|
||||||
|
"activeMode": "Vector Network Analyzer",
|
||||||
|
"version": "1.5.0-alpha.1-329f4487e"
|
||||||
|
}
|
@ -40,7 +40,6 @@ SOURCES += \
|
|||||||
../LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp \
|
../LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp \
|
||||||
../LibreVNA-GUI/SpectrumAnalyzer/tracewidgetsa.cpp \
|
../LibreVNA-GUI/SpectrumAnalyzer/tracewidgetsa.cpp \
|
||||||
../LibreVNA-GUI/Tools/eseries.cpp \
|
../LibreVNA-GUI/Tools/eseries.cpp \
|
||||||
../LibreVNA-GUI/Tools/eyediagramdialog.cpp \
|
|
||||||
../LibreVNA-GUI/Tools/impedancematchdialog.cpp \
|
../LibreVNA-GUI/Tools/impedancematchdialog.cpp \
|
||||||
../LibreVNA-GUI/Tools/parameters.cpp \
|
../LibreVNA-GUI/Tools/parameters.cpp \
|
||||||
../LibreVNA-GUI/Traces/Marker/marker.cpp \
|
../LibreVNA-GUI/Traces/Marker/marker.cpp \
|
||||||
@ -91,6 +90,7 @@ SOURCES += \
|
|||||||
../LibreVNA-GUI/Traces/Math/timegate.cpp \
|
../LibreVNA-GUI/Traces/Math/timegate.cpp \
|
||||||
../LibreVNA-GUI/Traces/Math/tracemath.cpp \
|
../LibreVNA-GUI/Traces/Math/tracemath.cpp \
|
||||||
../LibreVNA-GUI/Traces/Math/windowfunction.cpp \
|
../LibreVNA-GUI/Traces/Math/windowfunction.cpp \
|
||||||
|
../LibreVNA-GUI/Traces/eyediagramplot.cpp \
|
||||||
../LibreVNA-GUI/Traces/fftcomplex.cpp \
|
../LibreVNA-GUI/Traces/fftcomplex.cpp \
|
||||||
../LibreVNA-GUI/Traces/sparamtraceselector.cpp \
|
../LibreVNA-GUI/Traces/sparamtraceselector.cpp \
|
||||||
../LibreVNA-GUI/Traces/trace.cpp \
|
../LibreVNA-GUI/Traces/trace.cpp \
|
||||||
@ -203,7 +203,6 @@ HEADERS += \
|
|||||||
../LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.h \
|
../LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.h \
|
||||||
../LibreVNA-GUI/SpectrumAnalyzer/tracewidgetsa.h \
|
../LibreVNA-GUI/SpectrumAnalyzer/tracewidgetsa.h \
|
||||||
../LibreVNA-GUI/Tools/eseries.h \
|
../LibreVNA-GUI/Tools/eseries.h \
|
||||||
../LibreVNA-GUI/Tools/eyediagramdialog.h \
|
|
||||||
../LibreVNA-GUI/Tools/impedancematchdialog.h \
|
../LibreVNA-GUI/Tools/impedancematchdialog.h \
|
||||||
../LibreVNA-GUI/Tools/parameters.h \
|
../LibreVNA-GUI/Tools/parameters.h \
|
||||||
../LibreVNA-GUI/Traces/Marker/marker.h \
|
../LibreVNA-GUI/Traces/Marker/marker.h \
|
||||||
@ -265,6 +264,7 @@ HEADERS += \
|
|||||||
../LibreVNA-GUI/Traces/Math/timegate.h \
|
../LibreVNA-GUI/Traces/Math/timegate.h \
|
||||||
../LibreVNA-GUI/Traces/Math/tracemath.h \
|
../LibreVNA-GUI/Traces/Math/tracemath.h \
|
||||||
../LibreVNA-GUI/Traces/Math/windowfunction.h \
|
../LibreVNA-GUI/Traces/Math/windowfunction.h \
|
||||||
|
../LibreVNA-GUI/Traces/eyediagramplot.h \
|
||||||
../LibreVNA-GUI/Traces/fftcomplex.h \
|
../LibreVNA-GUI/Traces/fftcomplex.h \
|
||||||
../LibreVNA-GUI/Traces/sparamtraceselector.h \
|
../LibreVNA-GUI/Traces/sparamtraceselector.h \
|
||||||
../LibreVNA-GUI/Traces/trace.h \
|
../LibreVNA-GUI/Traces/trace.h \
|
||||||
@ -293,7 +293,6 @@ HEADERS += \
|
|||||||
../LibreVNA-GUI/VNA/Deembedding/matchingnetwork.h \
|
../LibreVNA-GUI/VNA/Deembedding/matchingnetwork.h \
|
||||||
../LibreVNA-GUI/VNA/Deembedding/portextension.h \
|
../LibreVNA-GUI/VNA/Deembedding/portextension.h \
|
||||||
../LibreVNA-GUI/VNA/Deembedding/twothru.h \
|
../LibreVNA-GUI/VNA/Deembedding/twothru.h \
|
||||||
../LibreVNA-GUI/VNA/portextensiondialog.h \
|
|
||||||
../LibreVNA-GUI/VNA/tracewidgetvna.h \
|
../LibreVNA-GUI/VNA/tracewidgetvna.h \
|
||||||
../LibreVNA-GUI/VNA/vna.h \
|
../LibreVNA-GUI/VNA/vna.h \
|
||||||
../LibreVNA-GUI/about.h \
|
../LibreVNA-GUI/about.h \
|
||||||
@ -343,7 +342,6 @@ FORMS += \
|
|||||||
../LibreVNA-GUI/Device/firmwareupdatedialog.ui \
|
../LibreVNA-GUI/Device/firmwareupdatedialog.ui \
|
||||||
../LibreVNA-GUI/Device/manualcontroldialog.ui \
|
../LibreVNA-GUI/Device/manualcontroldialog.ui \
|
||||||
../LibreVNA-GUI/Generator/signalgenwidget.ui \
|
../LibreVNA-GUI/Generator/signalgenwidget.ui \
|
||||||
../LibreVNA-GUI/Tools/eyediagramdialog.ui \
|
|
||||||
../LibreVNA-GUI/Tools/impedancematchdialog.ui \
|
../LibreVNA-GUI/Tools/impedancematchdialog.ui \
|
||||||
../LibreVNA-GUI/Traces/Marker/markerwidget.ui \
|
../LibreVNA-GUI/Traces/Marker/markerwidget.ui \
|
||||||
../LibreVNA-GUI/Traces/Math/dftdialog.ui \
|
../LibreVNA-GUI/Traces/Math/dftdialog.ui \
|
||||||
|
Loading…
Reference in New Issue
Block a user