different marker types for VNA/SA, added TOI measurement

This commit is contained in:
Jan Käberich 2020-10-23 14:12:45 +02:00
parent 398db2253d
commit 41f99148b7
6 changed files with 203 additions and 80 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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