diff --git a/Software/PC_Application/Traces/Marker/marker.cpp b/Software/PC_Application/Traces/Marker/marker.cpp index 941e2c1..5f9089c 100644 --- a/Software/PC_Application/Traces/Marker/marker.cpp +++ b/Software/PC_Application/Traces/Marker/marker.cpp @@ -123,6 +123,7 @@ QString Marker::formatToString(Marker::Format f) case Format::Cutoff: return "Cutoff frequency"; case Format::CenterBandwidth: return "Center + Bandwidth"; case Format::InsertionLoss: return "Insertion loss"; + case Format::P1dB: return "P1dB"; case Format::Last: return ""; } return ""; @@ -150,23 +151,26 @@ std::vector Marker::formats() std::vector Marker::applicableFormats() { std::vector ret; - if(isTimeDomain()) { + switch(getDomain()) { + case Trace::DataType::Time: switch(type) { case Type::Manual: case Type::Delta: ret.push_back(Format::dB); ret.push_back(Format::RealImag); if(parentTrace) { - auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real(); + // check if step response data is available + auto step = parentTrace->sample(parentTrace->index(position), true).y.real(); if(!isnan(step)) { ret.push_back(Format::Impedance); } } break; default: - return {}; + break; } - } else { + break; + case Trace::DataType::Frequency: switch(type) { case Type::Manual: case Type::Delta: @@ -208,9 +212,39 @@ std::vector Marker::applicableFormats() ret.push_back(Format::AvgTone); ret.push_back(Format::AvgModulationProduct); break; - case Type::Last: + default: break; } + break; + case Trace::DataType::Power: + switch(type) { + case Type::P1dB: + ret.push_back(Format::P1dB); + [[fallthrough]]; + case Type::Manual: + case Type::Delta: + case Type::Maximum: + case Type::Minimum: + ret.push_back(Format::dB); + ret.push_back(Format::dBAngle); + ret.push_back(Format::RealImag); + if(parentTrace) { + if(parentTrace->isReflection()) { + ret.push_back(Format::Impedance); + ret.push_back(Format::VSWR); + ret.push_back(Format::SeriesR); + ret.push_back(Format::Capacitance); + ret.push_back(Format::Inductance); + ret.push_back(Format::QualityFactor); + } + } + break; + default: + break; + } + break; + case Trace::DataType::Invalid: + break; } return ret; } @@ -229,7 +263,8 @@ QString Marker::readableData(Format f) f = formatTable; } - if(isTimeDomain()) { + switch(getDomain()) { + case Trace::DataType::Time: if(type != Type::Delta) { switch(f) { case Format::dB: @@ -237,7 +272,7 @@ QString Marker::readableData(Format f) case Format::RealImag: return Unit::ToString(data.real(), "", " ", 5) + "+"+Unit::ToString(data.imag(), "", " ", 5)+"j"; case Format::Impedance: { - auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real(); + auto step = parentTrace->sample(parentTrace->index(position), true).y.real(); auto impedance = Util::SparamToImpedance(step).real(); return Unit::ToString(impedance, "Ω", "m kM", 3); } @@ -246,7 +281,7 @@ QString Marker::readableData(Format f) return "Invalid"; } } else { - if(!delta || !delta->isTimeDomain()) { + if(!delta || delta->getDomain() != Trace::DataType::Time) { return "Invalid"; } switch(f) { @@ -255,8 +290,8 @@ QString Marker::readableData(Format f) case Format::RealImag: return "Δ:"+Unit::ToString(data.real() - delta->data.real(), "", " ", 5) + "+"+Unit::ToString(data.imag() - delta->data.real(), "", " ", 5)+"j"; case Format::Impedance: { - auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real(); - auto stepDelta = delta->parentTrace->sample(delta->parentTrace->index(delta->position), Trace::SampleType::TimeStep).y.real(); + auto step = parentTrace->sample(parentTrace->index(position), true).y.real(); + auto stepDelta = delta->parentTrace->sample(delta->parentTrace->index(delta->position), true).y.real(); auto impedance = Util::SparamToImpedance(step).real(); auto impedanceDelta = Util::SparamToImpedance(stepDelta).real(); return "Δ:"+Unit::ToString(impedance - impedanceDelta, "Ω", "m kM", 3); @@ -266,7 +301,9 @@ QString Marker::readableData(Format f) return "Invalid"; } } - } else { + break; + case Trace::DataType::Frequency: + case Trace::DataType::Power: switch(type) { case Type::PeakTable: return "Found " + QString::number(helperMarkers.size()) + " peaks"; @@ -381,10 +418,20 @@ QString Marker::readableData(Format f) return ret; } break; + case Format::P1dB: + if(position == parentTrace->maxX()) { + // compression point higher than measurable with current power setting + return "Input P1dB:>"+Unit::ToString(position, "dBm", " ", 4); + } else { + return "Input P1dB:"+Unit::ToString(position, "dBm", " ", 4); + } case Format::Last: return "Invalid"; } } + break; + case Trace::DataType::Invalid: + break; } return "Invalid"; } @@ -397,17 +444,26 @@ QString Marker::readablePosition() pos -= delta->position; ret += "Δ:"; } - if(isTimeDomain()) { + switch(getDomain()) { + case Trace::DataType::Time: ret += Unit::ToString(pos, "s", "pnum ", 6); - } else { + break; + case Trace::DataType::Frequency: ret += Unit::ToString(pos, "Hz", " kMG", 6); + break; + case Trace::DataType::Power: + ret += Unit::ToString(pos, "dBm", " ", 4); + break; + case Trace::DataType::Invalid: + ret += "Invalid"; } return ret; } QString Marker::readableSettings() { - if(isTimeDomain()) { + switch(getDomain()) { + case Trace::DataType::Time: switch(type) { case Type::Manual: case Type::Delta: @@ -415,7 +471,8 @@ QString Marker::readableSettings() default: return "Unhandled case"; } - } else { + break; + case Trace::DataType::Frequency: switch(type) { case Type::Manual: case Type::Maximum: @@ -433,22 +490,41 @@ QString Marker::readableSettings() case Type::PhaseNoise: return Unit::ToString(offset, "Hz", " kM", 4); default: - return "Unhandled case"; + break; } + break; + case Trace::DataType::Power: + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + return Unit::ToString(position, "dBm", " ", 4); + case Type::P1dB: + return "none"; + default: + break; + } + break; + case Trace::DataType::Invalid: + break; } + return "Unhandled case"; } QString Marker::tooltipSettings() { - if(isTimeDomain()) { + switch(getDomain()) { + case Trace::DataType::Time: switch(type) { case Type::Manual: case Type::Delta: return "Time/Distance"; default: - return QString(); + break; } - } else { + break; + case Trace::DataType::Frequency: switch(type) { case Type::Manual: case Type::Maximum: @@ -463,10 +539,25 @@ QString Marker::tooltipSettings() return "Peak threshold"; case Type::PhaseNoise: return "Frequency offset"; + default: + break; + } + break; + case Trace::DataType::Power: + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + return "Input power position"; default: return QString(); } + break; + case Trace::DataType::Invalid: + break; } + return QString(); } QString Marker::readableType() @@ -505,8 +596,7 @@ void Marker::traceDataChanged() newdata = numeric_limits>::quiet_NaN(); } else { // some data of the parent trace changed, check if marker data also changed - auto sampleType = isTimeDomain() ? Trace::SampleType::TimeImpulse : Trace::SampleType::Frequency; - newdata = parentTrace->sample(parentTrace->index(position), sampleType).y; + newdata = parentTrace->sample(parentTrace->index(position)).y; } } if (newdata != data) { @@ -545,7 +635,7 @@ void Marker::checkDeltaMarker() return; } // Check if type of delta marker is still okay - if(!delta || delta->isTimeDomain() != isTimeDomain()) { + if(!delta || delta->getDomain() != getDomain()) { // not the same domain anymore, adjust delta assignDeltaMarker(bestDeltaCandidate()); } @@ -689,11 +779,13 @@ std::set Marker::getSupportedTypes() { set supported; if(parentTrace) { - if(isTimeDomain()) { + switch(getDomain()) { + case Trace::DataType::Time: // only basic markers in time domain supported.insert(Type::Manual); supported.insert(Type::Delta); - } else { + break; + case Trace::DataType::Frequency: // all traces support some basic markers supported.insert(Type::Manual); supported.insert(Type::Maximum); @@ -722,6 +814,16 @@ std::set Marker::getSupportedTypes() default: break; } } + break; + case Trace::DataType::Power: + supported.insert(Type::Manual); + supported.insert(Type::Maximum); + supported.insert(Type::Minimum); + supported.insert(Type::Delta); + supported.insert(Type::P1dB); + break; + case Trace::DataType::Invalid: + break; } } return supported; @@ -765,7 +867,7 @@ Marker *Marker::bestDeltaCandidate() // 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()) { + if(m->getDomain() != getDomain()) { // markers are not on the same domain continue; } @@ -881,6 +983,7 @@ bool Marker::isVisible() case Type::Maximum: case Type::Minimum: case Type::PhaseNoise: + case Type::P1dB: return true; default: return false; @@ -1152,7 +1255,8 @@ void Marker::updateTypeFromEditor(QWidget *w) SIUnitEdit *Marker::getSettingsEditor() { SIUnitEdit *ret = nullptr; - if(isTimeDomain()) { + switch(getDomain()) { + case Trace::DataType::Time: switch(type) { case Type::Manual: case Type::Delta: @@ -1162,7 +1266,8 @@ SIUnitEdit *Marker::getSettingsEditor() default: return nullptr; } - } else { + break; + case Trace::DataType::Frequency: switch(type) { case Type::Manual: case Type::Maximum: @@ -1185,17 +1290,33 @@ SIUnitEdit *Marker::getSettingsEditor() ret = new SIUnitEdit("db", " ", 3); ret->setValue(peakThreshold); break; - case Type::TOI: - case Type::Last: + default: return nullptr; } + break; + case Trace::DataType::Power: + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + ret = new SIUnitEdit("dBm", " ", 4); + ret->setValue(position); + break; + default: + return nullptr; + } + break; + case Trace::DataType::Invalid: + return nullptr; } return ret; } void Marker::adjustSettings(double value) { - if(isTimeDomain()) { + switch(getDomain()) { + case Trace::DataType::Time: switch(type) { case Type::Manual: case Type::Delta: { @@ -1211,7 +1332,8 @@ void Marker::adjustSettings(double value) default: break; } - } else { + break; + case Trace::DataType::Frequency: switch(type) { case Type::Manual: case Type::Maximum: @@ -1236,6 +1358,21 @@ void Marker::adjustSettings(double value) default: break; } + break; + case Trace::DataType::Power: + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + setPosition(value); + break; + default: + break; + } + break; + case Trace::DataType::Invalid: + break; } update(); } @@ -1260,10 +1397,10 @@ void Marker::update() // nothing to do break; case Type::Maximum: - setPosition(parentTrace->findExtremumFreq(true)); + setPosition(parentTrace->findExtremum(true)); break; case Type::Minimum: - setPosition(parentTrace->findExtremumFreq(false)); + setPosition(parentTrace->findExtremum(false)); break; case Type::PeakTable: { deleteHelperMarkers(); @@ -1289,7 +1426,7 @@ void Marker::update() break; } else { // find the maximum - auto peakFreq = parentTrace->findExtremumFreq(true); + auto peakFreq = parentTrace->findExtremum(true); // this marker shows the insertion loss setPosition(peakFreq); // find the cutoff frequency @@ -1319,7 +1456,7 @@ void Marker::update() break; } else { // find the maximum - auto peakFreq = parentTrace->findExtremumFreq(true); + auto peakFreq = parentTrace->findExtremum(true); // this marker shows the insertion loss setPosition(peakFreq); // find the cutoff frequencies @@ -1375,9 +1512,26 @@ void Marker::update() } break; case Type::PhaseNoise: - setPosition(parentTrace->findExtremumFreq(true)); + setPosition(parentTrace->findExtremum(true)); helperMarkers[0]->setPosition(position + offset); break; + case Type::P1dB: { + // find maximum + auto maxpos = parentTrace->findExtremum(true); + // starting at the maximum point, traverse trace data towards higher power levels until amplitude dropped by 1dB + auto maxindex = parentTrace->index(maxpos); + auto maxpower = abs(parentTrace->sample(maxindex).y); + double p1db = parentTrace->maxX(); + for(unsigned int i = maxindex; i < parentTrace->size(); i++) { + auto sample = parentTrace->sample(i); + if(Util::SparamTodB(maxpower) - Util::SparamTodB(sample.y) >= 1.0) { + p1db = sample.x; + break; + } + } + setPosition(p1db); + } + break; case Type::Last: break; } @@ -1424,13 +1578,11 @@ double Marker::getPosition() const return position; } -bool Marker::isTimeDomain() +Trace::DataType Marker::getDomain() { if(parentTrace) { - if(parentTrace->outputType() == Trace::DataType::Time) { - return true; - } + return parentTrace->outputType(); } - return false; + return Trace::DataType::Invalid; } diff --git a/Software/PC_Application/Traces/Marker/marker.h b/Software/PC_Application/Traces/Marker/marker.h index 8953959..365fb08 100644 --- a/Software/PC_Application/Traces/Marker/marker.h +++ b/Software/PC_Application/Traces/Marker/marker.h @@ -42,6 +42,8 @@ public: TOI, // third order intercept point AvgTone, // average level of tone AvgModulationProduct, // average level of modulation products + // compression parameters + P1dB, // power level at 1dB compression // keep last at end Last, }; @@ -60,7 +62,7 @@ public: double getPosition() const; std::complex getData() const; bool isMovable(); - bool isTimeDomain(); + Trace::DataType getDomain(); QPixmap& getSymbol(); @@ -81,6 +83,7 @@ public: Bandpass, TOI, PhaseNoise, + P1dB, // keep last at end Last, }; @@ -154,6 +157,7 @@ private: case Type::Bandpass: return "Bandpass"; case Type::TOI: return "TOI/IP3"; case Type::PhaseNoise: return "Phase noise"; + case Type::P1dB: return "1dB compression"; default: return QString(); } } diff --git a/Software/PC_Application/Traces/Marker/markergroup.cpp b/Software/PC_Application/Traces/Marker/markergroup.cpp index 3f3f827..8be2695 100644 --- a/Software/PC_Application/Traces/Marker/markergroup.cpp +++ b/Software/PC_Application/Traces/Marker/markergroup.cpp @@ -68,10 +68,10 @@ bool MarkerGroup::applicable(Marker *m) } if(markers.size() == 0) { // first marker in group - isTimeDomain = m->isTimeDomain(); + domain = m->getDomain(); } else { // check domain - if(isTimeDomain != m->isTimeDomain()) { + if(domain != m->getDomain()) { // only markers of the same domain are allowed in a group return false; } diff --git a/Software/PC_Application/Traces/Marker/markergroup.h b/Software/PC_Application/Traces/Marker/markergroup.h index a64cbad..db0603b 100644 --- a/Software/PC_Application/Traces/Marker/markergroup.h +++ b/Software/PC_Application/Traces/Marker/markergroup.h @@ -11,7 +11,7 @@ class MarkerGroup : public QObject public: MarkerGroup(unsigned int number) : adjustingMarkers(false), - isTimeDomain(false), + domain(Trace::DataType::Invalid), markers(), number(number){}; ~MarkerGroup(); @@ -31,7 +31,7 @@ private: // Check if marker still applicable for group, remove if necessary void checkMarker(Marker *m); bool adjustingMarkers; - bool isTimeDomain; + Trace::DataType domain; std::set markers; unsigned int number; }; diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index 66eb536..6907165 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -838,12 +838,8 @@ double Trace::maxX() } } -double Trace::findExtremumFreq(bool max) +double Trace::findExtremum(bool max) { - if(lastMath->getDataType() != DataType::Frequency) { - // not in frequency domain - return numeric_limits::quiet_NaN(); - } double compare = max ? numeric_limits::min() : numeric_limits::max(); double freq = 0.0; for(auto sample : lastMath->rData()) { @@ -913,10 +909,10 @@ std::vector Trace::findPeakFrequencies(unsigned int maxPeaks, double min return frequencies; } -Trace::Data Trace::sample(unsigned int index, SampleType type) const +Trace::Data Trace::sample(unsigned int index, bool getStepResponse) const { auto data = lastMath->getSample(index); - if(type == SampleType::TimeStep) { + if(outputType() == Trace::DataType::Time && getStepResponse) { // exchange impulse data with step data data.y = lastMath->getStepResponse(index); } diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index c47e170..fecc664 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -66,7 +66,7 @@ public: unsigned int size() const; double minX(); double maxX(); - double findExtremumFreq(bool max); + double findExtremum(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. @@ -79,8 +79,8 @@ public: TimeStep, }; - Data sample(unsigned int index, SampleType type = SampleType::Frequency) const; - // returns a (possibly interpolated sample) at a specified frequency/time + Data sample(unsigned int index, bool getStepResponse = false) const; + // returns a (possibly interpolated sample) at a specified frequency/time/power Data interpolatedSample(double x); QString getFilename() const; unsigned int getFileParameter() const; diff --git a/Software/PC_Application/Traces/tracewidget.cpp b/Software/PC_Application/Traces/tracewidget.cpp index 007fd28..baf963b 100644 --- a/Software/PC_Application/Traces/tracewidget.cpp +++ b/Software/PC_Application/Traces/tracewidget.cpp @@ -239,7 +239,7 @@ void TraceWidget::SetupSCPI() if(!t) { return "ERROR"; } - auto d = t->interpolatedSample(t->findExtremumFreq(true)); + auto d = t->interpolatedSample(t->findExtremum(true)); return QString::number(d.x)+","+createStringFromData(t, d); })); add(new SCPICommand("MINAmplitude", nullptr, [=](QStringList params) -> QString { @@ -247,7 +247,7 @@ void TraceWidget::SetupSCPI() if(!t) { return "ERROR"; } - auto d = t->interpolatedSample(t->findExtremumFreq(false)); + auto d = t->interpolatedSample(t->findExtremum(false)); return QString::number(d.x)+","+createStringFromData(t, d); })); add(new SCPICommand("NEW", [=](QStringList params) -> QString { diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp index db56790..e760b83 100644 --- a/Software/PC_Application/Traces/tracexyplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -498,20 +498,7 @@ 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->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) { -// // wrong domain, skip this marker -// continue; -// } - double xPosition; -// if(m->isTimeDomain()) { -// if(XAxis.type == XAxisType::Distance) { -// xPosition = m->getTimeData().distance; -// } else { -// xPosition = m->getTimeData().time; -// } -// } else { - xPosition = m->getPosition(); -// } + double xPosition = m->getPosition(); if (xPosition < XAxis.rangeMin || xPosition > XAxis.rangeMax) { // marker not in graph range continue; @@ -905,10 +892,10 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo ret.setY(Util::SparamTodB(data.y)); break; case YAxisType::Step: - ret.setY(t->sample(sample, Trace::SampleType::TimeStep).y.real()); + ret.setY(t->sample(sample, true).y.real()); break; case YAxisType::Impedance: { - double step = t->sample(sample, Trace::SampleType::TimeStep).y.real(); + double step = t->sample(sample, true).y.real(); if(abs(step) < 1.0) { ret.setY(Util::SparamToImpedance(step).real()); }