Preserve phase information in spectrum analyzer mode

This commit is contained in:
Jan Käberich 2022-07-17 18:04:05 +02:00
parent 787f0d0223
commit 963578cc9f
15 changed files with 113 additions and 65 deletions

View file

@ -425,7 +425,7 @@ void AmplitudeCalDialog::AutomaticMeasurementDialog()
void AmplitudeCalDialog::ReceivedMeasurement(Protocol::SpectrumAnalyzerResult res)
{
MeasurementResult m = {.port1 = Util::SparamTodB(res.port1), .port2 = Util::SparamTodB(res.port2)};
MeasurementResult m = {.port1 = Util::SparamTodB(complex<double>(res.real_port1, res.imag_port1)), .port2 = Util::SparamTodB(complex<double>(res.real_port2, res.imag_port2))};
sweepMeasurements.push_back(m);
if(res.pointNum == automaticSweepPoints - 1) {
// sweep finished, find maximum for each port

View file

@ -24,6 +24,7 @@ HEADERS += \
Device/manualcontroldialog.h \
Generator/generator.h \
Generator/signalgenwidget.h \
SpectrumAnalyzer/sadata.h \
SpectrumAnalyzer/spectrumanalyzer.h \
SpectrumAnalyzer/tracewidgetsa.h \
Tools/eseries.h \

View file

@ -0,0 +1,24 @@
#ifndef SADATA_H
#define SADATA_H
#include "Tools/parameters.h"
#include <complex>
class SAData {
public:
SAData() = default;
SAData(const Protocol::SpectrumAnalyzerResult &d) {
port1 = std::complex<double>(d.real_port1, d.imag_port1);
port2 = std::complex<double>(d.real_port2, d.imag_port2);
frequency = d.frequency;
time = (double) d.us / 1000000.0;
pointNum = d.pointNum;
}
double frequency;
double time;
std::complex<double> port1, port2;
unsigned int pointNum;
};
#endif // SADATA_H

View file

@ -17,6 +17,7 @@
#include "Device/firmwareupdatedialog.h"
#include "preferences.h"
#include "Generator/signalgenwidget.h"
#include "sadata.h"
#include <QDockWidget>
#include <QDesktopWidget>
@ -330,6 +331,7 @@ nlohmann::json SpectrumAnalyzer::toJSON()
acq["detector"] = DetectorToString((Detector) settings.Detector).toStdString();
acq["signal ID"] = settings.SignalID ? true : false;
sweep["acquisition"] = acq;
sweep["averages"] = averages;
nlohmann::json tracking;
tracking["enabled"] = settings.trackingGenerator ? true : false;
tracking["port"] = settings.trackingGeneratorPort ? 2 : 1;
@ -425,6 +427,7 @@ void SpectrumAnalyzer::fromJSON(nlohmann::json j)
EnableNormalization(false);
}
}
SetAveraging(sweep.value("averages", 0));
SetSingleSweep(sweep.value("single", singleSweep));
}
}
@ -455,15 +458,17 @@ void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d)
return;
}
d = average.process(d);
auto sd = SAData(d);
sd = average.process(sd);
if(settings.f_start == settings.f_stop) {
// keep track of first point time
if(d.pointNum == 0) {
firstPointTime = d.us;
d.us = 0;
if(sd.pointNum == 0) {
firstPointTime = sd.time;
sd.time = 0;
} else {
d.us -= firstPointTime;
sd.time -= firstPointTime;
}
}
@ -472,8 +477,8 @@ void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d)
// this is the last averaging sweep, use values for normalization
if(normalize.port1Correction.size() > 0 || d.pointNum == 0) {
// add measurement
normalize.port1Correction.push_back(d.port1);
normalize.port2Correction.push_back(d.port2);
normalize.port1Correction.push_back(abs(sd.port1));
normalize.port2Correction.push_back(abs(sd.port2));
if(d.pointNum == settings.pointNum - 1) {
// this was the last point
normalize.measuring = false;
@ -490,14 +495,14 @@ void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d)
}
if(normalize.active) {
d.port1 /= normalize.port1Correction[d.pointNum];
d.port2 /= normalize.port2Correction[d.pointNum];
sd.port1 /= normalize.port1Correction[d.pointNum];
sd.port2 /= normalize.port2Correction[d.pointNum];
double corr = pow(10.0, normalize.Level->value() / 20.0);
d.port1 *= corr;
d.port2 *= corr;
sd.port1 *= corr;
sd.port2 *= corr;
}
traceModel.addSAData(d, settings);
traceModel.addSAData(sd, settings);
emit dataChanged();
if(d.pointNum == settings.pointNum - 1) {
UpdateAverageCount();

View file

@ -354,6 +354,7 @@ std::set<YAxis::Type> YAxis::getSupported(XAxis::Type type, TraceModel::DataSour
case XAxis::Type::Frequency:
ret.insert(YAxis::Type::Magnitude);
ret.insert(YAxis::Type::MagnitudedBuV);
ret.insert(YAxis::Type::Phase);
break;
default:
break;

View file

@ -250,7 +250,7 @@ void TraceModel::addVNAData(const VNAData& d, TraceMath::DataType datatype)
}
}
void TraceModel::addSAData(const Protocol::SpectrumAnalyzerResult& d, const Protocol::SpectrumAnalyzerSettings& settings)
void TraceModel::addSAData(const SAData &d, const Protocol::SpectrumAnalyzerSettings& settings)
{
source = DataSource::SA;
for(auto t : traces) {
@ -260,13 +260,13 @@ void TraceModel::addSAData(const Protocol::SpectrumAnalyzerResult& d, const Prot
if(settings.f_start == settings.f_stop) {
// in zerospan mode, insert data by index
index = d.pointNum;
td.x = (double) d.us / 1000000.0;
td.x = (double) d.time / 1000000.0;
} else {
td.x = d.frequency;
}
switch(t->liveParameter()) {
case Trace::LiveParameter::Port1: td.y = complex<double>(d.port1, 0); break;
case Trace::LiveParameter::Port2: td.y = complex<double>(d.port2, 0); break;
case Trace::LiveParameter::Port1: td.y = d.port1; break;
case Trace::LiveParameter::Port2: td.y = d.port2; break;
default:
// not a SA trace, skip
continue;

View file

@ -5,6 +5,7 @@
#include "savable.h"
#include "trace.h"
#include "VNA/vnadata.h"
#include "SpectrumAnalyzer/sadata.h"
#include <QAbstractTableModel>
#include <vector>
@ -66,7 +67,7 @@ signals:
public slots:
void clearLiveData();
void addVNAData(const VNAData& d, TraceMath::DataType datatype);
void addSAData(const Protocol::SpectrumAnalyzerResult& d, const Protocol::SpectrumAnalyzerSettings& settings);
void addSAData(const SAData& d, const Protocol::SpectrumAnalyzerSettings& settings);
private:
DataSource source;

View file

@ -737,6 +737,7 @@ nlohmann::json VNA::toJSON()
sweep["power"] = power;
sweep["points"] = settings.npoints;
sweep["IFBW"] = settings.bandwidth;
sweep["averages"] = averages;
j["sweep"] = sweep;
j["traces"] = traceModel.toJSON();
@ -793,6 +794,7 @@ void VNA::fromJSON(nlohmann::json j)
type = SweepType::Frequency;
}
SetSweepType(type);
SetAveraging(sweep.value("averages", 0));
SetSingleSweep(sweep.value("single", singleSweep));
}
}

View file

@ -104,7 +104,7 @@ VNAData Averaging::process(VNAData d)
return d;
}
Protocol::SpectrumAnalyzerResult Averaging::process(Protocol::SpectrumAnalyzerResult d)
SAData Averaging::process(SAData d)
{
if (d.pointNum == avg.size()) {
// add moving average entry
@ -123,16 +123,21 @@ Protocol::SpectrumAnalyzerResult Averaging::process(Protocol::SpectrumAnalyzerRe
deque->pop_front();
}
if(averages == 1) {
// no need for any calculation, no averaging set
return d;
}
switch(mode) {
case Mode::Mean: {
// calculate average
complex<double> sum[2];
for(auto s : *deque) {
sum[0] += s[0];
sum[1] += s[1];
sum[0] += abs(s[0]);
sum[1] += abs(s[1]);
}
d.port1 = abs(sum[0] / (double) (deque->size()));
d.port2 = abs(sum[1] / (double) (deque->size()));
d.port1 = sum[0] / (double) (deque->size());
d.port2 = sum[1] / (double) (deque->size());
}
break;
case Mode::Median: {
@ -143,12 +148,12 @@ Protocol::SpectrumAnalyzerResult Averaging::process(Protocol::SpectrumAnalyzerRe
port2.reserve(size);
for(auto d : *deque) {
port1.insert(upper_bound(port1.begin(), port1.end(), abs(d[0])), abs(d[0]));
port2.insert(upper_bound(port2.begin(), port2.end(), abs(d[0])), abs(d[0]));
port2.insert(upper_bound(port2.begin(), port2.end(), abs(d[1])), abs(d[1]));
}
if(size & 0x01) {
// odd number of samples
d.port1 = port1[size / 2];
d.port2 = port1[size / 2];
d.port2 = port2[size / 2];
} else {
// even number, use average of middle samples
d.port1 = (port1[size / 2 - 1] + port1[size / 2]) / 2;

View file

@ -3,6 +3,7 @@
#include "Device/device.h"
#include "VNA/vnadata.h"
#include "SpectrumAnalyzer/sadata.h"
#include <array>
#include <deque>
@ -20,7 +21,7 @@ public:
void reset(unsigned int points);
void setAverages(unsigned int a);
VNAData process(VNAData d);
Protocol::SpectrumAnalyzerResult process(Protocol::SpectrumAnalyzerResult d);
SAData process(SAData d);
// Returns the number of averaged sweeps. Value is incremented whenever the last point of the sweep is added.
// Returned values are in range 0 to averages
unsigned int getLevel();

View file

@ -4,7 +4,7 @@
namespace Protocol {
static constexpr uint16_t Version = 11;
static constexpr uint16_t Version = 12;
#pragma pack(push, 1)
@ -150,8 +150,8 @@ using SpectrumAnalyzerSettings = struct _spectrumAnalyzerSettings {
};
using SpectrumAnalyzerResult = struct _spectrumAnalyzerResult {
float port1;
float port2;
float real_port1, imag_port1;
float real_port2, imag_port2;
union {
struct {
// for non-zero span

View file

@ -452,11 +452,9 @@ FPGA::DFTResult FPGA::ReadDFTResult() {
int64_t p1real = assembleSampleResultValue(&recv[18]);
// LOG_INFO("DFT raw: %ld, %ld, %ld, %ld", (int32_t) p1real, (int32_t) p1imag, (int32_t) p2real, (int32_t) p2imag);
// Log_Flush();
auto p1 = std::complex<float>(p1real, p1imag);
auto p2 = std::complex<float>(p2real, p2imag);
DFTResult res;
res.P1 = std::complex<float>(p1real, p1imag);
res.P2 = std::complex<float>(p2real, p2imag);
// LOG_INFO("DFT: %ld, %ld, %ld, %ld", (int32_t) p1.real(), (int32_t) p1.imag(), (int32_t) p2.real(), (int32_t) p2.imag());
res.P1 = std::abs(p1);
res.P2 = std::abs(p2);
return res;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <complex>
#include "Flash.hpp"
#include "max2871.hpp"
@ -40,7 +41,7 @@ using SamplingResult = struct _samplingresult {
};
using DFTResult = struct _dftresult {
float P1, P2;
std::complex<float> P1, P2;
};
using ADCLimits = struct _adclimits {

View file

@ -19,7 +19,7 @@ static uint8_t *USBD_Class_GetDeviceQualifierDescriptor (uint16_t *length);
static usbd_recv_callback_t cb;
static uint8_t usb_receive_buffer[1024];
static uint8_t usb_transmit_fifo[8192];
static uint8_t usb_transmit_fifo[6144];
static uint16_t usb_transmit_read_index = 0;
static uint16_t usb_transmit_fifo_level = 0;
static bool data_transmission_active = false;

View file

@ -22,13 +22,17 @@ static uint32_t binSize;
static uint8_t signalIDstep;
static uint32_t sampleNum;
static Protocol::PacketInfo p;
// Result values
static std::complex<double> res_port1, res_port2;
static bool active = false;
static uint32_t lastLO2;
static uint32_t actualRBW;
static uint16_t DFTpoints;
static bool negativeDFT; // if true, a positive frequency shift at input results in a negative shift at the 2.IF. Handle DFT accordingly
static float port1Measurement[FPGA::DFTbins], port2Measurement[FPGA::DFTbins];
static std::complex<float> port1Measurement[FPGA::DFTbins], port2Measurement[FPGA::DFTbins];
static uint8_t signalIDsteps;
static std::array<uint8_t, 4> signalIDprescalers;
@ -274,15 +278,15 @@ bool SA::MeasurementDone(const FPGA::SamplingResult &result) {
FPGA::AbortSweep();
for(uint16_t i=0;i<DFTpoints;i++) {
float port1, port2;
std::complex<double> port1, port2;
if (s.UseDFT) {
// use DFT result
auto dft = FPGA::ReadDFTResult();
port1 = dft.P1;
port2 = dft.P2;
} else {
port1 = fabs(std::complex<float>(result.P1I, result.P1Q));
port2 = fabs(std::complex<float>(result.P2I, result.P2Q));
port1 = std::complex<float>(result.P1I, result.P1Q);
port2 = std::complex<float>(result.P2I, result.P2Q);
}
port1 /= sampleNum;
port2 /= sampleNum;
@ -293,10 +297,11 @@ bool SA::MeasurementDone(const FPGA::SamplingResult &result) {
index = DFTpoints - i - 1;
}
if(port1 < port1Measurement[index]) {
// Signal ID: only keep the lowest measurement
if(abs(port1) < abs(port1Measurement[index])) {
port1Measurement[index] = port1;
}
if(port2 < port2Measurement[index]) {
if(abs(port2) < abs(port2Measurement[index])) {
port2Measurement[index] = port2;
}
}
@ -332,46 +337,46 @@ void SA::Work() {
switch(det) {
case Detector::PosPeak:
if(pointInBin == 0) {
p.spectrumResult.port1 = std::numeric_limits<float>::min();
p.spectrumResult.port2 = std::numeric_limits<float>::min();
res_port1 = std::numeric_limits<std::complex<float>>::min();
res_port2 = std::numeric_limits<std::complex<float>>::min();
}
if(port1Measurement[i] > p.spectrumResult.port1) {
p.spectrumResult.port1 = port1Measurement[i];
if(abs(port1Measurement[i]) > abs(res_port1)) {
res_port1 = port1Measurement[i];
}
if(port2Measurement[i] > p.spectrumResult.port2) {
p.spectrumResult.port2 = port2Measurement[i];
if(abs(port2Measurement[i]) > abs(res_port2)) {
res_port2 = port2Measurement[i];
}
break;
case Detector::NegPeak:
if(pointInBin == 0) {
p.spectrumResult.port1 = std::numeric_limits<float>::max();
p.spectrumResult.port2 = std::numeric_limits<float>::max();
res_port1 = std::complex<float>(std::numeric_limits<float>::max());
res_port2 = std::complex<float>(std::numeric_limits<float>::max());
}
if(port1Measurement[i] < p.spectrumResult.port1) {
p.spectrumResult.port1 = port1Measurement[i];
if(abs(port1Measurement[i]) < abs(res_port1)) {
res_port1 = port1Measurement[i];
}
if(port2Measurement[i] < p.spectrumResult.port2) {
p.spectrumResult.port2 = port2Measurement[i];
if(abs(port2Measurement[i]) < abs(res_port2)) {
res_port2 = port2Measurement[i];
}
break;
case Detector::Sample:
if(pointInBin <= binSize / 2) {
// still in first half of bin, simply overwrite
p.spectrumResult.port1 = port1Measurement[i];
p.spectrumResult.port2 = port2Measurement[i];
res_port1 = port1Measurement[i];
res_port2 = port2Measurement[i];
}
break;
case Detector::Average:
if(pointInBin == 0) {
p.spectrumResult.port1 = 0;
p.spectrumResult.port2 = 0;
res_port1 = 0;
res_port2 = 0;
}
p.spectrumResult.port1 += port1Measurement[i];
p.spectrumResult.port2 += port2Measurement[i];
res_port1 += port1Measurement[i];
res_port2 += port2Measurement[i];
if(lastPointInBin) {
// calculate average
p.spectrumResult.port1 /= binSize;
p.spectrumResult.port2 /= binSize;
res_port1 /= binSize;
res_port2 /= binSize;
}
break;
case Detector::Normal:
@ -396,13 +401,17 @@ void SA::Work() {
p.spectrumResult.frequency = s.f_start + (s.f_stop - s.f_start) * binIndex / (s.pointNum - 1);
}
// scale approximately (constant determined empirically)
p.spectrumResult.port1 /= 253000000.0;
p.spectrumResult.port2 /= 253000000.0;
res_port1 /= 253000000.0;
res_port2 /= 253000000.0;
if (s.applyReceiverCorrection) {
auto correction = Cal::ReceiverCorrection(p.spectrumResult.frequency);
p.spectrumResult.port1 *= powf(10.0f, (float) correction.port1 / 100.0f / 20.0f);
p.spectrumResult.port2 *= powf(10.0f, (float) correction.port2 / 100.0f / 20.0f);
res_port1 *= powf(10.0f, (float) correction.port1 / 100.0f / 20.0f);
res_port2 *= powf(10.0f, (float) correction.port2 / 100.0f / 20.0f);
}
p.spectrumResult.real_port1 = real(res_port1);
p.spectrumResult.imag_port1 = imag(res_port1);
p.spectrumResult.real_port2 = real(res_port2);
p.spectrumResult.imag_port2 = imag(res_port2);
p.spectrumResult.pointNum = binIndex;
Communication::Send(p);
}