different marker types for VNA/SA, added TOI measurement
This commit is contained in:
parent
398db2253d
commit
41f99148b7
@ -193,7 +193,7 @@ bool Trace::isCalibration()
|
||||
|
||||
bool Trace::isLive()
|
||||
{
|
||||
return !isCalibration() && !isTouchstone() && !isPaused();
|
||||
return !isCalibration() && !isTouchstone();
|
||||
}
|
||||
|
||||
bool Trace::isReflection()
|
||||
@ -216,6 +216,57 @@ double Trace::findExtremumFreq(bool max)
|
||||
return freq;
|
||||
}
|
||||
|
||||
std::vector<double> Trace::findPeakFrequencies(unsigned int maxPeaks, double minLevel, double minValley)
|
||||
{
|
||||
using peakInfo = struct peakinfo {
|
||||
double frequency;
|
||||
double level_dbm;
|
||||
};
|
||||
vector<peakInfo> peaks;
|
||||
double frequency = 0.0;
|
||||
double max_dbm = -200.0;
|
||||
double min_dbm = 200.0;
|
||||
for(auto d : _data) {
|
||||
double dbm = 20*log10(abs(d.S));
|
||||
if((dbm >= max_dbm) && (min_dbm <= dbm - minValley)) {
|
||||
// potential peak frequency
|
||||
frequency = d.frequency;
|
||||
max_dbm = dbm;
|
||||
}
|
||||
if(dbm <= min_dbm) {
|
||||
min_dbm = dbm;
|
||||
}
|
||||
if((dbm <= max_dbm - minValley) && (max_dbm >= minLevel)) {
|
||||
// peak was high enough and dropped below minValley afterwards
|
||||
peakInfo peak;
|
||||
peak.frequency = frequency;
|
||||
peak.level_dbm = max_dbm;
|
||||
peaks.push_back(peak);
|
||||
// reset
|
||||
frequency = 0.0;
|
||||
max_dbm = min_dbm = dbm;
|
||||
}
|
||||
}
|
||||
if(peaks.size() > maxPeaks) {
|
||||
// found more peaks than requested, remove excess peaks
|
||||
// sort with descending peak level
|
||||
sort(peaks.begin(), peaks.end(), [](peakInfo higher, peakInfo lower) {
|
||||
return higher.level_dbm >= lower.level_dbm;
|
||||
});
|
||||
// only keep the requested number of peaks
|
||||
peaks.resize(maxPeaks);
|
||||
// sort again with ascending frequencies
|
||||
sort(peaks.begin(), peaks.end(), [](peakInfo lower, peakInfo higher) {
|
||||
return higher.frequency >= lower.frequency;
|
||||
});
|
||||
}
|
||||
vector<double> frequencies;
|
||||
for(auto p : peaks) {
|
||||
frequencies.push_back(p.frequency);
|
||||
}
|
||||
return frequencies;
|
||||
}
|
||||
|
||||
QString Trace::getTouchstoneFilename() const
|
||||
{
|
||||
return touchstoneFilename;
|
||||
|
@ -61,6 +61,12 @@ public:
|
||||
double minFreq() { return _data.front().frequency; };
|
||||
double maxFreq() { return _data.back().frequency; };
|
||||
double findExtremumFreq(bool max);
|
||||
/* Searches for peaks in the trace data and returns the peak frequencies in ascending order.
|
||||
* Up to maxPeaks will be returned, with higher level peaks taking priority over lower level peaks.
|
||||
* Only peaks with at least minLevel will be considered.
|
||||
* To detect the next peak, the signal first has to drop at least minValley below the peak level.
|
||||
*/
|
||||
std::vector<double> findPeakFrequencies(unsigned int maxPeaks = 100, double minLevel = -100.0, double minValley = 3.0);
|
||||
Data sample(unsigned int index) { return _data.at(index); }
|
||||
QString getTouchstoneFilename() const;
|
||||
unsigned int getTouchstoneParameter() const;
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include "tracemarkermodel.h"
|
||||
#include "unit.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TraceMarker::TraceMarker(TraceMarkerModel *model, int number)
|
||||
: editingFrequeny(false),
|
||||
model(model),
|
||||
@ -41,6 +43,11 @@ void TraceMarker::assignTrace(Trace *t)
|
||||
disconnect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
|
||||
}
|
||||
parentTrace = t;
|
||||
if(!getSupportedTypes().count(type)) {
|
||||
// new trace does not support the current type
|
||||
setType(Type::Manual);
|
||||
}
|
||||
|
||||
connect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted);
|
||||
connect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
|
||||
connect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
|
||||
@ -64,9 +71,8 @@ QString TraceMarker::readableData()
|
||||
case Type::Manual:
|
||||
case Type::Maximum:
|
||||
case Type::Minimum: {
|
||||
auto db = 20*log10(abs(data));
|
||||
auto phase = arg(data);
|
||||
return QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
|
||||
return QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
|
||||
}
|
||||
case Type::Delta:
|
||||
if(!delta) {
|
||||
@ -75,17 +81,16 @@ QString TraceMarker::readableData()
|
||||
// calculate difference between markers
|
||||
auto freqDiff = frequency - delta->frequency;
|
||||
auto valueDiff = data / delta->data;
|
||||
auto db = 20*log10(abs(valueDiff));
|
||||
auto phase = arg(valueDiff);
|
||||
return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
|
||||
return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
|
||||
}
|
||||
case Type::Lowpass:
|
||||
case Type::Highpass:
|
||||
if(parentTrace->isReflection()) {
|
||||
return "Calculation not possible with reflection measurement";
|
||||
} else {
|
||||
auto insertionLoss = 20*log10(abs(data));
|
||||
auto cutoff = 20*log10(abs(helperMarkers[0]->data));
|
||||
auto insertionLoss = toDecibel();
|
||||
auto cutoff = helperMarkers[0]->toDecibel();
|
||||
QString ret = "fc: ";
|
||||
if(cutoff > insertionLoss + cutoffAmplitude) {
|
||||
// the trace never dipped below the specified cutoffAmplitude, exact cutoff frequency unknown
|
||||
@ -100,9 +105,9 @@ QString TraceMarker::readableData()
|
||||
if(parentTrace->isReflection()) {
|
||||
return "Calculation not possible with reflection measurement";
|
||||
} else {
|
||||
auto insertionLoss = 20*log10(abs(data));
|
||||
auto cutoffL = 20*log10(abs(helperMarkers[0]->data));
|
||||
auto cutoffH = 20*log10(abs(helperMarkers[1]->data));
|
||||
auto insertionLoss = toDecibel();
|
||||
auto cutoffL = helperMarkers[0]->toDecibel();
|
||||
auto cutoffH = helperMarkers[1]->toDecibel();
|
||||
auto bandwidth = helperMarkers[1]->frequency - helperMarkers[0]->frequency;
|
||||
auto center = helperMarkers[2]->frequency;
|
||||
QString ret = "fc: ";
|
||||
@ -117,6 +122,13 @@ QString TraceMarker::readableData()
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case Type::TOI: {
|
||||
auto avgFundamental = (toDecibel() + helperMarkers[0]->toDecibel()) / 2;
|
||||
auto avgDistortion = (helperMarkers[1]->toDecibel() + helperMarkers[2]->toDecibel()) / 2;
|
||||
auto TOI = (3 * avgFundamental - avgDistortion) / 2;
|
||||
return "Fundamental: " + Unit::ToString(avgFundamental, "dbm", " ", 3) + ", distortion: " + Unit::ToString(avgDistortion, "dbm", " ", 3) + ", TOI: "+Unit::ToString(TOI, "dbm", " ", 3);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return "Unknown marker type";
|
||||
}
|
||||
@ -134,8 +146,10 @@ QString TraceMarker::readableSettings()
|
||||
case Type::Highpass:
|
||||
case Type::Bandpass:
|
||||
return Unit::ToString(cutoffAmplitude, "db", " ", 3);
|
||||
case Type::TOI:
|
||||
return "none";
|
||||
default:
|
||||
return "Invalid";
|
||||
return "Unhandled case";
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +195,37 @@ void TraceMarker::updateSymbol()
|
||||
emit symbolChanged(this);
|
||||
}
|
||||
|
||||
std::set<TraceMarker::Type> TraceMarker::getSupportedTypes()
|
||||
{
|
||||
set<TraceMarker::Type> supported;
|
||||
if(parentTrace) {
|
||||
// all traces support some basic markers
|
||||
supported.insert(Type::Manual);
|
||||
supported.insert(Type::Maximum);
|
||||
supported.insert(Type::Minimum);
|
||||
supported.insert(Type::Delta);
|
||||
if(parentTrace->isLive()) {
|
||||
switch(parentTrace->liveParameter()) {
|
||||
case Trace::LiveParameter::S11:
|
||||
case Trace::LiveParameter::S12:
|
||||
case Trace::LiveParameter::S21:
|
||||
case Trace::LiveParameter::S22:
|
||||
// special VNA marker types
|
||||
supported.insert(Type::Lowpass);
|
||||
supported.insert(Type::Highpass);
|
||||
supported.insert(Type::Bandpass);
|
||||
break;
|
||||
case Trace::LiveParameter::Port1:
|
||||
case Trace::LiveParameter::Port2:
|
||||
// special SA marker types
|
||||
supported.insert(Type::TOI);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
void TraceMarker::constrainFrequency()
|
||||
{
|
||||
if(parentTrace && parentTrace->size() > 0) {
|
||||
@ -217,6 +262,64 @@ void TraceMarker::deleteHelperMarkers()
|
||||
helperMarkers.clear();
|
||||
}
|
||||
|
||||
void TraceMarker::setType(TraceMarker::Type t)
|
||||
{
|
||||
// remove any potential helper markers
|
||||
deleteHelperMarkers();
|
||||
type = t;
|
||||
vector<QString> helperSuffixes;
|
||||
switch(type) {
|
||||
case Type::Delta:
|
||||
if(!delta) {
|
||||
// invalid delta marker assigned, attempt to find a matching marker
|
||||
for(int pass = 0;pass < 3;pass++) {
|
||||
for(auto m : model->getMarkers()) {
|
||||
if(pass == 0 && m->parentTrace != parentTrace) {
|
||||
// ignore markers on different traces in first pass
|
||||
continue;
|
||||
}
|
||||
if(pass <= 1 && m == this) {
|
||||
// ignore itself on second pass
|
||||
continue;
|
||||
}
|
||||
assignDeltaMarker(m);
|
||||
break;
|
||||
}
|
||||
if(delta) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Type::Lowpass:
|
||||
case Type::Highpass:
|
||||
helperSuffixes = {"c"};
|
||||
break;
|
||||
case Type::Bandpass:
|
||||
helperSuffixes = {"l", "h" ,"c"};
|
||||
break;
|
||||
case Type::TOI:
|
||||
helperSuffixes = {"p", "l", "r"};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// create helper markers
|
||||
for(auto suffix : helperSuffixes) {
|
||||
auto helper = new TraceMarker(model);
|
||||
helper->suffix = suffix;
|
||||
helper->number = number;
|
||||
helper->assignTrace(parentTrace);
|
||||
helperMarkers.push_back(helper);
|
||||
}
|
||||
emit typeChanged(this);
|
||||
update();
|
||||
}
|
||||
|
||||
double TraceMarker::toDecibel()
|
||||
{
|
||||
return 20*log10(abs(data));
|
||||
}
|
||||
|
||||
void TraceMarker::setNumber(int value)
|
||||
{
|
||||
number = value;
|
||||
@ -226,7 +329,7 @@ void TraceMarker::setNumber(int value)
|
||||
QWidget *TraceMarker::getTypeEditor(QAbstractItemDelegate *delegate)
|
||||
{
|
||||
auto c = new QComboBox;
|
||||
for(auto t : getTypes()) {
|
||||
for(auto t : getSupportedTypes()) {
|
||||
c->addItem(typeToString(t));
|
||||
if(type == t) {
|
||||
// select this item
|
||||
@ -288,69 +391,10 @@ void TraceMarker::updateTypeFromEditor(QWidget *w)
|
||||
} else {
|
||||
c = (QComboBox*) w;
|
||||
}
|
||||
for(auto t : getTypes()) {
|
||||
for(auto t : getSupportedTypes()) {
|
||||
if(c->currentText() == typeToString(t)) {
|
||||
if(type != t) {
|
||||
// remove any potential helper markers
|
||||
deleteHelperMarkers();
|
||||
type = t;
|
||||
switch(type) {
|
||||
case Type::Delta:
|
||||
if(!delta) {
|
||||
// invalid delta marker assigned, attempt to find a matching marker
|
||||
for(int pass = 0;pass < 3;pass++) {
|
||||
for(auto m : model->getMarkers()) {
|
||||
if(pass == 0 && m->parentTrace != parentTrace) {
|
||||
// ignore markers on different traces in first pass
|
||||
continue;
|
||||
}
|
||||
if(pass <= 1 && m == this) {
|
||||
// ignore itself on second pass
|
||||
continue;
|
||||
}
|
||||
assignDeltaMarker(m);
|
||||
break;
|
||||
}
|
||||
if(delta) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Type::Lowpass:
|
||||
case Type::Highpass: {
|
||||
// Create helper marker for cutoff frequency
|
||||
auto cutoff = new TraceMarker(model);
|
||||
cutoff->suffix = "c";
|
||||
// same trace as this one
|
||||
cutoff->assignTrace(parentTrace);
|
||||
helperMarkers.push_back(cutoff);
|
||||
}
|
||||
break;
|
||||
case Type::Bandpass: {
|
||||
// Create helper markers for cutoff frequency
|
||||
auto lower = new TraceMarker(model);
|
||||
lower->suffix = "l";
|
||||
// same trace as this one
|
||||
lower->assignTrace(parentTrace);
|
||||
helperMarkers.push_back(lower);
|
||||
auto higher = new TraceMarker(model);
|
||||
higher->suffix = "h";
|
||||
// same trace as this one
|
||||
higher->assignTrace(parentTrace);
|
||||
helperMarkers.push_back(higher);
|
||||
auto center = new TraceMarker(model);
|
||||
center->suffix = "c";
|
||||
// same trace as this one
|
||||
center->assignTrace(parentTrace);
|
||||
helperMarkers.push_back(center);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
emit typeChanged(this);
|
||||
update();
|
||||
setType(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,6 +412,8 @@ SIUnitEdit *TraceMarker::getSettingsEditor()
|
||||
case Type::Lowpass:
|
||||
case Type::Highpass:
|
||||
return new SIUnitEdit("db", " ");
|
||||
case Type::TOI:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,6 +530,22 @@ void TraceMarker::update()
|
||||
helperMarkers[2]->setFrequency((helperMarkers[0]->frequency + helperMarkers[1]->frequency) / 2);
|
||||
}
|
||||
break;
|
||||
case Type::TOI: {
|
||||
auto peaks = parentTrace->findPeakFrequencies(2);
|
||||
if(peaks.size() != 2) {
|
||||
// error finding peaks, do nothing
|
||||
break;
|
||||
}
|
||||
// assign marker frequenies:
|
||||
// this marker is the left peak, first helper the right peak.
|
||||
// 2nd and 3rd helpers are left and right TOI peaks
|
||||
setFrequency(peaks[0]);
|
||||
helperMarkers[0]->setFrequency(peaks[1]);
|
||||
auto freqDiff = peaks[1] - peaks[0];
|
||||
helperMarkers[1]->setFrequency(peaks[0] - freqDiff);
|
||||
helperMarkers[2]->setFrequency(peaks[1] + freqDiff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
emit dataChanged(this);
|
||||
}
|
||||
|
@ -64,10 +64,9 @@ private:
|
||||
Lowpass,
|
||||
Highpass,
|
||||
Bandpass,
|
||||
TOI,
|
||||
};
|
||||
static std::vector<Type> getTypes() {
|
||||
return {Type::Manual, Type::Maximum, Type::Minimum, Type::Delta, Type::Lowpass, Type::Highpass, Type::Bandpass};
|
||||
}
|
||||
std::set<Type> getSupportedTypes();
|
||||
static QString typeToString(Type t) {
|
||||
switch(t) {
|
||||
case Type::Manual: return "Manual";
|
||||
@ -77,12 +76,15 @@ private:
|
||||
case Type::Lowpass: return "Lowpass";
|
||||
case Type::Highpass: return "Highpass";
|
||||
case Type::Bandpass: return "Bandpass";
|
||||
case Type::TOI: return "TOI/IP3";
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
void constrainFrequency();
|
||||
void assignDeltaMarker(TraceMarker *m);
|
||||
void deleteHelperMarkers();
|
||||
void setType(Type t);
|
||||
double toDecibel();
|
||||
|
||||
TraceMarkerModel *model;
|
||||
Trace *parentTrace;
|
||||
|
@ -242,8 +242,10 @@ QWidget *MarkerSettingsDelegate::createEditor(QWidget *parent, const QStyleOptio
|
||||
auto marker = model->getMarkers()[index.row()];
|
||||
marker->editingFrequeny = true;
|
||||
auto e = marker->getSettingsEditor();
|
||||
e->setParent(parent);
|
||||
connect(e, &SIUnitEdit::valueUpdated, this, &MarkerSettingsDelegate::commitData);
|
||||
if(e) {
|
||||
e->setParent(parent);
|
||||
connect(e, &SIUnitEdit::valueUpdated, this, &MarkerSettingsDelegate::commitData);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ void TraceModel::clearVNAData()
|
||||
void TraceModel::addVNAData(Protocol::Datapoint d)
|
||||
{
|
||||
for(auto t : traces) {
|
||||
if (t->isLive()) {
|
||||
if (t->isLive() && !t->isPaused()) {
|
||||
Trace::Data td;
|
||||
td.frequency = d.frequency;
|
||||
switch(t->liveParameter()) {
|
||||
@ -160,7 +160,7 @@ void TraceModel::addVNAData(Protocol::Datapoint d)
|
||||
void TraceModel::addSAData(Protocol::SpectrumAnalyzerResult d)
|
||||
{
|
||||
for(auto t : traces) {
|
||||
if (t->isLive()) {
|
||||
if (t->isLive() && !t->isPaused()) {
|
||||
Trace::Data td;
|
||||
td.frequency = d.frequency;
|
||||
switch(t->liveParameter()) {
|
||||
|
Loading…
Reference in New Issue
Block a user