partial eye diagram implementation

This commit is contained in:
Jan Käberich 2022-10-19 17:31:14 +02:00
parent 9a198217b8
commit c7a99af820
17 changed files with 7404 additions and 25 deletions

View File

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

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

View File

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

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

View File

@ -450,8 +450,8 @@
</connection>
</connections>
<buttongroups>
<buttongroup name="lGroup"/>
<buttongroup name="zGroup"/>
<buttongroup name="cGroup"/>
<buttongroup name="lGroup"/>
</buttongroups>
</ui>

View File

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

View File

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

View File

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

View File

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

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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