partial eye diagram implementation
This commit is contained in:
parent
9a198217b8
commit
c7a99af820
@ -33,6 +33,7 @@ HEADERS += \
|
||||
SpectrumAnalyzer/spectrumanalyzer.h \
|
||||
SpectrumAnalyzer/tracewidgetsa.h \
|
||||
Tools/eseries.h \
|
||||
Tools/eyediagramdialog.h \
|
||||
Tools/impedancematchdialog.h \
|
||||
Tools/parameters.h \
|
||||
Traces/Marker/marker.h \
|
||||
@ -112,6 +113,7 @@ HEADERS += \
|
||||
Traces/waterfallaxisdialog.h \
|
||||
Traces/xyplotaxisdialog.h \
|
||||
Traces/tracepolarchart.h \
|
||||
Util/prbs.h \
|
||||
Util/qpointervariant.h \
|
||||
Util/util.h \
|
||||
Util/app_common.h \
|
||||
@ -175,6 +177,7 @@ SOURCES += \
|
||||
SpectrumAnalyzer/spectrumanalyzer.cpp \
|
||||
SpectrumAnalyzer/tracewidgetsa.cpp \
|
||||
Tools/eseries.cpp \
|
||||
Tools/eyediagramdialog.cpp \
|
||||
Tools/impedancematchdialog.cpp \
|
||||
Tools/parameters.cpp \
|
||||
Traces/Marker/marker.cpp \
|
||||
@ -243,6 +246,7 @@ SOURCES += \
|
||||
Traces/tracepolar.cpp \
|
||||
Traces/waterfallaxisdialog.cpp \
|
||||
Traces/xyplotaxisdialog.cpp \
|
||||
Util/prbs.cpp \
|
||||
Util/util.cpp \
|
||||
VNA/Deembedding/deembedding.cpp \
|
||||
VNA/Deembedding/deembeddingdialog.cpp \
|
||||
@ -301,6 +305,7 @@ FORMS += \
|
||||
Device/firmwareupdatedialog.ui \
|
||||
Device/manualcontroldialog.ui \
|
||||
Generator/signalgenwidget.ui \
|
||||
Tools/eyediagramdialog.ui \
|
||||
Tools/impedancematchdialog.ui \
|
||||
Traces/Marker/markerwidget.ui \
|
||||
Traces/Math/dftdialog.ui \
|
||||
@ -343,7 +348,7 @@ RESOURCES += \
|
||||
icons.qrc \
|
||||
resources/librevna.qrc
|
||||
|
||||
QMAKE_CXXFLAGS += -Wno-deprecated -Wno-deprecated-declarations
|
||||
QMAKE_CXXFLAGS += -Wno-deprecated -Wno-deprecated-declarations -Wno-deprecated-copy
|
||||
|
||||
CONFIG += c++17
|
||||
REVISION = $$system(git rev-parse HEAD)
|
||||
|
File diff suppressed because it is too large
Load Diff
412
Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.cpp
Normal file
412
Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.cpp
Normal file
@ -0,0 +1,412 @@
|
||||
#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 <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);
|
||||
|
||||
workingBuffer = &eyeBuffer[0];
|
||||
finishedBuffer = &eyeBuffer[1];
|
||||
|
||||
updating = false;
|
||||
|
||||
trace = nullptr;
|
||||
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::updatePercent, ui->progress, &QProgressBar::setValue, 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()));
|
||||
ui->update->setEnabled(true);
|
||||
});
|
||||
|
||||
// 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()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
bool EyeDiagramDialog::triggerUpdate()
|
||||
{
|
||||
update(ui->widget->width(), ui->widget->height());
|
||||
}
|
||||
|
||||
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 updatePercent(0);
|
||||
if(!trace) {
|
||||
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();
|
||||
unsigned int patternbits = ui->patternLength->currentIndex() + 2;
|
||||
unsigned int cycles = ui->displayedCycles->value() + 1; // first cycle will not be displayed
|
||||
|
||||
// sanity check values
|
||||
// TODO
|
||||
|
||||
qDebug() << "Eye calculation: input values okay";
|
||||
|
||||
// resize working buffer
|
||||
workingBuffer->clear();
|
||||
workingBuffer->resize(width);
|
||||
for(auto& y : *workingBuffer) {
|
||||
y.resize(height, 0.0);
|
||||
}
|
||||
|
||||
// calculate timestep
|
||||
double displayedTime = 2 * 1.0/datarate; // always showing two bit periods
|
||||
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<double> input(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<input.size();i++) {
|
||||
double time = i*timestep;
|
||||
double voltage;
|
||||
if(time >= transitionTime) {
|
||||
// currently within a bit transition
|
||||
double edgeTime = 0;
|
||||
if(!currentBit && nextBit) {
|
||||
edgeTime = risetime;
|
||||
} else if(currentBit && !nextBit) {
|
||||
edgeTime = falltime;
|
||||
}
|
||||
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 edgeRatio = timeSinceEdge / edgeTime;
|
||||
double from = currentBit ? highlevel : lowlevel;
|
||||
double to = nextBit ? highlevel : lowlevel;
|
||||
voltage = from * (1.0 - edgeRatio) + to * edgeRatio;
|
||||
}
|
||||
} else {
|
||||
// still before the next edge
|
||||
voltage = currentBit ? highlevel : lowlevel;
|
||||
}
|
||||
voltage += dist_noise(mt_noise);
|
||||
input[i] = voltage;
|
||||
}
|
||||
|
||||
// input voltage vector fully assembled
|
||||
qDebug() << "Eye calculation: input data generated";
|
||||
|
||||
// calculate impulse response of trace
|
||||
auto tdr = new Math::TDR();
|
||||
// default configuration of TDR is lowpass with automatic DC, which is exactly what we need
|
||||
|
||||
// TDR calculation happens in background thread, need to wait for emitted signal
|
||||
volatile bool TDRdone = false;
|
||||
|
||||
double eyeTimeShift = 0;
|
||||
|
||||
std::vector<double> convolutionData;
|
||||
auto conn = connect(tdr, &Math::TDR::outputSamplesChanged, [&](){
|
||||
if(!TDRdone) {
|
||||
// determine how long the impulse response is
|
||||
auto samples = tdr->numSamples();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
auto scale = timestep / (length / (samples - 1));
|
||||
unsigned long convolutedSize = length / timestep;
|
||||
if(convolutedSize > input.size()) {
|
||||
// impulse response is longer than what we display, truncate
|
||||
convolutedSize = input.size();
|
||||
}
|
||||
convolutionData.resize(convolutedSize);
|
||||
for(unsigned long i=0;i<convolutedSize;i++) {
|
||||
auto x = i*timestep;
|
||||
convolutionData[i] = tdr->getInterpolatedSample(x).y.real() * scale;
|
||||
}
|
||||
TDRdone = true;
|
||||
}
|
||||
});
|
||||
// assigning the trace starts the TDR calculation
|
||||
tdr->assignInput(trace);
|
||||
|
||||
// wait for the TDR calculation to be done
|
||||
while(!TDRdone) {
|
||||
std::this_thread::sleep_for(20ms);
|
||||
};
|
||||
disconnect(conn);
|
||||
delete tdr;
|
||||
|
||||
eyeTimeShift += (risetime + falltime) / 4;
|
||||
eyeTimeShift += 0.5 / datarate;
|
||||
int eyeXshift = eyeTimeShift / timestep;
|
||||
|
||||
qDebug() << "Eye calculation: TDR calculation done";
|
||||
|
||||
// calculate voltage at top and bottom of diagram for y binning to pixels
|
||||
auto eyeRange = highlevel - lowlevel;
|
||||
auto topValue = highlevel + eyeRange * yOverrange;
|
||||
auto bottomValue = lowlevel - eyeRange * yOverrange;
|
||||
|
||||
unsigned int highestIntensity = 0;
|
||||
|
||||
qDebug() << "Convolve via FFT start";
|
||||
std::vector<std::complex<double>> inVec;
|
||||
std::vector<std::complex<double>> impulseVec;
|
||||
std::vector<std::complex<double>> outVec;
|
||||
for(auto i : input) {
|
||||
inVec.push_back(i);
|
||||
}
|
||||
for(auto i : convolutionData) {
|
||||
impulseVec.push_back(i);
|
||||
}
|
||||
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 */
|
||||
}
|
||||
};
|
||||
|
||||
// got the input data and the convolution data, calculate output
|
||||
int lastyBin;
|
||||
for(unsigned int i=width;i<input.size();i++) {
|
||||
// double voltage = 0;
|
||||
// for(unsigned j=0;j<convolutionData.size();j++) {
|
||||
// double inputValue = i >= j ? input[i-j] : input[0];
|
||||
// voltage += convolutionData[j] * inputValue;
|
||||
// }
|
||||
double voltage = outVec[i].real();
|
||||
int yBin = Util::Scale<double>(voltage, bottomValue, topValue, 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;
|
||||
emit updatePercent(100 * i / input.size());
|
||||
}
|
||||
|
||||
qDebug() << "Eye calculation: Convolution done";
|
||||
|
||||
// normalize intensity
|
||||
for(auto &y : *workingBuffer) {
|
||||
for(auto &v : y) {
|
||||
v /= highestIntensity;
|
||||
}
|
||||
}
|
||||
emit updatePercent(100);
|
||||
// switch buffers
|
||||
auto buf = finishedBuffer;
|
||||
finishedBuffer = workingBuffer;
|
||||
workingBuffer = buf;
|
||||
updating = false;
|
||||
emit updateDone();
|
||||
}
|
||||
|
||||
EyeDiagramPlot::EyeDiagramPlot(QDialog *dialog)
|
||||
{
|
||||
Q_UNUSED(dialog)
|
||||
}
|
||||
|
||||
void EyeDiagramPlot::setDialog(EyeDiagramDialog *dialog)
|
||||
{
|
||||
this->dialog = dialog;
|
||||
}
|
||||
|
||||
void EyeDiagramPlot::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
auto &pref = Preferences::getInstance();
|
||||
QPainter p(this);
|
||||
p.setBackground(QBrush(pref.Graphs.Color.background));
|
||||
p.fillRect(0, 0, width(), height(), QBrush(pref.Graphs.Color.background));
|
||||
if(!dialog) {
|
||||
return;
|
||||
}
|
||||
for(unsigned int i=0;i<width();i++) {
|
||||
for(unsigned int j=0;j<height();j++) {
|
||||
auto value = dialog->getIntensity(i, j);
|
||||
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(i, j);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
#ifndef EYEDIAGRAMDIALOG_H
|
||||
#define EYEDIAGRAMDIALOG_H
|
||||
|
||||
#include "Traces/tracemodel.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);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
EyeDiagramDialog *dialog;
|
||||
};
|
||||
|
||||
class EyeDiagramDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EyeDiagramDialog(TraceModel &model);
|
||||
~EyeDiagramDialog();
|
||||
|
||||
double getIntensity(unsigned int x, unsigned int y);
|
||||
|
||||
public slots:
|
||||
bool triggerUpdate();
|
||||
bool update(unsigned int width, unsigned int height);
|
||||
|
||||
signals:
|
||||
void updateDone();
|
||||
|
||||
private:
|
||||
signals:
|
||||
void updatePercent(int percent);
|
||||
|
||||
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;
|
||||
|
||||
bool updating;
|
||||
};
|
||||
|
||||
#endif // EYEDIAGRAMDIALOG_H
|
264
Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.ui
Normal file
264
Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.ui
Normal file
@ -0,0 +1,264 @@
|
||||
<?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>909</width>
|
||||
<height>544</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="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>High level:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="highLevel"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Low level:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="SIUnitEdit" name="lowLevel"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Noise (RMS):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="SIUnitEdit" name="noise"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Jitter (RMS):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="SIUnitEdit" name="jitter"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Pattern length:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" 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>
|
||||
</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="QProgressBar" name="progress">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</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>
|
@ -450,8 +450,8 @@
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="lGroup"/>
|
||||
<buttongroup name="zGroup"/>
|
||||
<buttongroup name="cGroup"/>
|
||||
<buttongroup name="lGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
@ -16,7 +16,7 @@ class TDRThread : public QThread
|
||||
Q_OBJECT
|
||||
public:
|
||||
TDRThread(TDR &tdr);
|
||||
~TDRThread(){};
|
||||
~TDRThread(){}
|
||||
private:
|
||||
void run() override;
|
||||
TDR &tdr;
|
||||
@ -38,7 +38,7 @@ public:
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
Type getType() override {return Type::TDR;};
|
||||
Type getType() override {return Type::TDR;}
|
||||
|
||||
enum class Mode {
|
||||
Lowpass,
|
||||
|
@ -101,13 +101,13 @@ public:
|
||||
// indicate whether this function produces time or frequency domain data
|
||||
virtual DataType outputType(DataType inputType) = 0;
|
||||
virtual QString description() = 0;
|
||||
virtual void edit(){};
|
||||
virtual void edit(){}
|
||||
|
||||
void removeInput();
|
||||
void assignInput(TraceMath *input);
|
||||
|
||||
DataType getDataType() const;
|
||||
std::vector<Data>& rData() { return data;};
|
||||
std::vector<Data>& rData() { return data;}
|
||||
Status getStatus() const;
|
||||
QString getStatusDescription() const;
|
||||
virtual Type getType() = 0;
|
||||
@ -119,7 +119,7 @@ public:
|
||||
|
||||
public slots:
|
||||
// some values of the input data have changed, begin/end determine which sample(s) has changed
|
||||
virtual void inputSamplesChanged(unsigned int begin, unsigned int end){Q_UNUSED(begin) Q_UNUSED(end)};
|
||||
virtual void inputSamplesChanged(unsigned int begin, unsigned int end){Q_UNUSED(begin) Q_UNUSED(end)}
|
||||
|
||||
void inputTypeChanged(DataType type);
|
||||
|
||||
|
@ -241,7 +241,7 @@ void TraceWaterfall::draw(QPainter &p)
|
||||
}
|
||||
p.drawRect(legendRect);
|
||||
for(int i=plotAreaTop + 1;i<plotAreaBottom;i++) {
|
||||
auto color = getColor(Util::Scale<double>(i, plotAreaTop, plotAreaBottom, 1.0, 0.0));
|
||||
auto color = Util::getIntensityGradeColor(Util::Scale<double>(i, plotAreaTop, plotAreaBottom, 1.0, 0.0));
|
||||
p.setPen(QColor(color));
|
||||
pen.setCosmetic(true);
|
||||
p.drawLine(legendRect.x()+1, i, legendRect.x()+legendRect.width()-1, i);
|
||||
@ -387,7 +387,7 @@ void TraceWaterfall::draw(QPainter &p)
|
||||
}
|
||||
x_stop = xAxis.transform(x_stop, plotAreaLeft, plotAreaLeft + plotAreaWidth);
|
||||
auto y = yAxis.sampleToCoordinate(sweep[s]);
|
||||
auto color = getColor(yAxis.transform(y, 0.0, 1.0));
|
||||
auto color = Util::getIntensityGradeColor(yAxis.transform(y, 0.0, 1.0));
|
||||
auto rect = QRect(round(x_start), ytop, round(x_stop - x_start) + 1, ybottom - ytop + 1);
|
||||
p.fillRect(rect, QBrush(color));
|
||||
}
|
||||
@ -569,19 +569,6 @@ void TraceWaterfall::updateYAxis()
|
||||
}
|
||||
}
|
||||
|
||||
QColor TraceWaterfall::getColor(double scale)
|
||||
{
|
||||
if(scale < 0.0) {
|
||||
return Qt::black;
|
||||
} else if(scale > 1.0) {
|
||||
return Qt::white;
|
||||
} else if(scale >= 0.0 && scale <= 1.0) {
|
||||
return QColor::fromHsv(Util::Scale<double>(scale, 0.0, 1.0, 240, 0), 255, 255);
|
||||
} else {
|
||||
return Qt::black;
|
||||
}
|
||||
}
|
||||
|
||||
QString TraceWaterfall::AlignmentToString(Alignment a)
|
||||
{
|
||||
switch(a) {
|
||||
|
@ -44,9 +44,6 @@ protected slots:
|
||||
private slots:
|
||||
void updateYAxis();
|
||||
private:
|
||||
// color scale, input value from 0.0 to 1.0
|
||||
QColor getColor(double scale);
|
||||
|
||||
enum class Direction {
|
||||
TopToBottom,
|
||||
BottomToTop,
|
||||
|
43
Software/PC_Application/LibreVNA-GUI/Util/prbs.cpp
Normal file
43
Software/PC_Application/LibreVNA-GUI/Util/prbs.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "prbs.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
PRBS::PRBS(unsigned int bits)
|
||||
{
|
||||
this->bits = bits;
|
||||
|
||||
// from https://www.eetimes.com/tutorial-linear-feedback-shift-registers-lfsrs-part-1/
|
||||
const std::array<unsigned int, 12> polynoms = {{
|
||||
0x00000000,
|
||||
0x00000000,
|
||||
0x00000003,
|
||||
0x00000005,
|
||||
0x00000009,
|
||||
0x00000012,
|
||||
0x00000021,
|
||||
0x00000041,
|
||||
0x0000008E,
|
||||
0x00000108,
|
||||
0x00000204,
|
||||
0x00000402,
|
||||
}};
|
||||
if(bits < 2 || bits >= polynoms.size()) {
|
||||
throw std::runtime_error("Bit size not supported");
|
||||
}
|
||||
polynom = polynoms[bits];
|
||||
shiftReg = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
bool PRBS::next()
|
||||
{
|
||||
bool newbit = false;
|
||||
unsigned int mask = 0x01;
|
||||
for(unsigned int i=0;i<bits;i++) {
|
||||
if(polynom & mask & shiftReg) {
|
||||
newbit = !newbit;
|
||||
}
|
||||
mask <<= 1;
|
||||
}
|
||||
shiftReg = (shiftReg << 1) | (newbit ? 0x01 : 0x00);
|
||||
return newbit;
|
||||
}
|
18
Software/PC_Application/LibreVNA-GUI/Util/prbs.h
Normal file
18
Software/PC_Application/LibreVNA-GUI/Util/prbs.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef PRBS_H
|
||||
#define PRBS_H
|
||||
|
||||
|
||||
class PRBS
|
||||
{
|
||||
public:
|
||||
PRBS(unsigned int bits);
|
||||
|
||||
bool next();
|
||||
|
||||
private:
|
||||
unsigned int bits;
|
||||
unsigned int shiftReg;
|
||||
unsigned int polynom;
|
||||
};
|
||||
|
||||
#endif // PRBS_H
|
@ -188,3 +188,16 @@ std::complex<double> Util::addTransmissionLine(std::complex<double> termination_
|
||||
|
||||
return Gamma_i;
|
||||
}
|
||||
|
||||
QColor Util::getIntensityGradeColor(double intensity)
|
||||
{
|
||||
if(intensity < 0.0) {
|
||||
return Qt::black;
|
||||
} else if(intensity > 1.0) {
|
||||
return Qt::white;
|
||||
} else if(intensity >= 0.0 && intensity <= 1.0) {
|
||||
return QColor::fromHsv(Util::Scale<double>(intensity, 0.0, 1.0, 240, 0), 255, 255);
|
||||
} else {
|
||||
return Qt::black;
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,9 @@ namespace Util {
|
||||
result1 = (-b + root) / (T(2) * a);
|
||||
result2 = (-b - root) / (T(2) * a);
|
||||
}
|
||||
|
||||
// intensity color scale, input value from 0.0 to 1.0
|
||||
QColor getIntensityGradeColor(double intensity);
|
||||
}
|
||||
|
||||
#endif // UTILH_H
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "CustomWidgets/siunitedit.h"
|
||||
#include "Traces/Marker/markerwidget.h"
|
||||
#include "Tools/impedancematchdialog.h"
|
||||
#include "Tools/eyediagramdialog.h"
|
||||
#include "ui_main.h"
|
||||
#include "Device/firmwareupdatedialog.h"
|
||||
#include "preferences.h"
|
||||
@ -182,6 +183,8 @@ VNA::VNA(AppWindow *window, QString name)
|
||||
actions.insert(toolsMenu->menuAction());
|
||||
auto impedanceMatching = toolsMenu->addAction("Impedance Matching");
|
||||
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);
|
||||
assignDefaultCal = defaultCalMenu->addAction("Assign...");
|
||||
@ -952,6 +955,14 @@ void VNA::StartImpedanceMatching()
|
||||
}
|
||||
}
|
||||
|
||||
void VNA::StartEyeDiagram()
|
||||
{
|
||||
auto dialog = new EyeDiagramDialog(traceModel);
|
||||
if(AppWindow::showGUI()) {
|
||||
dialog->show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void VNA::SetSweepType(SweepType sw)
|
||||
{
|
||||
|
@ -81,6 +81,7 @@ public slots:
|
||||
private slots:
|
||||
void NewDatapoint(VirtualDevice::VNAMeasurement m);
|
||||
void StartImpedanceMatching();
|
||||
void StartEyeDiagram();
|
||||
// Sweep control
|
||||
void SetSweepType(SweepType sw);
|
||||
void SetStartFreq(double freq);
|
||||
|
@ -40,6 +40,7 @@ SOURCES += \
|
||||
../LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp \
|
||||
../LibreVNA-GUI/SpectrumAnalyzer/tracewidgetsa.cpp \
|
||||
../LibreVNA-GUI/Tools/eseries.cpp \
|
||||
../LibreVNA-GUI/Tools/eyediagramdialog.cpp \
|
||||
../LibreVNA-GUI/Tools/impedancematchdialog.cpp \
|
||||
../LibreVNA-GUI/Tools/parameters.cpp \
|
||||
../LibreVNA-GUI/Traces/Marker/marker.cpp \
|
||||
@ -108,6 +109,7 @@ SOURCES += \
|
||||
../LibreVNA-GUI/Traces/tracexyplot.cpp \
|
||||
../LibreVNA-GUI/Traces/waterfallaxisdialog.cpp \
|
||||
../LibreVNA-GUI/Traces/xyplotaxisdialog.cpp \
|
||||
../LibreVNA-GUI/Util/prbs.cpp \
|
||||
../LibreVNA-GUI/Util/util.cpp \
|
||||
../LibreVNA-GUI/VNA/Deembedding/deembedding.cpp \
|
||||
../LibreVNA-GUI/VNA/Deembedding/deembeddingdialog.cpp \
|
||||
@ -201,6 +203,7 @@ HEADERS += \
|
||||
../LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.h \
|
||||
../LibreVNA-GUI/SpectrumAnalyzer/tracewidgetsa.h \
|
||||
../LibreVNA-GUI/Tools/eseries.h \
|
||||
../LibreVNA-GUI/Tools/eyediagramdialog.h \
|
||||
../LibreVNA-GUI/Tools/impedancematchdialog.h \
|
||||
../LibreVNA-GUI/Tools/parameters.h \
|
||||
../LibreVNA-GUI/Traces/Marker/marker.h \
|
||||
@ -280,6 +283,7 @@ HEADERS += \
|
||||
../LibreVNA-GUI/Traces/tracexyplot.h \
|
||||
../LibreVNA-GUI/Traces/waterfallaxisdialog.h \
|
||||
../LibreVNA-GUI/Traces/xyplotaxisdialog.h \
|
||||
../LibreVNA-GUI/Util/prbs.h \
|
||||
../LibreVNA-GUI/Util/util.h \
|
||||
../LibreVNA-GUI/VNA/Deembedding/deembedding.h \
|
||||
../LibreVNA-GUI/VNA/Deembedding/deembeddingdialog.h \
|
||||
@ -339,6 +343,7 @@ FORMS += \
|
||||
../LibreVNA-GUI/Device/firmwareupdatedialog.ui \
|
||||
../LibreVNA-GUI/Device/manualcontroldialog.ui \
|
||||
../LibreVNA-GUI/Generator/signalgenwidget.ui \
|
||||
../LibreVNA-GUI/Tools/eyediagramdialog.ui \
|
||||
../LibreVNA-GUI/Tools/impedancematchdialog.ui \
|
||||
../LibreVNA-GUI/Traces/Marker/markerwidget.ui \
|
||||
../LibreVNA-GUI/Traces/Math/dftdialog.ui \
|
||||
|
Loading…
Reference in New Issue
Block a user