From 93bf7255c39dcdfcd46556317e77c03b185d384c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Sun, 22 Nov 2020 21:25:41 +0100 Subject: [PATCH] time domain markers --- .../SpectrumAnalyzer/spectrumanalyzer.cpp | 2 +- .../Tools/impedancematchdialog.cpp | 2 +- .../PC_Application/Traces/markerwidget.cpp | 2 + Software/PC_Application/Traces/trace.cpp | 60 +++ Software/PC_Application/Traces/trace.h | 7 + .../PC_Application/Traces/tracemarker.cpp | 367 ++++++++++++------ Software/PC_Application/Traces/tracemarker.h | 18 +- .../Traces/tracemarkermodel.cpp | 24 +- .../PC_Application/Traces/tracemarkermodel.h | 13 + Software/PC_Application/Traces/tracemodel.cpp | 3 + Software/PC_Application/Traces/tracemodel.h | 1 + Software/PC_Application/Traces/traceplot.cpp | 29 +- Software/PC_Application/Traces/traceplot.h | 3 +- .../PC_Application/Traces/tracesmithchart.cpp | 38 +- .../PC_Application/Traces/tracesmithchart.h | 4 +- .../PC_Application/Traces/tracexyplot.cpp | 112 +++++- Software/PC_Application/Traces/tracexyplot.h | 5 +- 17 files changed, 505 insertions(+), 185 deletions(-) diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index b17e069..629d31b 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -617,7 +617,7 @@ void SpectrumAnalyzer::ConstrainAndUpdateFrequencies() trackingOffset_limited = true; settings.trackingGeneratorOffset = Device::Info().limits_maxFreq - settings.f_stop; } - if(settings.f_start + settings.trackingGeneratorOffset < Device::Info().limits_minFreq) { + if((long) settings.f_start + settings.trackingGeneratorOffset < (long) Device::Info().limits_minFreq) { trackingOffset_limited = true; settings.trackingGeneratorOffset = Device::Info().limits_minFreq - settings.f_start; } diff --git a/Software/PC_Application/Tools/impedancematchdialog.cpp b/Software/PC_Application/Tools/impedancematchdialog.cpp index dd45a79..6ed92b5 100644 --- a/Software/PC_Application/Tools/impedancematchdialog.cpp +++ b/Software/PC_Application/Tools/impedancematchdialog.cpp @@ -70,7 +70,7 @@ void ImpedanceMatchDialog::on_cSource_currentIndexChanged(int index) auto reflection = Z0 * (1.0 + data) / (1.0 - data); ui->zReal->setValue(reflection.real()); ui->zImag->setValue(reflection.imag()); - ui->zFreq->setValue(m->getFrequency()); + ui->zFreq->setValue(m->getPosition()); } } diff --git a/Software/PC_Application/Traces/markerwidget.cpp b/Software/PC_Application/Traces/markerwidget.cpp index 7a9bd96..18ca77e 100644 --- a/Software/PC_Application/Traces/markerwidget.cpp +++ b/Software/PC_Application/Traces/markerwidget.cpp @@ -19,6 +19,7 @@ MarkerWidget::MarkerWidget(TraceMarkerModel &model, QWidget *parent) : connect(&model.getModel(), &TraceModel::traceAdded, this, &MarkerWidget::updatePersistentEditors); connect(&model.getModel(), &TraceModel::traceRemoved, this, &MarkerWidget::updatePersistentEditors); connect(&model.getModel(), &TraceModel::traceNameChanged, this, &MarkerWidget::updatePersistentEditors); + connect(&model.getModel(), &TraceModel::traceTDRstateChanged, this, &MarkerWidget::updatePersistentEditors); } MarkerWidget::~MarkerWidget() @@ -55,6 +56,7 @@ void MarkerWidget::on_bAdd_clicked() { auto marker = model.createDefaultMarker(); connect(marker, &TraceMarker::typeChanged, this, &MarkerWidget::updatePersistentEditors); + connect(marker, &TraceMarker::timeDomainChanged, this, &MarkerWidget::updatePersistentEditors); model.addMarker(marker); updatePersistentEditors(); } diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index 4a900fb..5f089c9 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -215,6 +215,11 @@ void Trace::updateTimeDomainData() } t.impulseResponse = real(frequencyDomain[i]) / fft_bins; t.stepResponse = last_step; + if(abs(t.stepResponse) < 1.0) { + t.impedance = 50.0 * (1+t.stepResponse) / (1-t.stepResponse); + } else { + t.impedance = numeric_limits::quiet_NaN(); + } last_step += t.impulseResponse; timeDomain.push_back(t); } @@ -236,12 +241,67 @@ void Trace::addTDRinterest() updateTimeDomainData(); } tdr_users++; + if(tdr_users == 1) { + emit changedTDRstate(true); + } } void Trace::removeTDRinterest() { if(tdr_users > 0) { tdr_users--; + if(tdr_users == 0) { + emit changedTDRstate(false); + } + } +} + +Trace::TimedomainData Trace::getTDR(double position) +{ + TimedomainData ret = {}; + if(!TDRactive() || position < 0) { + return ret; + } + int index = 0; + bool exact = false; + double alpha = 0.0; + if(position <= timeDomain.back().time) { + auto lower = lower_bound(timeDomain.begin(), timeDomain.end(), position, [](const TimedomainData &lhs, const double pos) -> bool { + return lhs.time < pos; + }); + index = lower - timeDomain.begin(); + if(timeDomain.at(index).time == position) { + exact = true; + } else { + alpha = (position - timeDomain.at(index-1).time) / (timeDomain.at(index).time - timeDomain.at(index-1).time); + } + } else { + if(position > timeDomain.back().distance) { + // too high, invalid position + return ret; + } + auto lower = lower_bound(timeDomain.begin(), timeDomain.end(), position, [](const TimedomainData &lhs, const double pos) -> bool { + return lhs.distance < pos; + }); + index = lower - timeDomain.begin(); + if(timeDomain.at(index).distance == position) { + exact = true; + } else { + alpha = (position - timeDomain.at(index-1).distance) / (timeDomain.at(index).distance - timeDomain.at(index-1).distance); + } + } + if(exact) { + return timeDomain.at(index); + } else { + // need to interpolate + auto low = timeDomain.at(index-1); + auto high = timeDomain.at(index); + ret.time = low.time * (1.0 - alpha) + high.time * alpha; + ret.distance = low.distance * (1.0 - alpha) + high.distance * alpha; + ret.stepResponse = low.stepResponse * (1.0 - alpha) + high.stepResponse * alpha; + ret.impulseResponse = low.impulseResponse * (1.0 - alpha) + high.impulseResponse * alpha; + ret.impedance = low.impedance * (1.0 - alpha) + high.impedance * alpha; + return ret; } } diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index 523e712..2584bf7 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -28,6 +28,7 @@ public: double distance; double impulseResponse; double stepResponse; + double impedance; }; enum class LiveParameter { @@ -95,7 +96,12 @@ public: // The data is only updated at the end of a sweep and upon the first addTDRinterest() call. void addTDRinterest(); void removeTDRinterest(); + bool TDRactive() { return tdr_users > 0;}; const std::vector& getTDR() { return timeDomain;} + // interpolates the TDR data + // position is assumed to be the delay if it is smaller than the maximum sampled delay, otherwise it is assumed to be the distance. + // Since the usual delay values are way smaller than the distance values this should work + TimedomainData getTDR(double position); public slots: void setTouchstoneParameter(int value); @@ -117,6 +123,7 @@ signals: void colorChanged(Trace *t); void markerAdded(TraceMarker *m); void markerRemoved(TraceMarker *m); + void changedTDRstate(bool enabled); private: void updateTimeDomainData(); diff --git a/Software/PC_Application/Traces/tracemarker.cpp b/Software/PC_Application/Traces/tracemarker.cpp index 7079158..70df22d 100644 --- a/Software/PC_Application/Traces/tracemarker.cpp +++ b/Software/PC_Application/Traces/tracemarker.cpp @@ -14,7 +14,7 @@ TraceMarker::TraceMarker(TraceMarkerModel *model, int number, TraceMarker *paren : editingFrequeny(false), model(model), parentTrace(nullptr), - frequency(1000000000), + position(1000000000), number(number), data(0), type(Type::Manual), @@ -35,6 +35,35 @@ TraceMarker::~TraceMarker() emit deleted(this); } +void TraceMarker::setTimeDomain(bool timeDomain) +{ + if(timeDomain != this->timeDomain) { + // setting changed, check if actually available + if(timeDomain && !parentTrace->TDRactive()) { + qWarning() << "Attempted to enable TDR marker on trace without active TDR"; + return; + } + this->timeDomain = timeDomain; + // TODO handle changed setting and everything + if(timeDomain) { + // need to delete this marker if the TDR data of the trace is no longer available + connect(parentTrace, &Trace::changedTDRstate, [=](bool tdr_available){ + if(!tdr_available) { + delete this; + } + }); + // check if current type still supported + if(!getSupportedTypes().count(type)) { + // unsupported type, change to manual + setType(Type::Manual); + } + } else { + disconnect(parentTrace, &Trace::changedTDRstate, nullptr, nullptr); + } + emit timeDomainChanged(); + } +} + void TraceMarker::assignTrace(Trace *t) { if(parentTrace) { @@ -44,6 +73,7 @@ void TraceMarker::assignTrace(Trace *t) disconnect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged); disconnect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol); } + setTimeDomain(false); parentTrace = t; if(!getSupportedTypes().count(type)) { // new trace does not support the current type @@ -53,7 +83,7 @@ void TraceMarker::assignTrace(Trace *t) connect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted); connect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged); connect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol); - constrainFrequency(); + constrainPosition(); updateSymbol(); parentTrace->addMarker(this); for(auto m : helperMarkers) { @@ -73,24 +103,49 @@ QString TraceMarker::readableData() switch(type) { case Type::Manual: case Type::Maximum: - case Type::Minimum: { - auto phase = arg(data); - return QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4); - } + case Type::Minimum: + if(isTimeDomain()) { + QString ret; + ret += "Impulse:"+Unit::ToString(timeData.impulseResponse, "", "m ", 3)+" Step:"+Unit::ToString(timeData.stepResponse, "", "m ", 3)+" Impedance:"; + if(isnan(timeData.impedance)) { + ret += "Invalid"; + } else { + ret += Unit::ToString(timeData.impedance, "Ω", "m k", 3); + } + return ret; + } else { + auto phase = arg(data); + return QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4); + } case Type::Delta: - if(!delta) { + if(!delta || delta->isTimeDomain() != isTimeDomain()) { return "Invalid delta marker"; } else { - // calculate difference between markers - auto freqDiff = frequency - delta->frequency; - auto valueDiff = data / delta->data; - auto phase = arg(valueDiff); - auto db = 20*log10(abs(valueDiff)); - return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4); + if(isTimeDomain()) { + // calculate difference between markers + auto impulse = timeData.impulseResponse - delta->timeData.impulseResponse; + auto step = timeData.stepResponse - delta->timeData.stepResponse; + auto impedance = timeData.impedance - delta->timeData.impedance; + QString ret; + ret += "ΔImpulse:"+Unit::ToString(impulse, "", "m ", 3)+" ΔStep:"+Unit::ToString(step, "", "m ", 3)+" ΔImpedance:"; + if(isnan(timeData.impedance)) { + ret += "Invalid"; + } else { + ret += Unit::ToString(impedance, "Ω", "m k", 3); + } + return ret; + } else { + // calculate difference between markers + auto freqDiff = position - delta->position; + auto valueDiff = data / delta->data; + auto phase = arg(valueDiff); + auto db = 20*log10(abs(valueDiff)); + return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4); + } } break; case Type::Noise: - return Unit::ToString(parentTrace->getNoise(frequency), "dbm/Hz", " ", 3); + return Unit::ToString(parentTrace->getNoise(position), "dbm/Hz", " ", 3); case Type::PeakTable: return "Found " + QString::number(helperMarkers.size()) + " peaks"; case Type::Lowpass: @@ -105,7 +160,7 @@ QString TraceMarker::readableData() // the trace never dipped below the specified cutoffAmplitude, exact cutoff frequency unknown ret += type == Type::Lowpass ? ">" : "<"; } - ret += Unit::ToString(helperMarkers[0]->frequency, "Hz", " kMG", 4); + ret += Unit::ToString(helperMarkers[0]->position, "Hz", " kMG", 4); ret += ", Ins.Loss: >=" + QString::number(-insertionLoss, 'g', 4) + "db"; return ret; } @@ -117,8 +172,8 @@ QString TraceMarker::readableData() 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; + auto bandwidth = helperMarkers[1]->position - helperMarkers[0]->position; + auto center = helperMarkers[2]->position; QString ret = "fc: "; if(cutoffL > insertionLoss + cutoffAmplitude || cutoffH > insertionLoss + cutoffAmplitude) { // the trace never dipped below the specified cutoffAmplitude, center and exact bandwidth unknown @@ -140,8 +195,8 @@ QString TraceMarker::readableData() break; case Type::PhaseNoise: { auto carrier = toDecibel(); - auto phasenoise = parentTrace->getNoise(helperMarkers[0]->frequency) - carrier; - return Unit::ToString(phasenoise, "dbc/Hz", " ", 3) +"@" + Unit::ToString(offset, "Hz", " kM", 4) + " offset (" + Unit::ToString(frequency, "Hz", " kMG", 6) + " carrier)"; + auto phasenoise = parentTrace->getNoise(helperMarkers[0]->position) - carrier; + return Unit::ToString(phasenoise, "dbc/Hz", " ", 3) +"@" + Unit::ToString(offset, "Hz", " kM", 4) + " offset (" + Unit::ToString(position, "Hz", " kMG", 6) + " carrier)"; } default: return "Unknown marker type"; @@ -150,25 +205,42 @@ QString TraceMarker::readableData() QString TraceMarker::readableSettings() { - switch(type) { - case Type::Manual: - case Type::Maximum: - case Type::Minimum: - case Type::Delta: - case Type::Noise: - return Unit::ToString(frequency, "Hz", " kMG", 6); - case Type::Lowpass: - case Type::Highpass: - case Type::Bandpass: - return Unit::ToString(cutoffAmplitude, "db", " ", 3); - case Type::PeakTable: - return Unit::ToString(peakThreshold, "db", " ", 3); - case Type::TOI: - return "none"; - case Type::PhaseNoise: - return Unit::ToString(offset, "Hz", " kM", 4); - default: - return "Unhandled case"; + if(timeDomain) { + switch(type) { + case Type::Manual: + case Type::Delta: { + QString unit; + if(position <= parentTrace->getTDR().back().time) { + unit = "s"; + } else { + unit = "m"; + } + return Unit::ToString(position, unit, "fpnum k", 4); + } + default: + return "Unhandled case"; + } + } else { + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + case Type::Noise: + return Unit::ToString(position, "Hz", " kMG", 6); + case Type::Lowpass: + case Type::Highpass: + case Type::Bandpass: + return Unit::ToString(cutoffAmplitude, "db", " ", 3); + case Type::PeakTable: + return Unit::ToString(peakThreshold, "db", " ", 3); + case Type::TOI: + return "none"; + case Type::PhaseNoise: + return Unit::ToString(offset, "Hz", " kM", 4); + default: + return "Unhandled case"; + } } } @@ -181,10 +253,10 @@ QString TraceMarker::readableType() } } -void TraceMarker::setFrequency(double freq) +void TraceMarker::setPosition(double pos) { - frequency = freq; - constrainFrequency(); + position = pos; + constrainPosition(); } void TraceMarker::parentTraceDeleted(Trace *t) @@ -197,9 +269,15 @@ void TraceMarker::parentTraceDeleted(Trace *t) void TraceMarker::traceDataChanged() { // some data of the parent trace changed, check if marker data also changed - auto tracedata = parentTrace->getData(frequency); - if(tracedata != data) { - data = tracedata; + complex newdata; + if (timeDomain) { + timeData = parentTrace->getTDR(position); + newdata = complex(timeData.stepResponse, timeData.impulseResponse); + } else { + newdata = parentTrace->getData(position); + } + if (newdata != data) { + data = newdata; update(); emit rawDataChanged(); } @@ -231,43 +309,59 @@ std::set TraceMarker::getSupportedTypes() { set 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); - supported.insert(Type::PeakTable); - 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::Noise); - supported.insert(Type::TOI); - supported.insert(Type::PhaseNoise); - break; + if(timeDomain) { + // only basic markers in time domain + supported.insert(Type::Manual); + supported.insert(Type::Delta); + } else { + // all traces support some basic markers + supported.insert(Type::Manual); + supported.insert(Type::Maximum); + supported.insert(Type::Minimum); + supported.insert(Type::Delta); + supported.insert(Type::PeakTable); + 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::Noise); + supported.insert(Type::TOI); + supported.insert(Type::PhaseNoise); + break; + } } } } return supported; } -void TraceMarker::constrainFrequency() +void TraceMarker::constrainPosition() { - if(parentTrace && parentTrace->size() > 0) { - if(frequency > parentTrace->maxFreq()) { - frequency = parentTrace->maxFreq(); - } else if(frequency < parentTrace->minFreq()) { - frequency = parentTrace->minFreq(); + if(parentTrace) { + if(timeDomain) { + if(position < 0) { + position = 0; + } else if(position > parentTrace->getTDR().back().distance) { + position = parentTrace->getTDR().back().distance; + } + } else { + if(parentTrace->size() > 0) { + if(position > parentTrace->maxFreq()) { + position = parentTrace->maxFreq(); + } else if(position < parentTrace->minFreq()) { + position = parentTrace->minFreq(); + } + } } traceDataChanged(); } @@ -312,24 +406,27 @@ void TraceMarker::setType(TraceMarker::Type t) vector required_helpers; 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; + delta = nullptr; + // invalid delta marker assigned, attempt to find a matching marker + for(int pass = 0;pass < 3;pass++) { + for(auto m : model->getMarkers()) { + if(m->isTimeDomain() != isTimeDomain()) { + // markers are not on the same domain + continue; } - if(delta) { - break; + 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; @@ -387,6 +484,11 @@ QString TraceMarker::getSuffix() const return suffix; } +bool TraceMarker::isTimeDomain() const +{ + return timeDomain; +} + const std::vector &TraceMarker::getHelperMarkers() const { return helperMarkers; @@ -492,21 +594,31 @@ void TraceMarker::updateTypeFromEditor(QWidget *w) SIUnitEdit *TraceMarker::getSettingsEditor() { - switch(type) { - case Type::Manual: - case Type::Maximum: - case Type::Minimum: - case Type::Delta: - case Type::Noise: - case Type::PhaseNoise: - default: - return new SIUnitEdit("Hz", " kMG", 6); - case Type::Lowpass: - case Type::Highpass: - case Type::PeakTable: - return new SIUnitEdit("db", " ", 3); - case Type::TOI: - return nullptr; + if(timeDomain) { + switch(type) { + case Type::Manual: + case Type::Delta: + return new SIUnitEdit("", "fpnum k", 6); + default: + return nullptr; + } + } else { + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + case Type::Noise: + case Type::PhaseNoise: + default: + return new SIUnitEdit("Hz", " kMG", 6); + case Type::Lowpass: + case Type::Highpass: + case Type::PeakTable: + return new SIUnitEdit("db", " ", 3); + case Type::TOI: + return nullptr; + } } } @@ -519,7 +631,7 @@ void TraceMarker::adjustSettings(double value) case Type::Delta: case Type::Noise: default: - setFrequency(value); + setPosition(value); /* no break */ case Type::Lowpass: case Type::Highpass: @@ -552,10 +664,10 @@ void TraceMarker::update() // nothing to do break; case Type::Maximum: - setFrequency(parentTrace->findExtremumFreq(true)); + setPosition(parentTrace->findExtremumFreq(true)); break; case Type::Minimum: - setFrequency(parentTrace->findExtremumFreq(false)); + setPosition(parentTrace->findExtremumFreq(false)); break; case Type::PeakTable: { deleteHelperMarkers(); @@ -565,7 +677,7 @@ void TraceMarker::update() auto helper = new TraceMarker(model, number, this); helper->suffix = suffix; helper->assignTrace(parentTrace); - helper->setFrequency(p); + helper->setPosition(p); suffix++; helperMarkers.push_back(helper); } @@ -580,7 +692,7 @@ void TraceMarker::update() // find the maximum auto peakFreq = parentTrace->findExtremumFreq(true); // this marker shows the insertion loss - setFrequency(peakFreq); + setPosition(peakFreq); // find the cutoff frequency auto index = parentTrace->index(peakFreq); auto peakAmplitude = 20*log10(abs(parentTrace->sample(index).S)); @@ -599,7 +711,7 @@ void TraceMarker::update() index = parentTrace->size() - 1; } // set position of cutoff marker - helperMarkers[0]->setFrequency(parentTrace->sample(index).frequency); + helperMarkers[0]->setPosition(parentTrace->sample(index).frequency); } break; case Type::Bandpass: @@ -610,7 +722,7 @@ void TraceMarker::update() // find the maximum auto peakFreq = parentTrace->findExtremumFreq(true); // this marker shows the insertion loss - setFrequency(peakFreq); + setPosition(peakFreq); // find the cutoff frequencies auto index = parentTrace->index(peakFreq); auto peakAmplitude = 20*log10(abs(parentTrace->sample(index).S)); @@ -628,7 +740,7 @@ void TraceMarker::update() low_index = 0; } // set position of cutoff marker - helperMarkers[0]->setFrequency(parentTrace->sample(low_index).frequency); + helperMarkers[0]->setPosition(parentTrace->sample(low_index).frequency); auto high_index = index; while(high_index < (int) parentTrace->size()) { @@ -642,9 +754,9 @@ void TraceMarker::update() high_index = parentTrace->size() - 1; } // set position of cutoff marker - helperMarkers[1]->setFrequency(parentTrace->sample(high_index).frequency); + helperMarkers[1]->setPosition(parentTrace->sample(high_index).frequency); // set center marker inbetween cutoff markers - helperMarkers[2]->setFrequency((helperMarkers[0]->frequency + helperMarkers[1]->frequency) / 2); + helperMarkers[2]->setPosition((helperMarkers[0]->position + helperMarkers[1]->position) / 2); } break; case Type::TOI: { @@ -656,16 +768,16 @@ void TraceMarker::update() // assign marker frequenies: // this marker is the left peak, first helper the right peak. // 2nd and 3rd helpers are left and right TOI peaks - helperMarkers[0]->setFrequency(peaks[0]); - helperMarkers[1]->setFrequency(peaks[1]); + helperMarkers[0]->setPosition(peaks[0]); + helperMarkers[1]->setPosition(peaks[1]); auto freqDiff = peaks[1] - peaks[0]; - helperMarkers[2]->setFrequency(peaks[0] - freqDiff); - helperMarkers[3]->setFrequency(peaks[1] + freqDiff); + helperMarkers[2]->setPosition(peaks[0] - freqDiff); + helperMarkers[3]->setPosition(peaks[1] + freqDiff); } break; case Type::PhaseNoise: - setFrequency(parentTrace->findExtremumFreq(true)); - helperMarkers[0]->setFrequency(frequency + offset); + setPosition(parentTrace->findExtremumFreq(true)); + helperMarkers[0]->setPosition(position + offset); break; } emit dataChanged(this); @@ -686,6 +798,15 @@ std::complex TraceMarker::getData() const return data; } +Trace::TimedomainData TraceMarker::getTimeData() const +{ + Trace::TimedomainData ret = {}; + if(timeDomain) { + ret = timeData; + } + return ret; +} + bool TraceMarker::isMovable() { if(parent) { @@ -707,8 +828,8 @@ QPixmap &TraceMarker::getSymbol() return symbol; } -double TraceMarker::getFrequency() const +double TraceMarker::getPosition() const { - return frequency; + return position; } diff --git a/Software/PC_Application/Traces/tracemarker.h b/Software/PC_Application/Traces/tracemarker.h index 4ad78cf..f6e13df 100644 --- a/Software/PC_Application/Traces/tracemarker.h +++ b/Software/PC_Application/Traces/tracemarker.h @@ -15,14 +15,16 @@ class TraceMarker : public QObject public: TraceMarker(TraceMarkerModel *model, int number = 1, TraceMarker *parent = nullptr, QString descr = QString()); ~TraceMarker(); + void setTimeDomain(bool timeDomain); void assignTrace(Trace *t); Trace* trace(); QString readableData(); QString readableSettings(); QString readableType(); - double getFrequency() const; + double getPosition() const; std::complex getData() const; + Trace::TimedomainData getTimeData() const; bool isMovable(); QPixmap& getSymbol(); @@ -46,8 +48,10 @@ public: TraceMarker *helperMarker(unsigned int i); QString getSuffix() const; + bool isTimeDomain() const; + public slots: - void setFrequency(double freq); + void setPosition(double freq); signals: void deleted(TraceMarker *m); void dataChanged(TraceMarker *m); @@ -56,6 +60,7 @@ signals: void traceChanged(TraceMarker *m); void beginRemoveHelperMarkers(TraceMarker *m); void endRemoveHelperMarkers(TraceMarker *m); + void timeDomainChanged(); private slots: void parentTraceDeleted(Trace *t); @@ -95,7 +100,7 @@ private: default: return QString(); } } - void constrainFrequency(); + void constrainPosition(); void assignDeltaMarker(TraceMarker *m); void deleteHelperMarkers(); void setType(Type t); @@ -104,9 +109,12 @@ private: TraceMarkerModel *model; Trace *parentTrace; - double frequency; + double position; int number; + // Frequency domain: S parameter + // Time domain: imag part is impulse response, real part is step response std::complex data; + Trace::TimedomainData timeData; QPixmap symbol; Type type; QString suffix; @@ -120,6 +128,8 @@ private: double peakThreshold; double offset; }; + + bool timeDomain; }; #endif // TRACEMARKER_H diff --git a/Software/PC_Application/Traces/tracemarkermodel.cpp b/Software/PC_Application/Traces/tracemarkermodel.cpp index 9a45cf0..59408bf 100644 --- a/Software/PC_Application/Traces/tracemarkermodel.cpp +++ b/Software/PC_Application/Traces/tracemarkermodel.cpp @@ -70,7 +70,7 @@ TraceMarker *TraceMarkerModel::createDefaultMarker() } } while (used); auto marker = new TraceMarker(this, number); - marker->setFrequency(2150000000); + marker->setPosition(2150000000); marker->assignTrace(model.trace(0)); return marker; } @@ -199,8 +199,11 @@ bool TraceMarkerModel::setData(const QModelIndex &index, const QVariant &value, } break; case ColIndexTrace: { - auto trace = qvariant_cast(value); - m->assignTrace(trace); + auto info = qvariant_cast(value); + // always disable timedomain before switching trace + m->setTimeDomain(false); + m->assignTrace(info.trace); + m->setTimeDomain(info.isTimeDomain); } break; case ColIndexSettings: { @@ -283,7 +286,14 @@ QWidget *MarkerTraceDelegate::createEditor(QWidget *parent, const QStyleOptionVi }); auto traces = model->getModel().getTraces(); for(auto t : traces) { - c->addItem(t->name(), QVariant::fromValue(t)); + MarkerWidgetTraceInfo info; + info.trace = t; + info.isTimeDomain = false; + c->addItem(t->name(), QVariant::fromValue(info)); + if(t->TDRactive()) { + info.isTimeDomain = true; + c->addItem(t->name() + " Time Domain", QVariant::fromValue(info)); + } } return c; } @@ -292,8 +302,12 @@ void MarkerTraceDelegate::setEditorData(QWidget *editor, const QModelIndex &inde { auto marker = static_cast(index.model())->markerFromIndex(index); auto c = (QComboBox*) editor; + MarkerWidgetTraceInfo markerInfo; + markerInfo.trace = marker->trace(); + markerInfo.isTimeDomain = marker->isTimeDomain(); for(int i=0;icount();i++) { - if(qvariant_cast(c->itemData(i)) == marker->trace()) { + auto info = qvariant_cast(c->itemData(i)); + if(info == markerInfo) { c->setCurrentIndex(i); return; } diff --git a/Software/PC_Application/Traces/tracemarkermodel.h b/Software/PC_Application/Traces/tracemarkermodel.h index 6f693a2..bb85461 100644 --- a/Software/PC_Application/Traces/tracemarkermodel.h +++ b/Software/PC_Application/Traces/tracemarkermodel.h @@ -16,6 +16,19 @@ class MarkerTraceDelegate : public QStyledItemDelegate void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; }; +class MarkerWidgetTraceInfo { +public: + Trace *trace; + bool isTimeDomain; + +}; + +inline bool operator==(const MarkerWidgetTraceInfo& lhs, const MarkerWidgetTraceInfo& rhs) +{ + return lhs.trace == rhs.trace && lhs.isTimeDomain == rhs.isTimeDomain; +} +Q_DECLARE_METATYPE(MarkerWidgetTraceInfo) + class MarkerTypeDelegate : public QStyledItemDelegate { Q_OBJECT; diff --git a/Software/PC_Application/Traces/tracemodel.cpp b/Software/PC_Application/Traces/tracemodel.cpp index c198930..e90eab7 100644 --- a/Software/PC_Application/Traces/tracemodel.cpp +++ b/Software/PC_Application/Traces/tracemodel.cpp @@ -23,6 +23,9 @@ void TraceModel::addTrace(Trace *t) connect(t, &Trace::nameChanged, [=]() { emit traceNameChanged(t); }); + connect(t, &Trace::changedTDRstate, [=](bool enabled) { + emit traceTDRstateChanged(t, enabled); + }); traces.push_back(t); endInsertRows(); emit traceAdded(t); diff --git a/Software/PC_Application/Traces/tracemodel.h b/Software/PC_Application/Traces/tracemodel.h index 1531c82..c6ea214 100644 --- a/Software/PC_Application/Traces/tracemodel.h +++ b/Software/PC_Application/Traces/tracemodel.h @@ -33,6 +33,7 @@ signals: void traceRemoved(Trace *t); void requiredExcitation(bool excitePort1, bool excitePort2); void traceNameChanged(Trace *t); + void traceTDRstateChanged(Trace *t, bool enabled); public slots: void clearVNAData(); diff --git a/Software/PC_Application/Traces/traceplot.cpp b/Software/PC_Application/Traces/traceplot.cpp index b7abc71..618cf05 100644 --- a/Software/PC_Application/Traces/traceplot.cpp +++ b/Software/PC_Application/Traces/traceplot.cpp @@ -141,10 +141,7 @@ void TracePlot::mousePressEvent(QMouseEvent *event) if(!m->isMovable()) { continue; } - Trace::Data d; - d.S = m->getData(); - d.frequency = m->getFrequency(); - auto markerPoint = dataToPixel(d); + auto markerPoint = markerToPixel(m); if(markerPoint.isNull()) { // invalid, skip continue; @@ -167,29 +164,9 @@ void TracePlot::mousePressEvent(QMouseEvent *event) void TracePlot::mouseMoveEvent(QMouseEvent *event) { if(selectedMarker) { - auto t = selectedMarker->trace(); auto clickPoint = event->pos() - QPoint(marginLeft, marginTop); - auto samples = t->size(); - if(!samples) { - return; - } - double closestDistance = numeric_limits::max(); - unsigned int closestIndex = 0; - for(unsigned int i=0;isample(i); - auto plotPoint = dataToPixel(data); - if (plotPoint.isNull()) { - // destination point outside of currently displayed range - continue; - } - auto diff = plotPoint - clickPoint; - unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); - if(distance < closestDistance) { - closestDistance = distance; - closestIndex = i; - } - } - selectedMarker->setFrequency(t->sample(closestIndex).frequency); + auto trace = selectedMarker->getTrace(); + selectedMarker->setPosition(nearestTracePoint(trace, clickPoint)); } } diff --git a/Software/PC_Application/Traces/traceplot.h b/Software/PC_Application/Traces/traceplot.h index 508a7d5..0c1a779 100644 --- a/Software/PC_Application/Traces/traceplot.h +++ b/Software/PC_Application/Traces/traceplot.h @@ -40,7 +40,8 @@ protected: bool markedForDeletion; static std::set plots; - virtual QPoint dataToPixel(Trace::Data d) = 0; + virtual QPoint markerToPixel(TraceMarker *m) = 0; + virtual double nearestTracePoint(Trace *t, QPoint pixel) = 0; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; diff --git a/Software/PC_Application/Traces/tracesmithchart.cpp b/Software/PC_Application/Traces/tracesmithchart.cpp index dc8d915..2fc1c1f 100644 --- a/Software/PC_Application/Traces/tracesmithchart.cpp +++ b/Software/PC_Application/Traces/tracesmithchart.cpp @@ -46,6 +46,39 @@ QPoint TraceSmithChart::dataToPixel(Trace::Data d) return transform.map(QPoint(d.S.real() * smithCoordMax, -d.S.imag() * smithCoordMax)); } +QPoint TraceSmithChart::markerToPixel(TraceMarker *m) +{ + QPoint ret = QPoint(); + if(!m->isTimeDomain()) { + if(m->getPosition() >= sweep_fmin && m->getPosition() <= sweep_fmax) { + auto d = m->getData(); + ret = transform.map(QPoint(d.real() * smithCoordMax, -d.imag() * smithCoordMax)); + } + } + return ret; +} + +double TraceSmithChart::nearestTracePoint(Trace *t, QPoint pixel) +{ + double closestDistance = numeric_limits::max(); + unsigned int closestIndex = 0; + for(unsigned int i=0;isize();i++) { + auto data = t->sample(i); + auto plotPoint = dataToPixel(data); + if (plotPoint.isNull()) { + // destination point outside of currently displayed range + continue; + } + auto diff = plotPoint - pixel; + unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); + if(distance < closestDistance) { + closestDistance = distance; + closestIndex = i; + } + } + return t->sample(closestIndex).frequency; +} + void TraceSmithChart::draw(QPainter &p) { auto pref = Preferences::getInstance(); @@ -120,7 +153,10 @@ void TraceSmithChart::draw(QPainter &p) { // only draw markers if the trace has at least one point auto markers = t.first->getMarkers(); for(auto m : markers) { - if (limitToSpan && (m->getFrequency() < sweep_fmin || m->getFrequency() > sweep_fmax)) { + if (m->isTimeDomain()) { + continue; + } + if (limitToSpan && (m->getPosition() < sweep_fmin || m->getPosition() > sweep_fmax)) { continue; } auto coords = m->getData(); diff --git a/Software/PC_Application/Traces/tracesmithchart.h b/Software/PC_Application/Traces/tracesmithchart.h index d800362..70667be 100644 --- a/Software/PC_Application/Traces/tracesmithchart.h +++ b/Software/PC_Application/Traces/tracesmithchart.h @@ -18,7 +18,9 @@ protected: static constexpr double screenUsage = 0.9; static constexpr double smithCoordMax = 4096; - QPoint dataToPixel(Trace::Data d) override; + QPoint dataToPixel(Trace::Data d); + QPoint markerToPixel(TraceMarker *m) override; + double nearestTracePoint(Trace *t, QPoint pixel) override; //void paintEvent(QPaintEvent *event) override; virtual void updateContextMenu() override; diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp index 0f8893e..e16b07b 100644 --- a/Software/PC_Application/Traces/tracexyplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -68,14 +68,17 @@ void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool } } while(erased); - if(isTDRtype(YAxis[axis].type)) { + auto oldType = YAxis[axis].type; + if(isTDRtype(YAxis[axis].type) && !isTDRtype(type)) { + // not TDR axis anymore for(auto t : tracesAxis[axis]) { t->removeTDRinterest(); } } YAxis[axis].type = type; - for(auto t : tracesAxis[axis]) { - if(isTDRtype(type)) { + if(isTDRtype(type) && !isTDRtype(oldType)) { + // now a TDR axis + for(auto t : tracesAxis[axis]) { t->addTDRinterest(); } } @@ -256,7 +259,7 @@ void TraceXYPlot::draw(QPainter &p) } for(auto t : XAxis.ticks) { - auto xCoord = Util::Scale(t, XAxis.ticks.front(), XAxis.ticks.back(), plotAreaLeft, plotAreaLeft + plotAreaWidth); + auto xCoord = Util::Scale(t, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth); auto tickValue = Unit::ToString(t, "", prefixes, significantDigits); p.setPen(QPen(pref.General.graphColors.axis, 1)); if(displayFullFreq) { @@ -335,13 +338,6 @@ void TraceXYPlot::draw(QPainter &p) } } - auto toPlotCoordinates = [=](const QPointF& point, const class YAxis& ax) -> QPoint { - QPoint p; - p.setX(Util::Scale(point.x(), XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth)); - p.setY(Util::Scale(point.y(), ax.rangeMin, ax.rangeMax, plotAreaBottom, 0)); - return p; - }; - // plot traces p.setClipRect(QRect(plotRect.x()+1, plotRect.y()+1, plotRect.width()-2, plotRect.height()-2)); for(auto t : tracesAxis[i]) { @@ -366,8 +362,8 @@ void TraceXYPlot::draw(QPainter &p) } // scale to plot coordinates - auto p1 = toPlotCoordinates(last, YAxis[i]); - auto p2 = toPlotCoordinates(now, YAxis[i]); + auto p1 = plotValueToPixel(last, i); + auto p2 = plotValueToPixel(now, i); if(!plotRect.contains(p1) && !plotRect.contains(p2)) { // completely out of frame continue; @@ -379,11 +375,25 @@ void TraceXYPlot::draw(QPainter &p) // only draw markers on primary YAxis and if the trace has at least one point auto markers = t->getMarkers(); for(auto m : markers) { - if ((m->getFrequency() < XAxis.rangeMin || m->getFrequency() > XAxis.rangeMax)) { + if(m->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) { + // wrong domain, skip this marker continue; } - QPointF markerPoint = QPointF(m->getFrequency(), transformY(m->getData(), YAxis[i].type)); - auto point = toPlotCoordinates(markerPoint, YAxis[i]); + double xPosition; + if(m->isTimeDomain()) { + if(XAxis.type == XAxisType::Distance) { + xPosition = m->getTimeData().distance; + } else { + xPosition = m->getTimeData().time; + } + } else { + xPosition = m->getPosition(); + } + if (xPosition < XAxis.rangeMin || xPosition > XAxis.rangeMax) { + continue; + } + QPointF markerPoint = QPointF(xPosition, transformY(m->getData(), YAxis[i].type)); + auto point = plotValueToPixel(markerPoint, i); if(!plotRect.contains(point)) { // out of screen continue; @@ -412,7 +422,7 @@ void TraceXYPlot::draw(QPainter &p) auto text = "Drop here to add\n" + dropTrace->name() + "\nto XY-plot"; p.drawText(plotRect, Qt::AlignCenter, text); } else { - // both axis enabled show regions + // both axis enabled, show regions auto leftRect = plotRect; leftRect.setWidth(plotRect.width() * 0.3); auto centerRect = plotRect; @@ -621,6 +631,14 @@ double TraceXYPlot::transformY(std::complex data, TraceXYPlot::YAxisType return (1+abs(data)) / (1-abs(data)); } break; + case YAxisType::Step: + return data.real(); + case YAxisType::Impulse: + return data.imag(); + case YAxisType::Impedance: + if(abs(data.real()) < 1.0) { + return 50 * (1+data.real()) / (1-data.real()); + } default: break; } @@ -656,10 +674,7 @@ QPointF TraceXYPlot::transformY(Trace *t, unsigned int sample, TraceXYPlot::YAxi } break; case YAxisType::Impedance: { - auto step = t->getTDR()[sample].stepResponse; - if(abs(step) < 1.0) { - ret.setY(50 * (1+step) / (1-step)); - } + ret.setY(t->getTDR()[sample].impedance); if(XAxis.type == XAxisType::Distance) { ret.setX(t->getTDR()[sample].distance); } else { @@ -696,6 +711,61 @@ QPoint TraceXYPlot::dataToPixel(Trace::Data d) return p; } +QPoint TraceXYPlot::plotValueToPixel(QPointF plotValue, int Yaxis) +{ + QPoint p; + p.setX(Util::Scale(plotValue.x(), XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth)); + p.setY(Util::Scale(plotValue.y(), YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax, plotAreaBottom, 0)); + return p; +} + +QPoint TraceXYPlot::markerToPixel(TraceMarker *m) +{ + QPoint ret = QPoint(); + if(m->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) { + // invalid domain + return ret; + } + QPointF plotPoint; + plotPoint.setY(transformY(m->getData(), YAxis[0].type)); + if(m->isTimeDomain()) { + auto timedata = m->getTimeData(); + if(XAxis.type == XAxisType::Distance) { + plotPoint.setX(timedata.distance); + } else { + plotPoint.setX(timedata.time); + } + } else { + plotPoint.setX(m->getPosition()); + } + return plotValueToPixel(plotPoint, 0); +} + +double TraceXYPlot::nearestTracePoint(Trace *t, QPoint pixel) +{ + if(!tracesAxis[0].count(t)) { + // trace not enabled + return 0; + } + double closestDistance = numeric_limits::max(); + double closestXpos = 0; + auto samples = numTraceSamples(t); + for(unsigned int i=0;i data, YAxisType type); QPointF transformY(Trace *t, unsigned int sample, YAxisType type); unsigned int numTraceSamples(Trace *t); - QPoint dataToPixel(Trace::Data d) override; + QPoint dataToPixel(Trace::Data d); + QPoint plotValueToPixel(QPointF plotValue, int Yaxis); + QPoint markerToPixel(TraceMarker *m) override; + double nearestTracePoint(Trace *t, QPoint pixel) override; void traceDropped(Trace *t, QPoint position) override; std::set tracesAxis[2];